From 1ac42bb56d5a25edd80c6ddf2fbe7976d11b5678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 19 Jul 2020 13:30:29 +0200 Subject: [PATCH] Migrate presenters from rxjava to coroutines flow (#894) --- app/build.gradle | 6 +- .../wulkanowy/data/TestDispatchersProvider.kt | 11 + .../data/repositories/TestEntityCreator.kt | 15 + .../attendance/AttendanceLocalTest.kt | 3 +- .../CompletedLessonsLocalTest.kt | 3 +- .../data/repositories/exam/ExamLocalTest.kt | 3 +- .../data/repositories/grade/GradeLocalTest.kt | 3 +- .../repositories/grade/GradeRepositoryTest.kt | 107 ++++--- .../GradeStatisticsLocalTest.kt | 20 +- .../luckynumber/LuckyNumberLocalTest.kt | 3 +- .../repositories/student/StudentLocalTest.kt | 3 +- .../timetable/TimetableLocalTest.kt | 3 +- .../timetable/TimetableRepositoryTest.kt | 85 ++---- app/src/main/AndroidManifest.xml | 3 +- .../java/io/github/wulkanowy/data/Resource.kt | 23 ++ .../wulkanowy/data/db/dao/AttendanceDao.kt | 3 +- .../data/db/dao/AttendanceSummaryDao.kt | 3 +- .../data/db/dao/CompletedLessonsDao.kt | 3 +- .../github/wulkanowy/data/db/dao/ExamDao.kt | 3 +- .../github/wulkanowy/data/db/dao/GradeDao.kt | 3 +- .../data/db/dao/GradePointsStatisticsDao.kt | 5 +- .../data/db/dao/GradeStatisticsDao.kt | 5 +- .../wulkanowy/data/db/dao/GradeSummaryDao.kt | 3 +- .../wulkanowy/data/db/dao/HomeworkDao.kt | 3 +- .../wulkanowy/data/db/dao/LuckyNumberDao.kt | 3 +- .../wulkanowy/data/db/dao/MessagesDao.kt | 7 +- .../wulkanowy/data/db/dao/MobileDeviceDao.kt | 3 +- .../github/wulkanowy/data/db/dao/NoteDao.kt | 3 +- .../github/wulkanowy/data/db/dao/SchoolDao.kt | 3 +- .../wulkanowy/data/db/dao/SubjectDao.kt | 3 +- .../wulkanowy/data/db/dao/TeacherDao.kt | 3 +- .../wulkanowy/data/db/dao/TimetableDao.kt | 3 +- .../appcreator/AppCreatorRepository.kt | 12 +- .../attendance/AttendanceLocal.kt | 3 +- .../attendance/AttendanceRepository.kt | 26 +- .../AttendanceSummaryLocal.kt | 3 +- .../AttendanceSummaryRepository.kt | 20 +- .../completedlessons/CompletedLessonsLocal.kt | 3 +- .../CompletedLessonsRepository.kt | 23 +- .../data/repositories/exam/ExamLocal.kt | 3 +- .../data/repositories/exam/ExamRepository.kt | 23 +- .../data/repositories/grade/GradeLocal.kt | 5 +- .../repositories/grade/GradeRepository.kt | 62 ++-- .../gradestatistics/GradeStatisticsLocal.kt | 25 +- .../GradeStatisticsRepository.kt | 52 ++-- .../repositories/homework/HomeworkLocal.kt | 3 +- .../homework/HomeworkRepository.kt | 20 +- .../repositories/logger/LoggerRepository.kt | 14 +- .../luckynumber/LuckyNumberLocal.kt | 3 +- .../luckynumber/LuckyNumberRepository.kt | 24 +- .../data/repositories/message/MessageLocal.kt | 5 +- .../repositories/message/MessageRepository.kt | 68 ++--- .../mobiledevice/MobileDeviceLocal.kt | 3 +- .../mobiledevice/MobileDeviceRepository.kt | 20 +- .../data/repositories/note/NoteLocal.kt | 3 +- .../data/repositories/note/NoteRepository.kt | 27 +- .../recipient/RecipientRepository.kt | 16 +- .../reportingunit/ReportingUnitRepository.kt | 22 +- .../data/repositories/school/SchoolLocal.kt | 3 +- .../repositories/school/SchoolRepository.kt | 16 +- .../semester/SemesterRepository.kt | 14 +- .../data/repositories/student/StudentLocal.kt | 27 +- .../data/repositories/subject/SubjectLocal.kt | 3 +- .../repositories/subject/SubjectRepository.kt | 20 +- .../data/repositories/teacher/TeacherLocal.kt | 3 +- .../repositories/teacher/TeacherRepository.kt | 20 +- .../repositories/timetable/TimetableLocal.kt | 3 +- .../timetable/TimetableRepository.kt | 20 +- .../sync/works/AttendanceSummaryWork.kt | 2 +- .../services/sync/works/AttendanceWork.kt | 4 +- .../sync/works/CompletedLessonWork.kt | 5 +- .../wulkanowy/services/sync/works/ExamWork.kt | 2 +- .../sync/works/GradeStatisticsWork.kt | 13 +- .../services/sync/works/GradeWork.kt | 9 +- .../services/sync/works/HomeworkWork.kt | 4 +- .../services/sync/works/LuckyNumberWork.kt | 5 +- .../services/sync/works/MessageWork.kt | 5 +- .../wulkanowy/services/sync/works/NoteWork.kt | 5 +- .../services/sync/works/RecipientWork.kt | 5 +- .../services/sync/works/TeacherWork.kt | 2 +- .../services/sync/works/TimetableWork.kt | 2 +- .../wulkanowy/services/sync/works/Work.kt | 10 +- .../github/wulkanowy/ui/base/BasePresenter.kt | 77 +++-- .../github/wulkanowy/ui/base/ErrorHandler.kt | 2 +- .../about/contributor/ContributorPresenter.kt | 19 +- .../modules/about/license/LicenseAdapter.kt | 2 +- .../modules/about/license/LicensePresenter.kt | 29 +- .../about/logviewer/LogViewerPresenter.kt | 50 ++-- .../ui/modules/account/AccountPresenter.kt | 93 +++--- .../modules/attendance/AttendancePresenter.kt | 139 +++++---- .../summary/AttendanceSummaryPresenter.kt | 102 +++---- .../ui/modules/exam/ExamPresenter.kt | 84 +++--- .../ui/modules/grade/GradeAverageProvider.kt | 78 ++--- .../ui/modules/grade/GradePresenter.kt | 52 ++-- .../grade/details/GradeDetailsPresenter.kt | 139 ++++----- .../statistics/GradeStatisticsPresenter.kt | 126 ++++---- .../grade/summary/GradeSummaryPresenter.kt | 72 +++-- .../ui/modules/homework/HomeworkFragment.kt | 4 - .../ui/modules/homework/HomeworkPresenter.kt | 89 +++--- .../homework/details/HomeworkDetailsDialog.kt | 1 - .../details/HomeworkDetailsPresenter.kt | 29 +- .../login/advanced/LoginAdvancedPresenter.kt | 65 +++-- .../modules/login/form/LoginFormPresenter.kt | 59 ++-- .../login/recover/LoginRecoverPresenter.kt | 68 ++--- .../LoginStudentSelectPresenter.kt | 92 +++--- .../login/symbol/LoginSymbolPresenter.kt | 71 +++-- .../luckynumber/LuckyNumberPresenter.kt | 82 +++--- .../LuckyNumberWidgetConfigurePresenter.kt | 37 ++- .../LuckyNumberWidgetProvider.kt | 5 +- .../ui/modules/message/MessageFragment.kt | 5 - .../ui/modules/message/MessagePresenter.kt | 26 +- .../message/preview/MessagePreviewFragment.kt | 4 - .../preview/MessagePreviewPresenter.kt | 93 +++--- .../message/preview/MessagePreviewView.kt | 2 - .../message/send/SendMessagePresenter.kt | 147 +++++----- .../message/tab/MessageTabPresenter.kt | 105 ++++--- .../mobiledevice/MobileDevicePresenter.kt | 113 ++++--- .../token/MobileDeviceTokenPresenter.kt | 48 +-- .../ui/modules/note/NotePresenter.kt | 87 +++--- .../SchoolAndTeachersPresenter.kt | 16 +- .../school/SchoolPresenter.kt | 80 ++--- .../teacher/TeacherPresenter.kt | 72 ++--- .../ui/modules/splash/SplashPresenter.kt | 20 +- .../modules/timetable/TimetablePresenter.kt | 89 +++--- .../completed/CompletedLessonsPresenter.kt | 82 +++--- .../TimetableWidgetConfigurePresenter.kt | 37 ++- .../timetablewidget/TimetableWidgetFactory.kt | 5 +- .../io/github/wulkanowy/utils/FlowUtils.kt | 93 ++++++ app/src/main/res/layout/fragment_license.xml | 4 +- .../io/github/wulkanowy/MainCoroutineRule.kt | 26 ++ .../wulkanowy/TestDispatchersProvider.kt | 11 + .../message/MessageRepositoryTest.kt | 30 +- .../MobileDeviceRepositoryTest.kt | 6 +- .../semester/SemesterRepositoryTest.kt | 3 +- .../modules/grade/GradeAverageProviderTest.kt | 275 ++++++++++-------- .../login/form/LoginFormPresenterTest.kt | 32 +- .../LoginStudentSelectPresenterTest.kt | 19 +- .../ui/modules/splash/SplashPresenterTest.kt | 7 +- 138 files changed, 2351 insertions(+), 1892 deletions(-) create mode 100644 app/src/androidTest/java/io/github/wulkanowy/data/TestDispatchersProvider.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/Resource.kt create mode 100644 app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt create mode 100644 app/src/test/java/io/github/wulkanowy/MainCoroutineRule.kt create mode 100644 app/src/test/java/io/github/wulkanowy/TestDispatchersProvider.kt diff --git a/app/build.gradle b/app/build.gradle index df9535afe..d5031b315 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -92,6 +92,7 @@ android { kotlinOptions { jvmTarget = "1.8" + freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" } packagingOptions { @@ -116,7 +117,7 @@ ext { room = "2.2.5" dagger = "2.28.3" chucker = "3.2.0" - mockk = "1.9.2" + mockk = "1.10.0" } configurations.all { @@ -129,6 +130,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.3.7' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7' implementation "androidx.core:core-ktx:1.3.0" implementation "androidx.activity:activity-ktx:1.1.0" @@ -156,7 +158,6 @@ dependencies { implementation 'com.github.PaulinaSadowska:RxWorkManagerObservers:1.0.0' implementation "androidx.room:room-runtime:$room" - implementation "androidx.room:room-rxjava2:$room" implementation "androidx.room:room-ktx:$room" kapt "androidx.room:room-compiler:$room" @@ -199,6 +200,7 @@ dependencies { testImplementation "junit:junit:4.13" testImplementation "io.mockk:mockk:$mockk" testImplementation "org.threeten:threetenbp:1.4.4" + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.7' androidTestImplementation "androidx.test:core:1.2.0" androidTestImplementation "androidx.test:runner:1.2.0" diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/TestDispatchersProvider.kt b/app/src/androidTest/java/io/github/wulkanowy/data/TestDispatchersProvider.kt new file mode 100644 index 000000000..8c4354d9a --- /dev/null +++ b/app/src/androidTest/java/io/github/wulkanowy/data/TestDispatchersProvider.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.data + +import io.github.wulkanowy.utils.DispatchersProvider +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers + +class TestDispatchersProvider : DispatchersProvider() { + + override val backgroundThread: CoroutineDispatcher + get() = Dispatchers.Unconfined +} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/TestEntityCreator.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/TestEntityCreator.kt index bbbfd83dd..f7aa51e49 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/TestEntityCreator.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/TestEntityCreator.kt @@ -1,6 +1,8 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDateTime fun getStudent(): Student { @@ -27,3 +29,16 @@ fun getStudent(): Student { isParent = false ) } + +fun getSemester() = Semester( + semesterId = 1, + studentId = 1, + classId = 1, + diaryId = 2, + diaryName = "", + end = now(), + schoolYear = 2019, + semesterName = 1, + start = now(), + unitId = 1 +) diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt index f9326b2d5..4080b8313 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt @@ -6,6 +6,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Semester +import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Before @@ -53,7 +54,7 @@ class AttendanceLocalTest { runBlocking { attendanceLocal.saveAttendance(list) } val semester = Semester(1, 2, "", 1, 3, 2019, now(), now(), 1, 1) - val attendance = runBlocking { attendanceLocal.getAttendance(semester, of(2018, 9, 10), of(2018, 9, 14)) } + val attendance = runBlocking { attendanceLocal.getAttendance(semester, of(2018, 9, 10), of(2018, 9, 14)).first() } assertEquals(2, attendance.size) assertEquals(attendance[0].date, of(2018, 9, 10)) assertEquals(attendance[1].date, of(2018, 9, 14)) diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocalTest.kt index d8aac23da..f8ff92138 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocalTest.kt @@ -6,6 +6,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.entities.CompletedLesson import io.github.wulkanowy.data.db.entities.Semester +import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Before @@ -46,7 +47,7 @@ class CompletedLessonsLocalTest { runBlocking { completedLessonsLocal.saveCompletedLessons(list) } val semester = Semester(1, 2, "", 1, 3, 2019, now(), now(), 1, 1) - val completed = runBlocking { completedLessonsLocal.getCompletedLessons(semester, of(2018, 9, 10), of(2018, 9, 14)) } + val completed = runBlocking { completedLessonsLocal.getCompletedLessons(semester, of(2018, 9, 10), of(2018, 9, 14)).first() } assertEquals(2, completed.size) assertEquals(completed[0].date, of(2018, 9, 10)) assertEquals(completed[1].date, of(2018, 9, 14)) diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/exam/ExamLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/exam/ExamLocalTest.kt index f3b179a51..e595d77c6 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/exam/ExamLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/exam/ExamLocalTest.kt @@ -6,6 +6,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Semester +import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Before @@ -43,7 +44,7 @@ class ExamLocalTest { runBlocking { examLocal.saveExams(list) } val semester = Semester(1, 2, "", 1, 3, 2019, now(), now(), 1, 1) - val exams = runBlocking { examLocal.getExams(semester, of(2018, 9, 10), of(2018, 9, 14)) } + val exams = runBlocking { examLocal.getExams(semester, of(2018, 9, 10), of(2018, 9, 14)).first() } assertEquals(2, exams.size) assertEquals(exams[0].date, of(2018, 9, 10)) assertEquals(exams[1].date, of(2018, 9, 14)) diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeLocalTest.kt index 82129d868..6a01b09cb 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeLocalTest.kt @@ -5,6 +5,7 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.entities.Semester +import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Before @@ -45,7 +46,7 @@ class GradeLocalTest { val semester = Semester(1, 2, "", 2019, 2, 1, now(), now(), 1, 1) - val grades = runBlocking { gradeLocal.getGradesDetails(semester) } + val grades = runBlocking { gradeLocal.getGradesDetails(semester).first() } assertEquals(2, grades.size) assertEquals(grades[0].date, LocalDate.of(2019, 2, 27)) diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeRepositoryTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeRepositoryTest.kt index 5487fd4ce..5a8845307 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeRepositoryTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeRepositoryTest.kt @@ -5,15 +5,18 @@ import androidx.room.Room import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SdkSuppress +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.AppDatabase +import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.sdk.pojo.Grade import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.every import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Before @@ -29,15 +32,12 @@ import kotlin.test.assertTrue @RunWith(AndroidJUnit4::class) class GradeRepositoryTest { - @MockK - private lateinit var mockSdk: Sdk - @MockK private lateinit var semesterMock: Semester - @MockK private lateinit var studentMock: Student + @MockK private lateinit var gradeRemote: GradeRemote private lateinit var gradeLocal: GradeLocal @@ -49,14 +49,12 @@ class GradeRepositoryTest { MockKAnnotations.init(this) testDb = Room.inMemoryDatabaseBuilder(getApplicationContext(), AppDatabase::class.java).build() gradeLocal = GradeLocal(testDb.gradeDao, testDb.gradeSummaryDao) - gradeRemote = GradeRemote(mockSdk) + studentMock = getStudentMock() - every { studentMock.registrationDate } returns LocalDateTime.of(2019, 2, 27, 12, 0) every { semesterMock.studentId } returns 1 every { semesterMock.diaryId } returns 1 every { semesterMock.schoolYear } returns 2019 every { semesterMock.semesterId } returns 1 - every { mockSdk.switchDiary(any(), any()) } returns mockSdk } @After @@ -66,16 +64,17 @@ class GradeRepositoryTest { @Test fun markOlderThanRegisterDateAsRead() { - coEvery { mockSdk.getGrades(1) } returns (listOf( - createGradeApi(5, 4.0, of(2019, 2, 25), "Ocena pojawiła się"), - createGradeApi(5, 4.0, of(2019, 2, 26), "przed zalogowanie w aplikacji"), - createGradeApi(5, 4.0, of(2019, 2, 27), "Ocena z dnia logowania"), - createGradeApi(5, 4.0, of(2019, 2, 28), "Ocena jeszcze nowsza") + coEvery { gradeRemote.getGrades(studentMock, semesterMock) } returns (listOf( + createGradeLocal(5, 4.0, of(2019, 2, 25), "Ocena pojawiła się"), + createGradeLocal(5, 4.0, of(2019, 2, 26), "przed zalogowanie w aplikacji"), + createGradeLocal(5, 4.0, of(2019, 2, 27), "Ocena z dnia logowania"), + createGradeLocal(5, 4.0, of(2019, 2, 28), "Ocena jeszcze nowsza") ) to emptyList()) val grades = runBlocking { - GradeRepository(gradeLocal, gradeRemote).getGrades(studentMock, semesterMock, true) - .first.sortedByDescending { it.date } + GradeRepository(gradeLocal, gradeRemote) + .getGrades(studentMock, semesterMock, true) + .filter { it.status == Status.SUCCESS }.first().data!!.first.sortedByDescending { it.date } } assertFalse { grades[0].isRead } @@ -93,16 +92,17 @@ class GradeRepositoryTest { ) runBlocking { gradeLocal.saveGrades(list) } - coEvery { mockSdk.getGrades(1) } returns (listOf( - createGradeApi(5, 2.0, of(2019, 2, 25), "Ocena ma datę, jest inna, ale nie zostanie powiadomiona"), - createGradeApi(4, 3.0, of(2019, 2, 26), "starszą niż ostatnia lokalnie"), - createGradeApi(3, 4.0, of(2019, 2, 27), "Ta jest z tego samego dnia co ostatnia lokalnie"), - createGradeApi(2, 5.0, of(2019, 2, 28), "Ta jest już w ogóle nowa") + coEvery { gradeRemote.getGrades(studentMock, semesterMock) } returns (listOf( + createGradeLocal(5, 2.0, of(2019, 2, 25), "Ocena ma datę, jest inna, ale nie zostanie powiadomiona"), + createGradeLocal(4, 3.0, of(2019, 2, 26), "starszą niż ostatnia lokalnie"), + createGradeLocal(3, 4.0, of(2019, 2, 27), "Ta jest z tego samego dnia co ostatnia lokalnie"), + createGradeLocal(2, 5.0, of(2019, 2, 28), "Ta jest już w ogóle nowa") ) to emptyList()) val grades = runBlocking { - GradeRepository(gradeLocal, gradeRemote).getGrades(studentMock, semesterMock, true) - .first.sortedByDescending { it.date } + GradeRepository(gradeLocal, gradeRemote) + .getGrades(studentMock, semesterMock, true) + .filter { it.status == Status.SUCCESS }.first().data!!.first.sortedByDescending { it.date } } assertFalse { grades[0].isRead } @@ -120,13 +120,15 @@ class GradeRepositoryTest { ) runBlocking { gradeLocal.saveGrades(list) } - coEvery { mockSdk.getGrades(1) } returns (listOf( - createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") + coEvery { gradeRemote.getGrades(studentMock, semesterMock) } returns (listOf( + createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), + createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") ) to emptyList()) val grades = runBlocking { - GradeRepository(gradeLocal, gradeRemote).getGrades(studentMock, semesterMock, true) + GradeRepository(gradeLocal, gradeRemote) + .getGrades(studentMock, semesterMock, true) + .filter { it.status == Status.SUCCESS }.first().data!! } assertEquals(2, grades.first.size) @@ -140,14 +142,16 @@ class GradeRepositoryTest { ) runBlocking { gradeLocal.saveGrades(list) } - coEvery { mockSdk.getGrades(1) } returns (listOf( - createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") + coEvery { gradeRemote.getGrades(studentMock, semesterMock) } returns (listOf( + createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), + createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), + createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") ) to emptyList()) val grades = runBlocking { - GradeRepository(gradeLocal, gradeRemote).getGrades(studentMock, semesterMock, true) + GradeRepository(gradeLocal, gradeRemote) + .getGrades(studentMock, semesterMock, true) + .filter { it.status == Status.SUCCESS }.first().data!! } assertEquals(3, grades.first.size) @@ -157,14 +161,16 @@ class GradeRepositoryTest { fun emptyLocal() { runBlocking { gradeLocal.saveGrades(listOf()) } - coEvery { mockSdk.getGrades(1) } returns (listOf( - createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") + coEvery { gradeRemote.getGrades(studentMock, semesterMock) } returns (listOf( + createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), + createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), + createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") ) to emptyList()) val grades = runBlocking { - GradeRepository(gradeLocal, gradeRemote).getGrades(studentMock, semesterMock, true) + GradeRepository(gradeLocal, gradeRemote) + .getGrades(studentMock, semesterMock, true) + .filter { it.status == Status.SUCCESS }.first().data!! } assertEquals(3, grades.first.size) @@ -178,12 +184,37 @@ class GradeRepositoryTest { ) runBlocking { gradeLocal.saveGrades(list) } - coEvery { mockSdk.getGrades(1) } returns (emptyList() to emptyList()) + coEvery { gradeRemote.getGrades(studentMock, semesterMock) } returns (emptyList() to emptyList()) val grades = runBlocking { - GradeRepository(gradeLocal, gradeRemote).getGrades(studentMock, semesterMock, true) + GradeRepository(gradeLocal, gradeRemote) + .getGrades(studentMock, semesterMock, true) + .filter { it.status == Status.SUCCESS }.first().data!! } assertEquals(0, grades.first.size) } + + private fun getStudentMock() = Student( + scrapperBaseUrl = "http://fakelog.cf", + email = "jan@fakelog.cf", + certificateKey = "", + classId = 0, + className = "", + isCurrent = false, + isParent = false, + loginMode = Sdk.Mode.SCRAPPER.name, + loginType = "STANDARD", + mobileBaseUrl = "", + password = "", + privateKey = "", + registrationDate = LocalDateTime.of(2019, 2, 27, 12, 0), + schoolName = "", + schoolShortName = "test", + schoolSymbol = "", + studentId = 0, + studentName = "", + symbol = "", + userLoginId = 0 + ) } diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt index deda67ba3..ff6541589 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt @@ -7,6 +7,7 @@ import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.data.db.entities.Semester +import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Before @@ -42,7 +43,7 @@ class GradeStatisticsLocalTest { ) runBlocking { gradeStatisticsLocal.saveGradesStatistics(list) } - val stats = runBlocking { gradeStatisticsLocal.getGradesStatistics(getSemester(), false, "Matematyka") } + val stats = runBlocking { gradeStatisticsLocal.getGradesStatistics(getSemester(), false).first() } assertEquals(1, stats.size) assertEquals(stats[0].subject, "Matematyka") } @@ -56,11 +57,12 @@ class GradeStatisticsLocalTest { ) runBlocking { gradeStatisticsLocal.saveGradesStatistics(list) } - val stats = runBlocking { gradeStatisticsLocal.getGradesStatistics(getSemester(), false, "Wszystkie") } - assertEquals(3, stats.size) - assertEquals(stats[0].subject, "Wszystkie") - assertEquals(stats[1].subject, "Matematyka") - assertEquals(stats[2].subject, "Chemia") + val stats = runBlocking { gradeStatisticsLocal.getGradesStatistics(getSemester(), false).first() } + assertEquals(2, stats.size) +// assertEquals(3, stats.size) +// assertEquals(stats[0].subject, "Wszystkie") // now in main repo + assertEquals(stats[0].subject, "Matematyka") + assertEquals(stats[1].subject, "Chemia") } @Test @@ -72,7 +74,7 @@ class GradeStatisticsLocalTest { ) runBlocking { gradeStatisticsLocal.saveGradesPointsStatistics(list) } - val stats = runBlocking { gradeStatisticsLocal.getGradesPointsStatistics(getSemester(), "Matematyka") } + val stats = runBlocking { gradeStatisticsLocal.getGradesPointsStatistics(getSemester()).first() } with(stats[0]) { assertEquals(subject, "Matematyka") assertEquals(others, 5.0) @@ -84,7 +86,7 @@ class GradeStatisticsLocalTest { fun saveAndRead_subjectEmpty() { runBlocking { gradeStatisticsLocal.saveGradesPointsStatistics(listOf()) } - val stats = runBlocking { gradeStatisticsLocal.getGradesPointsStatistics(getSemester(), "Matematyka") } + val stats = runBlocking { gradeStatisticsLocal.getGradesPointsStatistics(getSemester()).first() } assertEquals(emptyList(), stats) } @@ -92,7 +94,7 @@ class GradeStatisticsLocalTest { fun saveAndRead_allEmpty() { runBlocking { gradeStatisticsLocal.saveGradesPointsStatistics(listOf()) } - val stats = runBlocking { gradeStatisticsLocal.getGradesPointsStatistics(getSemester(), "Wszystkie") } + val stats = runBlocking { gradeStatisticsLocal.getGradesPointsStatistics(getSemester()).first() } assertEquals(emptyList(), stats) } diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt index f37d7934b..dfd973944 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt @@ -6,6 +6,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.Student +import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Before @@ -41,7 +42,7 @@ class LuckyNumberLocalTest { runBlocking { luckyNumberLocal.saveLuckyNumber(number) } val student = Student("", "", "", "", "", "", false, "", "", "", 1, 1, "", "", "", "", "", 1, false, now()) - val luckyNumber = runBlocking { luckyNumberLocal.getLuckyNumber(student, LocalDate.of(2019, 1, 20)) } + val luckyNumber = runBlocking { luckyNumberLocal.getLuckyNumber(student, LocalDate.of(2019, 1, 20)).first() } assertEquals(1, luckyNumber?.studentId) assertEquals(LocalDate.of(2019, 1, 20), luckyNumber?.date) diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/student/StudentLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/student/StudentLocalTest.kt index 02a13344e..d68f15a80 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/student/StudentLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/student/StudentLocalTest.kt @@ -4,6 +4,7 @@ import android.content.Context import androidx.room.Room import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.github.wulkanowy.data.TestDispatchersProvider import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.repositories.getStudent import kotlinx.coroutines.runBlocking @@ -27,7 +28,7 @@ class StudentLocalTest { val context = ApplicationProvider.getApplicationContext() testDb = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java) .build() - studentLocal = StudentLocal(testDb.studentDao, context) + studentLocal = StudentLocal(testDb.studentDao, TestDispatchersProvider(), context) } @After diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocalTest.kt index fa353a332..77d7188c1 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocalTest.kt @@ -5,6 +5,7 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.entities.Semester +import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Before @@ -48,7 +49,7 @@ class TimetableLocalTest { semester = semester, startDate = LocalDate.of(2018, 9, 10), endDate = LocalDate.of(2018, 9, 14) - ) + ).first() } assertEquals(2, exams.size) diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt index a91651db1..fa62849ac 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt @@ -5,17 +5,16 @@ import androidx.room.Room import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SdkSuppress +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.repositories.getSemester import io.github.wulkanowy.data.repositories.getStudent -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper import io.mockk.MockKAnnotations import io.mockk.coEvery -import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.mockk +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Before @@ -29,45 +28,25 @@ import kotlin.test.assertEquals @RunWith(AndroidJUnit4::class) class TimetableRepositoryTest { - @MockK - private lateinit var mockSdk: Sdk - - @MockK - private lateinit var studentMock: Student - - private val student = getStudent() - - @MockK - private lateinit var semesterMock: Semester - - @MockK + @MockK(relaxed = true) private lateinit var timetableNotificationSchedulerHelper: TimetableNotificationSchedulerHelper + @MockK private lateinit var timetableRemote: TimetableRemote private lateinit var timetableLocal: TimetableLocal private lateinit var testDb: AppDatabase + private val student = getStudent() + + private val semester = getSemester() + @Before fun initApi() { MockKAnnotations.init(this) testDb = Room.inMemoryDatabaseBuilder(getApplicationContext(), AppDatabase::class.java).build() timetableLocal = TimetableLocal(testDb.timetableDao) - timetableRemote = TimetableRemote(mockSdk) - - every { timetableNotificationSchedulerHelper.scheduleNotifications(any(), any()) } returns mockk() - every { timetableNotificationSchedulerHelper.cancelScheduled(any(), any()) } returns mockk() - - every { studentMock.studentId } returns 1 - every { studentMock.studentName } returns "Jan Kowalski" - - every { semesterMock.studentId } returns 1 - every { semesterMock.diaryId } returns 2 - every { semesterMock.schoolYear } returns 2019 - every { semesterMock.semesterId } returns 1 - - every { mockSdk.switchDiary(any(), any()) } returns mockSdk } @After @@ -86,21 +65,21 @@ class TimetableRepositoryTest { )) } - coEvery { mockSdk.getTimetable(any(), any()) } returns listOf( - createTimetableRemote(of(2019, 3, 5, 8, 0), 1, "", "Przyroda"), - createTimetableRemote(of(2019, 3, 5, 8, 50), 2, "", "Religia"), - createTimetableRemote(of(2019, 3, 5, 9, 40), 3, "", "W-F"), - createTimetableRemote(of(2019, 3, 5, 10, 30), 4, "", "W-F") + coEvery { timetableRemote.getTimetable(student, semester, any(), any()) } returns listOf( + createTimetableLocal(of(2019, 3, 5, 8, 0), 1, "", "Przyroda"), + createTimetableLocal(of(2019, 3, 5, 8, 50), 2, "", "Religia"), + createTimetableLocal(of(2019, 3, 5, 9, 40), 3, "", "W-F"), + createTimetableLocal(of(2019, 3, 5, 10, 30), 4, "", "W-F") ) val lessons = runBlocking { TimetableRepository(timetableLocal, timetableRemote, timetableNotificationSchedulerHelper).getTimetable( student = student, - semester = semesterMock, + semester = semester, start = LocalDate.of(2019, 3, 5), end = LocalDate.of(2019, 3, 5), forceRefresh = true - ) + ).filter { it.status == Status.SUCCESS }.first().data.orEmpty() } assertEquals(4, lessons.size) @@ -129,31 +108,31 @@ class TimetableRepositoryTest { ) runBlocking { timetableLocal.saveTimetable(list) } - coEvery { mockSdk.getTimetable(any(), any()) } returns listOf( - createTimetableRemote(of(2019, 12, 23, 8, 0), 1, "123", "Matematyka", "Paweł Poniedziałkowski", false), - createTimetableRemote(of(2019, 12, 23, 8, 50), 2, "124", "Matematyka", "Jakub Wtorkowski", true), - createTimetableRemote(of(2019, 12, 23, 9, 40), 3, "125", "Język polski", "Joanna Poniedziałkowska", false), - createTimetableRemote(of(2019, 12, 23, 10, 40), 4, "126", "Język polski", "Joanna Wtorkowska", true), + coEvery { timetableRemote.getTimetable(student, semester, any(), any()) } returns listOf( + createTimetableLocal(of(2019, 12, 23, 8, 0), 1, "123", "Matematyka", "Paweł Poniedziałkowski", false), + createTimetableLocal(of(2019, 12, 23, 8, 50), 2, "124", "Matematyka", "Jakub Wtorkowski", true), + createTimetableLocal(of(2019, 12, 23, 9, 40), 3, "125", "Język polski", "Joanna Poniedziałkowska", false), + createTimetableLocal(of(2019, 12, 23, 10, 40), 4, "126", "Język polski", "Joanna Wtorkowska", true), - createTimetableRemote(of(2019, 12, 24, 8, 0), 1, "123", "Język polski", "", false), - createTimetableRemote(of(2019, 12, 24, 8, 50), 2, "124", "Język polski", "", true), - createTimetableRemote(of(2019, 12, 24, 9, 40), 3, "125", "Język polski", "", false), - createTimetableRemote(of(2019, 12, 24, 10, 40), 4, "126", "Język polski", "", true), + createTimetableLocal(of(2019, 12, 24, 8, 0), 1, "123", "Język polski", "", false), + createTimetableLocal(of(2019, 12, 24, 8, 50), 2, "124", "Język polski", "", true), + createTimetableLocal(of(2019, 12, 24, 9, 40), 3, "125", "Język polski", "", false), + createTimetableLocal(of(2019, 12, 24, 10, 40), 4, "126", "Język polski", "", true), - createTimetableRemote(of(2019, 12, 25, 8, 0), 1, "123", "Matematyka", "Paweł Środowski", false), - createTimetableRemote(of(2019, 12, 25, 8, 50), 2, "124", "Matematyka", "Paweł Czwartkowski", true), - createTimetableRemote(of(2019, 12, 25, 9, 40), 3, "125", "Matematyka", "Paweł Środowski", false), - createTimetableRemote(of(2019, 12, 25, 10, 40), 4, "126", "Matematyka", "Paweł Czwartkowski", true) + createTimetableLocal(of(2019, 12, 25, 8, 0), 1, "123", "Matematyka", "Paweł Środowski", false), + createTimetableLocal(of(2019, 12, 25, 8, 50), 2, "124", "Matematyka", "Paweł Czwartkowski", true), + createTimetableLocal(of(2019, 12, 25, 9, 40), 3, "125", "Matematyka", "Paweł Środowski", false), + createTimetableLocal(of(2019, 12, 25, 10, 40), 4, "126", "Matematyka", "Paweł Czwartkowski", true) ) val lessons = runBlocking { TimetableRepository(timetableLocal, timetableRemote, timetableNotificationSchedulerHelper).getTimetable( student = student, - semester = semesterMock, + semester = semester, start = LocalDate.of(2019, 12, 23), end = LocalDate.of(2019, 12, 25), forceRefresh = true - ) + ).filter { it.status == Status.SUCCESS }.first().data.orEmpty() } assertEquals(12, lessons.size) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 802cf1ad2..4ec2f7816 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,8 +18,7 @@ android:supportsRtl="false" android:theme="@style/WulkanowyTheme" android:usesCleartextTraffic="true" - tools:ignore="GoogleAppIndexingWarning,UnusedAttribute" - tools:replace="android:supportsRtl,android:allowBackup"> + tools:ignore="GoogleAppIndexingWarning,UnusedAttribute"> (val status: Status, val data: T?, val error: Throwable?) { + companion object { + fun success(data: T?): Resource { + return Resource(Status.SUCCESS, data, null) + } + + fun error(error: Throwable?, data: T? = null): Resource { + return Resource(Status.ERROR, data, error) + } + + fun loading(data: T? = null): Resource { + return Resource(Status.LOADING, data, null) + } + } +} + +enum class Status { + LOADING, + SUCCESS, + ERROR +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt index 49527a553..960795479 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.Attendance +import kotlinx.coroutines.flow.Flow import org.threeten.bp.LocalDate import javax.inject.Singleton @@ -11,5 +12,5 @@ import javax.inject.Singleton interface AttendanceDao : BaseDao { @Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") - suspend fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List + fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceSummaryDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceSummaryDao.kt index 1ba37c959..4218855ca 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceSummaryDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceSummaryDao.kt @@ -3,10 +3,11 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.AttendanceSummary +import kotlinx.coroutines.flow.Flow @Dao interface AttendanceSummaryDao : BaseDao { @Query("SELECT * FROM AttendanceSummary WHERE diary_id = :diaryId AND student_id = :studentId AND subject_id = :subjectId") - suspend fun loadAll(diaryId: Int, studentId: Int, subjectId: Int): List + fun loadAll(diaryId: Int, studentId: Int, subjectId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/CompletedLessonsDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/CompletedLessonsDao.kt index 6406d097d..4a827b4fd 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/CompletedLessonsDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/CompletedLessonsDao.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.CompletedLesson +import kotlinx.coroutines.flow.Flow import org.threeten.bp.LocalDate import javax.inject.Singleton @@ -11,5 +12,5 @@ import javax.inject.Singleton interface CompletedLessonsDao : BaseDao { @Query("SELECT * FROM CompletedLesson WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") - suspend fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List + fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/ExamDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/ExamDao.kt index e492f7b84..e3119d9b5 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/ExamDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/ExamDao.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.Exam +import kotlinx.coroutines.flow.Flow import org.threeten.bp.LocalDate import javax.inject.Singleton @@ -11,5 +12,5 @@ import javax.inject.Singleton interface ExamDao : BaseDao { @Query("SELECT * FROM Exams WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") - suspend fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List + fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt index df0276203..12e70bde6 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.Grade +import kotlinx.coroutines.flow.Flow import javax.inject.Singleton @Singleton @@ -10,5 +11,5 @@ import javax.inject.Singleton interface GradeDao : BaseDao { @Query("SELECT * FROM Grades WHERE semester_id = :semesterId AND student_id = :studentId") - suspend fun loadAll(semesterId: Int, studentId: Int): List + fun loadAll(semesterId: Int, studentId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePointsStatisticsDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePointsStatisticsDao.kt index b1e644bbd..e8074f003 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePointsStatisticsDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePointsStatisticsDao.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.GradePointsStatistics +import kotlinx.coroutines.flow.Flow import javax.inject.Singleton @Singleton @@ -10,8 +11,8 @@ import javax.inject.Singleton interface GradePointsStatisticsDao : BaseDao { @Query("SELECT * FROM GradesPointsStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND subject = :subjectName") - suspend fun loadSubject(semesterId: Int, studentId: Int, subjectName: String): List + fun loadSubject(semesterId: Int, studentId: Int, subjectName: String): Flow> @Query("SELECT * FROM GradesPointsStatistics WHERE student_id = :studentId AND semester_id = :semesterId") - suspend fun loadAll(semesterId: Int, studentId: Int): List + fun loadAll(semesterId: Int, studentId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeStatisticsDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeStatisticsDao.kt index 786da0d9e..b462ad5db 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeStatisticsDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeStatisticsDao.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.GradeStatistics +import kotlinx.coroutines.flow.Flow import javax.inject.Singleton @Singleton @@ -10,8 +11,8 @@ import javax.inject.Singleton interface GradeStatisticsDao : BaseDao { @Query("SELECT * FROM GradesStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND subject = :subjectName AND is_semester = :isSemester") - suspend fun loadSubject(semesterId: Int, studentId: Int, subjectName: String, isSemester: Boolean): List + fun loadSubject(semesterId: Int, studentId: Int, subjectName: String, isSemester: Boolean): Flow> @Query("SELECT * FROM GradesStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND is_semester = :isSemester") - suspend fun loadAll(semesterId: Int, studentId: Int, isSemester: Boolean): List + fun loadAll(semesterId: Int, studentId: Int, isSemester: Boolean): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt index 02d4e9229..fc9ad66ed 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.GradeSummary +import kotlinx.coroutines.flow.Flow import javax.inject.Singleton @Singleton @@ -10,5 +11,5 @@ import javax.inject.Singleton interface GradeSummaryDao : BaseDao { @Query("SELECT * FROM GradesSummary WHERE student_id = :studentId AND semester_id = :semesterId") - suspend fun loadAll(semesterId: Int, studentId: Int): List + fun loadAll(semesterId: Int, studentId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/HomeworkDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/HomeworkDao.kt index 9bbf80ace..5d417b046 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/HomeworkDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/HomeworkDao.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.Homework +import kotlinx.coroutines.flow.Flow import org.threeten.bp.LocalDate import javax.inject.Singleton @@ -11,5 +12,5 @@ import javax.inject.Singleton interface HomeworkDao : BaseDao { @Query("SELECT * FROM Homework WHERE semester_id = :semesterId AND student_id = :studentId AND date >= :from AND date <= :end") - suspend fun loadAll(semesterId: Int, studentId: Int, from: LocalDate, end: LocalDate): List + fun loadAll(semesterId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt index b4ead2454..55a005fff 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.LuckyNumber +import kotlinx.coroutines.flow.Flow import org.threeten.bp.LocalDate import javax.inject.Singleton @@ -11,5 +12,5 @@ import javax.inject.Singleton interface LuckyNumberDao : BaseDao { @Query("SELECT * FROM LuckyNumbers WHERE student_id = :studentId AND date = :date") - suspend fun load(studentId: Int, date: LocalDate): LuckyNumber + fun load(studentId: Int, date: LocalDate): Flow } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt index 2757978ae..0c63624f8 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt @@ -5,17 +5,18 @@ import androidx.room.Query import androidx.room.Transaction import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageWithAttachment +import kotlinx.coroutines.flow.Flow @Dao interface MessagesDao : BaseDao { @Transaction @Query("SELECT * FROM Messages WHERE student_id = :studentId AND message_id = :messageId") - suspend fun loadMessageWithAttachment(studentId: Int, messageId: Int): MessageWithAttachment + fun loadMessageWithAttachment(studentId: Int, messageId: Int): Flow @Query("SELECT * FROM Messages WHERE student_id = :studentId AND folder_id = :folder AND removed = 0 ORDER BY date DESC") - suspend fun loadAll(studentId: Int, folder: Int): List + fun loadAll(studentId: Int, folder: Int): Flow> @Query("SELECT * FROM Messages WHERE student_id = :studentId AND removed = 1 ORDER BY date DESC") - suspend fun loadDeleted(studentId: Int): List + fun loadDeleted(studentId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt index b07aab284..8baba2c30 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt @@ -3,10 +3,11 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.MobileDevice +import kotlinx.coroutines.flow.Flow @Dao interface MobileDeviceDao : BaseDao { @Query("SELECT * FROM MobileDevices WHERE student_id = :studentId ORDER BY date DESC") - suspend fun loadAll(studentId: Int): List + fun loadAll(studentId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/NoteDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/NoteDao.kt index 81c324f65..e89a4135a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/NoteDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/NoteDao.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.Note +import kotlinx.coroutines.flow.Flow import javax.inject.Singleton @Singleton @@ -10,5 +11,5 @@ import javax.inject.Singleton interface NoteDao : BaseDao { @Query("SELECT * FROM Notes WHERE student_id = :studentId") - suspend fun loadAll(studentId: Int): List + fun loadAll(studentId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolDao.kt index 37cb6c500..f39791f63 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolDao.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.School +import kotlinx.coroutines.flow.Flow import javax.inject.Singleton @Singleton @@ -10,5 +11,5 @@ import javax.inject.Singleton interface SchoolDao : BaseDao { @Query("SELECT * FROM School WHERE student_id = :studentId AND class_id = :classId") - suspend fun load(studentId: Int, classId: Int): School? + fun load(studentId: Int, classId: Int): Flow } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/SubjectDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/SubjectDao.kt index 92477552c..4cd742b56 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/SubjectDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/SubjectDao.kt @@ -3,10 +3,11 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.Subject +import kotlinx.coroutines.flow.Flow @Dao interface SubjectDao : BaseDao { @Query("SELECT * FROM Subjects WHERE diary_id = :diaryId AND student_id = :studentId") - suspend fun loadAll(diaryId: Int, studentId: Int): List + fun loadAll(diaryId: Int, studentId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/TeacherDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/TeacherDao.kt index 0b0e659b4..6adac220d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/TeacherDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/TeacherDao.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.Teacher +import kotlinx.coroutines.flow.Flow import javax.inject.Singleton @Singleton @@ -10,5 +11,5 @@ import javax.inject.Singleton interface TeacherDao : BaseDao { @Query("SELECT * FROM Teachers WHERE student_id = :studentId AND class_id = :classId") - suspend fun loadAll(studentId: Int, classId: Int): List + fun loadAll(studentId: Int, classId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt index 59200b80b..a099dd80d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.Timetable +import kotlinx.coroutines.flow.Flow import org.threeten.bp.LocalDate import javax.inject.Singleton @@ -11,5 +12,5 @@ import javax.inject.Singleton interface TimetableDao : BaseDao { @Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") - suspend fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List + fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt index 3fcd7cb57..d19565579 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt @@ -14,12 +14,10 @@ class AppCreatorRepository @Inject constructor( private val dispatchers: DispatchersProvider ) { - suspend fun getAppCreators(): List { - return withContext(dispatchers.backgroundThread) { - Gson().fromJson( - assets.open("contributors.json").bufferedReader().use { it.readText() }, - Array::class.java - ).toList() - } + suspend fun getAppCreators() = withContext(dispatchers.backgroundThread) { + Gson().fromJson( + assets.open("contributors.json").bufferedReader().use { it.readText() }, + Array::class.java + ).toList() } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocal.kt index b232033d1..1e56d872e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocal.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories.attendance import io.github.wulkanowy.data.db.dao.AttendanceDao import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Semester +import kotlinx.coroutines.flow.Flow import org.threeten.bp.LocalDate import javax.inject.Inject import javax.inject.Singleton @@ -18,7 +19,7 @@ class AttendanceLocal @Inject constructor(private val attendanceDb: AttendanceDa attendanceDb.deleteAll(attendance) } - suspend fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate): List { + fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate): Flow> { return attendanceDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRepository.kt index 0fa0090e7..cf4edb6a0 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRepository.kt @@ -4,6 +4,7 @@ import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.uniqueSubtract import org.threeten.bp.LocalDate @@ -16,19 +17,18 @@ class AttendanceRepository @Inject constructor( private val remote: AttendanceRemote ) { - suspend fun getAttendance(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean): List { - return local.getAttendance(semester, start.monday, end.sunday).filter { !forceRefresh }.ifEmpty { - val new = remote.getAttendance(student, semester, start.monday, end.sunday) - val old = local.getAttendance(semester, start.monday, end.sunday) + fun getAttendance(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { local.getAttendance(semester, start.monday, end.sunday) }, + fetch = { remote.getAttendance(student, semester, start.monday, end.sunday) }, + saveFetchResult = { old, new -> + local.deleteAttendance(old uniqueSubtract new) + local.saveAttendance(new uniqueSubtract old) + }, + filterResult = { it.filter { item -> item.date in start..end } } + ) - local.deleteAttendance(old.uniqueSubtract(new)) - local.saveAttendance(new.uniqueSubtract(old)) - - local.getAttendance(semester, start.monday, end.sunday) - }.filter { it.date in start..end } - } - - suspend fun excuseForAbsence(student: Student, semester: Semester, attendanceList: List, reason: String? = null): Boolean { - return remote.excuseAbsence(student, semester, attendanceList, reason) + suspend fun excuseForAbsence(student: Student, semester: Semester, attendanceList: List, reason: String? = null) { + remote.excuseAbsence(student, semester, attendanceList, reason) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryLocal.kt index f949f0163..703bc9474 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryLocal.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories.attendancesummary import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.Semester +import kotlinx.coroutines.flow.Flow import javax.inject.Inject import javax.inject.Singleton @@ -17,7 +18,7 @@ class AttendanceSummaryLocal @Inject constructor(private val attendanceDb: Atten attendanceDb.deleteAll(attendance) } - suspend fun getAttendanceSummary(semester: Semester, subjectId: Int): List { + fun getAttendanceSummary(semester: Semester, subjectId: Int): Flow> { return attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryRepository.kt index 7ef16fb0f..5dbe1ab05 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryRepository.kt @@ -1,8 +1,8 @@ package io.github.wulkanowy.data.repositories.attendancesummary -import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.uniqueSubtract import javax.inject.Inject import javax.inject.Singleton @@ -13,15 +13,13 @@ class AttendanceSummaryRepository @Inject constructor( private val remote: AttendanceSummaryRemote ) { - suspend fun getAttendanceSummary(student: Student, semester: Semester, subjectId: Int, forceRefresh: Boolean = false): List { - return local.getAttendanceSummary(semester, subjectId).filter { !forceRefresh }.ifEmpty { - val new = remote.getAttendanceSummary(student, semester, subjectId) - - val old = local.getAttendanceSummary(semester, subjectId) - local.deleteAttendanceSummary(old.uniqueSubtract(new)) - local.saveAttendanceSummary(new.uniqueSubtract(old)) - - return local.getAttendanceSummary(semester, subjectId) + fun getAttendanceSummary(student: Student, semester: Semester, subjectId: Int, forceRefresh: Boolean) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { local.getAttendanceSummary(semester, subjectId) }, + fetch = { remote.getAttendanceSummary(student, semester, subjectId) }, + saveFetchResult = { old, new -> + local.deleteAttendanceSummary(old uniqueSubtract new) + local.saveAttendanceSummary(new uniqueSubtract old) } - } + ) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocal.kt index f355f4166..f68e13cbc 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocal.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories.completedlessons import io.github.wulkanowy.data.db.dao.CompletedLessonsDao import io.github.wulkanowy.data.db.entities.CompletedLesson import io.github.wulkanowy.data.db.entities.Semester +import kotlinx.coroutines.flow.Flow import org.threeten.bp.LocalDate import javax.inject.Inject import javax.inject.Singleton @@ -18,7 +19,7 @@ class CompletedLessonsLocal @Inject constructor(private val completedLessonsDb: completedLessonsDb.deleteAll(completedLessons) } - suspend fun getCompletedLessons(semester: Semester, start: LocalDate, end: LocalDate): List { + fun getCompletedLessons(semester: Semester, start: LocalDate, end: LocalDate): Flow> { return completedLessonsDb.loadAll(semester.diaryId, semester.studentId, start, end) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRepository.kt index 8e81c54ae..7303575e0 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRepository.kt @@ -1,9 +1,9 @@ package io.github.wulkanowy.data.repositories.completedlessons -import io.github.wulkanowy.data.db.entities.CompletedLesson import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.uniqueSubtract import org.threeten.bp.LocalDate @@ -16,15 +16,14 @@ class CompletedLessonsRepository @Inject constructor( private val remote: CompletedLessonsRemote ) { - suspend fun getCompletedLessons(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): List { - return local.getCompletedLessons(semester, start.monday, end.sunday).filter { !forceRefresh }.ifEmpty { - val new = remote.getCompletedLessons(student, semester, start.monday, end.sunday) - val old = local.getCompletedLessons(semester, start.monday, end.sunday) - - local.deleteCompleteLessons(old.uniqueSubtract(new)) - local.saveCompletedLessons(new.uniqueSubtract(old)) - - local.getCompletedLessons(semester, start.monday, end.sunday) - }.filter { it.date in start..end } - } + fun getCompletedLessons(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { local.getCompletedLessons(semester, start.monday, end.sunday) }, + fetch = { remote.getCompletedLessons(student, semester, start.monday, end.sunday) }, + saveFetchResult = { old, new -> + local.deleteCompleteLessons(old uniqueSubtract new) + local.saveCompletedLessons(new uniqueSubtract old) + }, + filterResult = { it.filter { item -> item.date in start..end } } + ) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt index d1888380a..2b32f5270 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories.exam import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Semester +import kotlinx.coroutines.flow.Flow import org.threeten.bp.LocalDate import javax.inject.Inject import javax.inject.Singleton @@ -10,7 +11,7 @@ import javax.inject.Singleton @Singleton class ExamLocal @Inject constructor(private val examDb: ExamDao) { - suspend fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate): List { + fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate): Flow> { return examDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRepository.kt index 13af62c5b..152974170 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRepository.kt @@ -1,9 +1,9 @@ package io.github.wulkanowy.data.repositories.exam -import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.uniqueSubtract import org.threeten.bp.LocalDate @@ -16,15 +16,14 @@ class ExamRepository @Inject constructor( private val remote: ExamRemote ) { - suspend fun getExams(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): List { - return local.getExams(semester, start.monday, end.sunday).filter { !forceRefresh }.ifEmpty { - val new = remote.getExams(student, semester, start.monday, end.sunday) - val old = local.getExams(semester, start.monday, end.sunday) - - local.deleteExams(old.uniqueSubtract(new)) - local.saveExams(new.uniqueSubtract(old)) - - local.getExams(semester, start.monday, end.sunday) - }.filter { it.date in start..end } - } + fun getExams(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { local.getExams(semester, start.monday, end.sunday) }, + fetch = { remote.getExams(student, semester, start.monday, end.sunday) }, + saveFetchResult = { old, new -> + local.deleteExams(old uniqueSubtract new) + local.saveExams(new uniqueSubtract old) + }, + filterResult = { it.filter { item -> item.date in start..end } } + ) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt index 234fc6b80..ed3635423 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt @@ -5,6 +5,7 @@ import io.github.wulkanowy.data.db.dao.GradeSummaryDao import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Semester +import kotlinx.coroutines.flow.Flow import javax.inject.Inject import javax.inject.Singleton @@ -30,7 +31,7 @@ class GradeLocal @Inject constructor( gradeSummaryDb.updateAll(gradesSummary) } - suspend fun getGradesDetails(semester: Semester): List { + fun getGradesDetails(semester: Semester): Flow> { return gradeDb.loadAll(semester.semesterId, semester.studentId) } @@ -42,7 +43,7 @@ class GradeLocal @Inject constructor( gradeSummaryDb.deleteAll(gradesSummary) } - suspend fun getGradesSummary(semester: Semester): List { + fun getGradesSummary(semester: Semester): Flow> { return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt index 6dcbb0657..935cbedd9 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt @@ -4,7 +4,11 @@ import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map import org.threeten.bp.LocalDateTime import javax.inject.Inject import javax.inject.Singleton @@ -15,30 +19,30 @@ class GradeRepository @Inject constructor( private val remote: GradeRemote ) { - suspend fun getGrades(student: Student, semester: Semester, forceRefresh: Boolean = false, notify: Boolean = false): Pair, List> { - val details = local.getGradesDetails(semester) - val summaries = local.getGradesSummary(semester) - - if ((details.isNotEmpty() || summaries.isNotEmpty()) && !forceRefresh) { - return details to summaries + fun getGrades(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource( + shouldFetch = { (details, summaries) -> details.isEmpty() || summaries.isEmpty() || forceRefresh }, + query = { local.getGradesDetails(semester).combine(local.getGradesSummary(semester)) { details, summaries -> details to summaries } }, + fetch = { remote.getGrades(student, semester) }, + saveFetchResult = { old, new -> + refreshGradeDetails(student, old.first, new.first, notify) + refreshGradeSummaries(old.second, new.second, notify) } + ) - val (newDetails, newSummary) = remote.getGrades(student, semester) - val oldGrades = local.getGradesDetails(semester) - + private suspend fun refreshGradeDetails(student: Student, oldGrades: List, newDetails: List, notify: Boolean) { val notifyBreakDate = oldGrades.maxBy { it.date }?.date ?: student.registrationDate.toLocalDate() - local.deleteGrades(oldGrades.uniqueSubtract(newDetails)) - local.saveGrades(newDetails.uniqueSubtract(oldGrades).onEach { - if (it.date >= notifyBreakDate) it.apply { - isRead = false - if (notify) isNotified = false - } - }) + local.deleteGrades(oldGrades uniqueSubtract newDetails) + local.saveGrades((newDetails uniqueSubtract oldGrades).onEach { + if (it.date >= notifyBreakDate) it.apply { + isRead = false + if (notify) isNotified = false + } + }) + } - val oldSummaries = local.getGradesSummary(semester) - - local.deleteGradesSummary(oldSummaries.uniqueSubtract(newSummary)) - local.saveGradesSummary(newSummary.uniqueSubtract(oldSummaries).onEach { summary -> + private suspend fun refreshGradeSummaries(oldSummaries: List, newSummary: List, notify: Boolean) { + local.deleteGradesSummary(oldSummaries uniqueSubtract newSummary) + local.saveGradesSummary((newSummary uniqueSubtract oldSummaries).onEach { summary -> val oldSummary = oldSummaries.find { oldSummary -> oldSummary.subject == summary.subject } summary.isPredictedGradeNotified = when { summary.predictedGrade.isEmpty() -> true @@ -62,24 +66,22 @@ class GradeRepository @Inject constructor( else -> oldSummary.finalGradeLastChange } }) - - return local.getGradesDetails(semester) to local.getGradesSummary(semester) } - suspend fun getUnreadGrades(semester: Semester): List { - return local.getGradesDetails(semester).filter { grade -> !grade.isRead } + fun getUnreadGrades(semester: Semester): Flow> { + return local.getGradesDetails(semester).map { it.filter { grade -> !grade.isRead } } } - suspend fun getNotNotifiedGrades(semester: Semester): List { - return local.getGradesDetails(semester).filter { grade -> !grade.isNotified } + fun getNotNotifiedGrades(semester: Semester): Flow> { + return local.getGradesDetails(semester).map { it.filter { grade -> !grade.isNotified } } } - suspend fun getNotNotifiedPredictedGrades(semester: Semester): List { - return local.getGradesSummary(semester).filter { gradeSummary -> !gradeSummary.isPredictedGradeNotified } + fun getNotNotifiedPredictedGrades(semester: Semester): Flow> { + return local.getGradesSummary(semester).map { it.filter { gradeSummary -> !gradeSummary.isPredictedGradeNotified } } } - suspend fun getNotNotifiedFinalGrades(semester: Semester): List { - return local.getGradesSummary(semester).filter { gradeSummary -> !gradeSummary.isFinalGradeNotified } + fun getNotNotifiedFinalGrades(semester: Semester): Flow> { + return local.getGradesSummary(semester).map { it.filter { gradeSummary -> !gradeSummary.isFinalGradeNotified } } } suspend fun updateGrade(grade: Grade) { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt index d34f2b2ec..e0e2cd4db 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt @@ -5,6 +5,7 @@ import io.github.wulkanowy.data.db.dao.GradeStatisticsDao import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.data.db.entities.Semester +import kotlinx.coroutines.flow.Flow import javax.inject.Inject import javax.inject.Singleton @@ -14,34 +15,14 @@ class GradeStatisticsLocal @Inject constructor( private val gradePointsStatisticsDb: GradePointsStatisticsDao ) { - suspend fun getGradesStatistics(semester: Semester, isSemester: Boolean): List { + fun getGradesStatistics(semester: Semester, isSemester: Boolean): Flow> { return gradeStatisticsDb.loadAll(semester.semesterId, semester.studentId, isSemester) } - suspend fun getGradesPointsStatistics(semester: Semester): List { + fun getGradesPointsStatistics(semester: Semester): Flow> { return gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) } - suspend fun getGradesStatistics(semester: Semester, isSemester: Boolean, subjectName: String): List { - return when (subjectName) { - "Wszystkie" -> { - val statistics = gradeStatisticsDb.loadAll(semester.semesterId, semester.studentId, isSemester) - statistics.groupBy { it.grade }.map { - GradeStatistics(semester.studentId, semester.semesterId, subjectName, it.key, - it.value.fold(0) { acc, e -> acc + e.amount }, false) - } + statistics - } - else -> gradeStatisticsDb.loadSubject(semester.semesterId, semester.studentId, subjectName, isSemester) - } - } - - suspend fun getGradesPointsStatistics(semester: Semester, subjectName: String): List { - return when (subjectName) { - "Wszystkie" -> gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) - else -> gradePointsStatisticsDb.loadSubject(semester.semesterId, semester.studentId, subjectName) - } - } - suspend fun saveGradesStatistics(gradesStatistics: List) { gradeStatisticsDb.insertAll(gradesStatistics) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt index 93df69406..52ca705f7 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt @@ -6,6 +6,7 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.pojos.GradeStatisticsItem import io.github.wulkanowy.ui.modules.grade.statistics.ViewType +import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.uniqueSubtract import javax.inject.Inject import javax.inject.Singleton @@ -16,29 +17,40 @@ class GradeStatisticsRepository @Inject constructor( private val remote: GradeStatisticsRemote ) { - suspend fun getGradesStatistics(student: Student, semester: Semester, subjectName: String, isSemester: Boolean, forceRefresh: Boolean = false): List { - return local.getGradesStatistics(semester, isSemester, subjectName).mapToStatisticItems().filter { !forceRefresh }.ifEmpty { - val new = remote.getGradeStatistics(student, semester, isSemester) - val old = local.getGradesStatistics(semester, isSemester) - - local.deleteGradesStatistics(old.uniqueSubtract(new)) - local.saveGradesStatistics(new.uniqueSubtract(old)) - - local.getGradesStatistics(semester, isSemester, subjectName).mapToStatisticItems() + fun getGradesStatistics(student: Student, semester: Semester, subjectName: String, isSemester: Boolean, forceRefresh: Boolean) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { local.getGradesStatistics(semester, isSemester) }, + fetch = { remote.getGradeStatistics(student, semester, isSemester) }, + saveFetchResult = { old, new -> + local.deleteGradesStatistics(old uniqueSubtract new) + local.saveGradesStatistics(new uniqueSubtract old) + }, + mapResult = { items -> + when (subjectName) { + "Wszystkie" -> items.groupBy { it.grade }.map { + GradeStatistics(semester.studentId, semester.semesterId, subjectName, it.key, + it.value.fold(0) { acc, e -> acc + e.amount }, false) + } + items + else -> items.filter { it.subject == subjectName } + }.mapToStatisticItems() } - } + ) - suspend fun getGradesPointsStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean): List { - return local.getGradesPointsStatistics(semester, subjectName).mapToStatisticsItem().filter { !forceRefresh }.ifEmpty { - val new = remote.getGradePointsStatistics(student, semester) - val old = local.getGradesPointsStatistics(semester) - - local.deleteGradesPointsStatistics(old.uniqueSubtract(new)) - local.saveGradesPointsStatistics(new.uniqueSubtract(old)) - - local.getGradesPointsStatistics(semester, subjectName).mapToStatisticsItem() + fun getGradesPointsStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { local.getGradesPointsStatistics(semester) }, + fetch = { remote.getGradePointsStatistics(student, semester) }, + saveFetchResult = { old, new -> + local.deleteGradesPointsStatistics(old uniqueSubtract new) + local.saveGradesPointsStatistics(new uniqueSubtract old) + }, + mapResult = { items -> + when (subjectName) { + "Wszystkie" -> items + else -> items.filter { it.subject == subjectName } + }.mapToStatisticsItem() } - } + ) private fun List.mapToStatisticItems() = groupBy { it.subject }.map { GradeStatisticsItem( diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkLocal.kt index ed6bb0cf5..5373e1b10 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkLocal.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories.homework import io.github.wulkanowy.data.db.dao.HomeworkDao import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Semester +import kotlinx.coroutines.flow.Flow import org.threeten.bp.LocalDate import javax.inject.Inject import javax.inject.Singleton @@ -22,7 +23,7 @@ class HomeworkLocal @Inject constructor(private val homeworkDb: HomeworkDao) { homeworkDb.updateAll(homework) } - suspend fun getHomework(semester: Semester, startDate: LocalDate, endDate: LocalDate): List { + fun getHomework(semester: Semester, startDate: LocalDate, endDate: LocalDate): Flow> { return homeworkDb.loadAll(semester.semesterId, semester.studentId, startDate, endDate) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRepository.kt index ca0a84a5f..5b6aeed41 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRepository.kt @@ -4,6 +4,7 @@ import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.uniqueSubtract import org.threeten.bp.LocalDate @@ -16,18 +17,15 @@ class HomeworkRepository @Inject constructor( private val remote: HomeworkRemote ) { - suspend fun getHomework(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): List { - return local.getHomework(semester, start.monday, end.sunday).filter { !forceRefresh }.ifEmpty { - val new = remote.getHomework(student, semester, start.monday, end.sunday) - - val old = local.getHomework(semester, start.monday, end.sunday) - - local.deleteHomework(old.uniqueSubtract(new)) - local.saveHomework(new.uniqueSubtract(old)) - - local.getHomework(semester, start.monday, end.sunday) + fun getHomework(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { local.getHomework(semester, start.monday, end.sunday) }, + fetch = { remote.getHomework(student, semester, start.monday, end.sunday) }, + saveFetchResult = { old, new -> + local.deleteHomework(old uniqueSubtract new) + local.saveHomework(new uniqueSubtract old) } - } + ) suspend fun toggleDone(homework: Homework) { local.updateHomework(listOf(homework.apply { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/logger/LoggerRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/logger/LoggerRepository.kt index d03d3ccd6..e50955e2c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/logger/LoggerRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/logger/LoggerRepository.kt @@ -12,16 +12,12 @@ class LoggerRepository @Inject constructor( private val dispatchers: DispatchersProvider ) { - suspend fun getLastLogLines(): List { - return getLastModified().readText().split("\n") - } + suspend fun getLastLogLines() = getLastModified().readText().split("\n") - suspend fun getLogFiles(): List { - return withContext(dispatchers.backgroundThread) { - File(context.filesDir.absolutePath).listFiles(File::isFile)?.filter { - it.name.endsWith(".log") - }!! - } + suspend fun getLogFiles() = withContext(dispatchers.backgroundThread) { + File(context.filesDir.absolutePath).listFiles(File::isFile)?.filter { + it.name.endsWith(".log") + }!! } private suspend fun getLastModified(): File { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocal.kt index 22b5786dd..ecc784c44 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocal.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories.luckynumber import io.github.wulkanowy.data.db.dao.LuckyNumberDao import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.Student +import kotlinx.coroutines.flow.Flow import org.threeten.bp.LocalDate import javax.inject.Inject import javax.inject.Singleton @@ -22,7 +23,7 @@ class LuckyNumberLocal @Inject constructor(private val luckyNumberDb: LuckyNumbe luckyNumberDb.deleteAll(listOfNotNull(luckyNumber)) } - suspend fun getLuckyNumber(student: Student, date: LocalDate): LuckyNumber? { + fun getLuckyNumber(student: Student, date: LocalDate): Flow { return luckyNumberDb.load(student.studentId, date) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRepository.kt index 3f6089628..3553a4615 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRepository.kt @@ -2,6 +2,8 @@ package io.github.wulkanowy.data.repositories.luckynumber import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.utils.networkBoundResource +import kotlinx.coroutines.flow.Flow import org.threeten.bp.LocalDate.now import javax.inject.Inject import javax.inject.Singleton @@ -12,31 +14,25 @@ class LuckyNumberRepository @Inject constructor( private val remote: LuckyNumberRemote ) { - suspend fun getLuckyNumber(student: Student, forceRefresh: Boolean = false, notify: Boolean = false): LuckyNumber? { - return local.getLuckyNumber(student, now())?.takeIf { !forceRefresh } ?: run { - val new = remote.getLuckyNumber(student) - val old = local.getLuckyNumber(student, now()) - + fun getLuckyNumber(student: Student, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource( + shouldFetch = { it == null || forceRefresh }, + query = { local.getLuckyNumber(student, now()) }, + fetch = { remote.getLuckyNumber(student) }, + saveFetchResult = { old, new -> if (new != old) { old?.let { local.deleteLuckyNumber(it) } local.saveLuckyNumber(new?.apply { if (notify) isNotified = false }) } - - local.saveLuckyNumber(new?.apply { - if (notify) isNotified = false - }) - - local.getLuckyNumber(student, now()) } - } + ) - suspend fun getNotNotifiedLuckyNumber(student: Student): LuckyNumber? { + fun getNotNotifiedLuckyNumber(student: Student): Flow { return local.getLuckyNumber(student, now()) } - suspend fun updateLuckyNumber(luckyNumber: LuckyNumber) { + suspend fun updateLuckyNumber(luckyNumber: LuckyNumber?) { local.updateLuckyNumber(luckyNumber) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageLocal.kt index f05c49d87..01231efe8 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageLocal.kt @@ -7,6 +7,7 @@ import io.github.wulkanowy.data.db.entities.MessageAttachment import io.github.wulkanowy.data.db.entities.MessageWithAttachment import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.message.MessageFolder.TRASHED +import kotlinx.coroutines.flow.Flow import javax.inject.Inject import javax.inject.Singleton @@ -28,7 +29,7 @@ class MessageLocal @Inject constructor( messagesDb.deleteAll(messages) } - suspend fun getMessageWithAttachment(student: Student, message: Message): MessageWithAttachment { + fun getMessageWithAttachment(student: Student, message: Message): Flow { return messagesDb.loadMessageWithAttachment(student.id.toInt(), message.messageId) } @@ -36,7 +37,7 @@ class MessageLocal @Inject constructor( messageAttachmentDao.insertAttachments(attachments) } - suspend fun getMessages(student: Student, folder: MessageFolder): List { + fun getMessages(student: Student, folder: MessageFolder): Flow> { return when (folder) { TRASHED -> messagesDb.loadDeleted(student.id.toInt()) else -> messagesDb.loadAll(student.id.toInt(), folder.id) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRepository.kt index 7138566bd..ea46b6877 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRepository.kt @@ -1,13 +1,15 @@ package io.github.wulkanowy.data.repositories.message import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.db.entities.MessageWithAttachment import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.message.MessageFolder.RECEIVED import io.github.wulkanowy.sdk.pojo.SentMessage +import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -18,46 +20,37 @@ class MessageRepository @Inject constructor( private val remote: MessageRemote ) { - suspend fun getMessages(student: Student, semester: Semester, folder: MessageFolder, forceRefresh: Boolean = false, notify: Boolean = false): List { - return local.getMessages(student, folder).filter { !forceRefresh }.ifEmpty { - val new = remote.getMessages(student, semester, folder) - val old = local.getMessages(student, folder) - - local.deleteMessages(old.uniqueSubtract(new)) - local.saveMessages(new.uniqueSubtract(old).onEach { + fun getMessages(student: Student, semester: Semester, folder: MessageFolder, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { local.getMessages(student, folder) }, + fetch = { remote.getMessages(student, semester, folder) }, + saveFetchResult = { old, new -> + local.deleteMessages(old uniqueSubtract new) + local.saveMessages((new uniqueSubtract old).onEach { it.isNotified = !notify }) - - local.getMessages(student, folder) } - } + ) - suspend fun getMessage(student: Student, message: Message, markAsRead: Boolean = false): MessageWithAttachment { - return local.getMessageWithAttachment(student, message).let { - if (it.message.content.isNotEmpty().also { status -> - Timber.d("Message content in db empty: ${!status}") - } && !it.message.unread) { - return@let it - } - - val dbMessage = local.getMessageWithAttachment(student, message) - - val (downloadedMessage, attachments) = remote.getMessagesContentDetails(student, dbMessage.message, markAsRead) - - local.updateMessages(listOf(dbMessage.message.copy(unread = !markAsRead).apply { - id = dbMessage.message.id + fun getMessage(student: Student, message: Message, markAsRead: Boolean = false) = networkBoundResource( + shouldFetch = { + Timber.d("Message content in db empty: ${it.message.content.isEmpty()}") + it.message.unread || it.message.content.isEmpty() + }, + query = { local.getMessageWithAttachment(student, message) }, + fetch = { remote.getMessagesContentDetails(student, it.message, markAsRead) }, + saveFetchResult = { old, (downloadedMessage, attachments) -> + local.updateMessages(listOf(old.message.copy(unread = !markAsRead).apply { + id = old.message.id content = content.ifBlank { downloadedMessage } })) local.saveMessageAttachments(attachments) - Timber.d("Message ${message.messageId} with blank content: ${dbMessage.message.content.isBlank()}, marked as read") - - local.getMessageWithAttachment(student, message) + Timber.d("Message ${message.messageId} with blank content: ${old.message.content.isBlank()}, marked as read") } - } + ) - suspend fun getNotNotifiedMessages(student: Student): List { - return local.getMessages(student, RECEIVED) - .filter { message -> !message.isNotified && message.unread } + fun getNotNotifiedMessages(student: Student): Flow> { + return local.getMessages(student, RECEIVED).map { it.filter { message -> !message.isNotified && message.unread } } } suspend fun updateMessages(messages: List) { @@ -68,15 +61,12 @@ class MessageRepository @Inject constructor( return remote.sendMessage(student, subject, content, recipients) } - suspend fun deleteMessage(student: Student, message: Message): Boolean { - val delete = remote.deleteMessage(student, message) + suspend fun deleteMessage(student: Student, message: Message) { + val isDeleted = remote.deleteMessage(student, message) - if (!message.removed) local.updateMessages(listOf(message.copy(removed = true).apply { + if (!message.removed) local.updateMessages(listOf(message.copy(removed = isDeleted).apply { id = message.id content = message.content - })) - else local.deleteMessages(listOf(message)) - - return delete // TODO: wtf + })) else local.deleteMessages(listOf(message)) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceLocal.kt index 911ed3af5..0ccb3d7ef 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceLocal.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories.mobiledevice import io.github.wulkanowy.data.db.dao.MobileDeviceDao import io.github.wulkanowy.data.db.entities.MobileDevice import io.github.wulkanowy.data.db.entities.Semester +import kotlinx.coroutines.flow.Flow import javax.inject.Inject import javax.inject.Singleton @@ -17,7 +18,7 @@ class MobileDeviceLocal @Inject constructor(private val mobileDb: MobileDeviceDa mobileDb.deleteAll(devices) } - suspend fun getDevices(semester: Semester): List { + fun getDevices(semester: Semester): Flow> { return mobileDb.loadAll(semester.studentId) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRepository.kt index f327ef607..65526ef86 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRepository.kt @@ -4,6 +4,7 @@ import io.github.wulkanowy.data.db.entities.MobileDevice import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.pojos.MobileDeviceToken +import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.uniqueSubtract import javax.inject.Inject import javax.inject.Singleton @@ -14,20 +15,19 @@ class MobileDeviceRepository @Inject constructor( private val remote: MobileDeviceRemote ) { - suspend fun getDevices(student: Student, semester: Semester, forceRefresh: Boolean = false): List { - return local.getDevices(semester).filter { !forceRefresh }.ifEmpty { - val new = remote.getDevices(student, semester) - val old = local.getDevices(semester) - + fun getDevices(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { local.getDevices(semester) }, + fetch = { remote.getDevices(student, semester) }, + saveFetchResult = { old, new -> local.deleteDevices(old uniqueSubtract new) local.saveDevices(new uniqueSubtract old) - - local.getDevices(semester) } - } + ) - suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice): Boolean { - return remote.unregisterDevice(student, semester, device) + suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) { + remote.unregisterDevice(student, semester, device) + local.deleteDevices(listOf(device)) } suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteLocal.kt index b1c6b2902..85ba5e223 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteLocal.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories.note import io.github.wulkanowy.data.db.dao.NoteDao import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Student +import kotlinx.coroutines.flow.Flow import javax.inject.Inject import javax.inject.Singleton @@ -21,7 +22,7 @@ class NoteLocal @Inject constructor(private val noteDb: NoteDao) { noteDb.deleteAll(notes) } - suspend fun getNotes(student: Student): List { + fun getNotes(student: Student): Flow> { return noteDb.loadAll(student.studentId) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteRepository.kt index 3628f5b87..6cf62ba22 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteRepository.kt @@ -3,7 +3,10 @@ package io.github.wulkanowy.data.repositories.note import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import javax.inject.Inject import javax.inject.Singleton @@ -13,29 +16,27 @@ class NoteRepository @Inject constructor( private val remote: NoteRemote ) { - suspend fun getNotes(student: Student, semester: Semester, forceRefresh: Boolean = false, notify: Boolean = false): List { - return local.getNotes(student).filter { !forceRefresh }.ifEmpty { - val new = remote.getNotes(student, semester) - val old = local.getNotes(student) - - local.deleteNotes(old.uniqueSubtract(new)) - local.saveNotes(new.uniqueSubtract(old).onEach { + fun getNotes(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { local.getNotes(student) }, + fetch = { remote.getNotes(student, semester) }, + saveFetchResult = { old, new -> + local.deleteNotes(old uniqueSubtract new) + local.saveNotes((new uniqueSubtract old).onEach { if (it.date >= student.registrationDate.toLocalDate()) it.apply { isRead = false if (notify) isNotified = false } }) - - local.getNotes(student) } - } + ) - suspend fun getNotNotifiedNotes(student: Student): List { - return local.getNotes(student).filter { note -> !note.isNotified } + fun getNotNotifiedNotes(student: Student): Flow> { + return local.getNotes(student).map { it.filter { note -> !note.isNotified } } } suspend fun updateNote(note: Note) { - return local.updateNotes(listOf(note)) + local.updateNotes(listOf(note)) } suspend fun updateNotes(notes: List) { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientRepository.kt index 5c16c57b8..f5e876b03 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientRepository.kt @@ -14,13 +14,17 @@ class RecipientRepository @Inject constructor( private val remote: RecipientRemote ) { - suspend fun getRecipients(student: Student, role: Int, unit: ReportingUnit, forceRefresh: Boolean = false): List { - return local.getRecipients(student, role, unit).filter { !forceRefresh }.ifEmpty { - val new = remote.getRecipients(student, role, unit) - val old = local.getRecipients(student, role, unit) + suspend fun refreshRecipients(student: Student, role: Int, unit: ReportingUnit) { + val new = remote.getRecipients(student, role, unit) + val old = local.getRecipients(student, role, unit) - local.deleteRecipients(old.uniqueSubtract(new)) - local.saveRecipients(new.uniqueSubtract(old)) + local.deleteRecipients(old uniqueSubtract new) + local.saveRecipients(new uniqueSubtract old) + } + + suspend fun getRecipients(student: Student, role: Int, unit: ReportingUnit): List { + return local.getRecipients(student, role, unit).ifEmpty { + refreshRecipients(student, role, unit) local.getRecipients(student, role, unit) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitRepository.kt index 70aefb9fd..ff5839460 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitRepository.kt @@ -12,23 +12,27 @@ class ReportingUnitRepository @Inject constructor( private val remote: ReportingUnitRemote ) { - suspend fun getReportingUnits(student: Student, forceRefresh: Boolean = false): List { - return local.getReportingUnits(student).filter { !forceRefresh }.ifEmpty { - val new = remote.getReportingUnits(student) - val old = local.getReportingUnits(student) + suspend fun refreshReportingUnits(student: Student) { + val new = remote.getReportingUnits(student) + val old = local.getReportingUnits(student) - local.deleteReportingUnits(old.uniqueSubtract(new)) - local.saveReportingUnits(new.uniqueSubtract(old)) + local.deleteReportingUnits(old.uniqueSubtract(new)) + local.saveReportingUnits(new.uniqueSubtract(old)) + } + + suspend fun getReportingUnits(student: Student): List { + return local.getReportingUnits(student).ifEmpty { + refreshReportingUnits(student) local.getReportingUnits(student) } } - suspend fun getReportingUnit(student: Student, unitId: Int): ReportingUnit { + suspend fun getReportingUnit(student: Student, unitId: Int): ReportingUnit? { return local.getReportingUnit(student, unitId) ?: run { - getReportingUnits(student, true) + refreshReportingUnits(student) - return local.getReportingUnit(student, unitId)!! + return local.getReportingUnit(student, unitId) } } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolLocal.kt index c8479b8f6..bc1b2f446 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolLocal.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories.school import io.github.wulkanowy.data.db.dao.SchoolDao import io.github.wulkanowy.data.db.entities.School import io.github.wulkanowy.data.db.entities.Semester +import kotlinx.coroutines.flow.Flow import javax.inject.Inject class SchoolLocal @Inject constructor(private val schoolDb: SchoolDao) { @@ -15,7 +16,7 @@ class SchoolLocal @Inject constructor(private val schoolDb: SchoolDao) { schoolDb.deleteAll(listOf(school)) } - suspend fun getSchool(semester: Semester): School? { + fun getSchool(semester: Semester): Flow { return schoolDb.load(semester.studentId, semester.classId) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolRepository.kt index 9ca945d06..4c84c3194 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolRepository.kt @@ -1,8 +1,8 @@ package io.github.wulkanowy.data.repositories.school -import io.github.wulkanowy.data.db.entities.School import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.utils.networkBoundResource import javax.inject.Inject import javax.inject.Singleton @@ -12,18 +12,16 @@ class SchoolRepository @Inject constructor( private val remote: SchoolRemote ) { - suspend fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean = false): School { - return local.getSchool(semester).takeIf { it != null && !forceRefresh } ?: run { - val new = remote.getSchoolInfo(student, semester) - val old = local.getSchool(semester) - + fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource( + shouldFetch = { it == null || forceRefresh }, + query = { local.getSchool(semester) }, + fetch = { remote.getSchoolInfo(student, semester) }, + saveFetchResult = { old, new -> if (new != old && old != null) { local.deleteSchool(old) local.saveSchool(new) } local.saveSchool(new) - - local.getSchool(semester)!! } - } + ) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRepository.kt index aeb424008..28d37ed85 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRepository.kt @@ -1,22 +1,24 @@ package io.github.wulkanowy.data.repositories.semester -import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.getCurrentOrLast import io.github.wulkanowy.utils.isCurrent import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.withContext import javax.inject.Inject import javax.inject.Singleton @Singleton class SemesterRepository @Inject constructor( private val remote: SemesterRemote, - private val local: SemesterLocal + private val local: SemesterLocal, + private val dispatchers: DispatchersProvider ) { - suspend fun getSemesters(student: Student, forceRefresh: Boolean = false, refreshOnNoCurrent: Boolean = false): List { - return local.getSemesters(student).let { semesters -> + suspend fun getSemesters(student: Student, forceRefresh: Boolean = false, refreshOnNoCurrent: Boolean = false) = withContext(dispatchers.backgroundThread) { + local.getSemesters(student).let { semesters -> semesters.filter { !forceRefresh && when { Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API -> semesters.firstOrNull { it.isCurrent }?.diaryId != 0 @@ -36,7 +38,7 @@ class SemesterRepository @Inject constructor( } } - suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false): Semester { - return getSemesters(student, forceRefresh).getCurrentOrLast() + suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) = withContext(dispatchers.backgroundThread) { + getSemesters(student, forceRefresh).getCurrentOrLast() } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentLocal.kt index 5a4322f36..3a964aae5 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentLocal.kt @@ -4,52 +4,55 @@ import android.content.Context import io.github.wulkanowy.data.db.dao.StudentDao import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.security.decrypt import io.github.wulkanowy.utils.security.encrypt +import kotlinx.coroutines.withContext import javax.inject.Inject import javax.inject.Singleton @Singleton class StudentLocal @Inject constructor( private val studentDb: StudentDao, + private val dispatchers: DispatchersProvider, private val context: Context ) { - suspend fun saveStudents(students: List): List { - return studentDb.insertAll(students.map { + suspend fun saveStudents(students: List) = withContext(dispatchers.backgroundThread) { + studentDb.insertAll(students.map { if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) it.copy(password = encrypt(it.password, context)) else it }) } - suspend fun getStudents(decryptPass: Boolean): List { - return studentDb.loadAll().map { + suspend fun getStudents(decryptPass: Boolean) = withContext(dispatchers.backgroundThread) { + studentDb.loadAll().map { it.apply { if (decryptPass && Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) password = decrypt(password) } } } - suspend fun getStudentById(id: Int): Student? { - return studentDb.loadById(id)?.apply { + suspend fun getStudentById(id: Int) = withContext(dispatchers.backgroundThread) { + studentDb.loadById(id)?.apply { if (Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) password = decrypt(password) } } - suspend fun getCurrentStudent(decryptPass: Boolean): Student? { - return studentDb.loadCurrent()?.apply { + suspend fun getCurrentStudent(decryptPass: Boolean) = withContext(dispatchers.backgroundThread) { + studentDb.loadCurrent()?.apply { if (decryptPass && Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) password = decrypt(password) } } - suspend fun setCurrentStudent(student: Student) { - return studentDb.run { + suspend fun setCurrentStudent(student: Student) = withContext(dispatchers.backgroundThread) { + studentDb.run { resetCurrent() updateCurrent(student.id) } } - suspend fun logoutStudent(student: Student) { - return studentDb.delete(student) + suspend fun logoutStudent(student: Student) = withContext(dispatchers.backgroundThread) { + studentDb.delete(student) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectLocal.kt index 1f9dfff32..e225a381e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectLocal.kt @@ -3,13 +3,14 @@ package io.github.wulkanowy.data.repositories.subject import io.github.wulkanowy.data.db.dao.SubjectDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Subject +import kotlinx.coroutines.flow.Flow import javax.inject.Inject import javax.inject.Singleton @Singleton class SubjectLocal @Inject constructor(private val subjectDao: SubjectDao) { - suspend fun getSubjects(semester: Semester): List { + fun getSubjects(semester: Semester): Flow> { return subjectDao.loadAll(semester.diaryId, semester.studentId) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectRepository.kt index 646e3642a..60a0c3e71 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectRepository.kt @@ -2,7 +2,7 @@ package io.github.wulkanowy.data.repositories.subject import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.db.entities.Subject +import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.uniqueSubtract import javax.inject.Inject import javax.inject.Singleton @@ -13,15 +13,13 @@ class SubjectRepository @Inject constructor( private val remote: SubjectRemote ) { - suspend fun getSubjects(student: Student, semester: Semester, forceRefresh: Boolean = false): List { - return local.getSubjects(semester).filter { !forceRefresh }.ifEmpty { - val new = remote.getSubjects(student, semester) - val old = local.getSubjects(semester) - - local.deleteSubjects(old.uniqueSubtract(new)) - local.saveSubjects(new.uniqueSubtract(old)) - - local.getSubjects(semester) + fun getSubjects(student: Student, semester: Semester, forceRefresh: Boolean = false) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { local.getSubjects(semester) }, + fetch = { remote.getSubjects(student, semester) }, + saveFetchResult = { old, new -> + local.deleteSubjects(old uniqueSubtract new) + local.saveSubjects(new uniqueSubtract old) } - } + ) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherLocal.kt index 53680b7b3..908f45a1a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherLocal.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories.teacher import io.github.wulkanowy.data.db.dao.TeacherDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Teacher +import kotlinx.coroutines.flow.Flow import javax.inject.Inject class TeacherLocal @Inject constructor(private val teacherDb: TeacherDao) { @@ -15,7 +16,7 @@ class TeacherLocal @Inject constructor(private val teacherDb: TeacherDao) { teacherDb.deleteAll(teachers) } - suspend fun getTeachers(semester: Semester): List { + fun getTeachers(semester: Semester): Flow> { return teacherDb.loadAll(semester.studentId, semester.classId) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherRepository.kt index a540e78c4..df25a53ec 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherRepository.kt @@ -2,7 +2,7 @@ package io.github.wulkanowy.data.repositories.teacher import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.db.entities.Teacher +import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.uniqueSubtract import javax.inject.Inject import javax.inject.Singleton @@ -13,15 +13,13 @@ class TeacherRepository @Inject constructor( private val remote: TeacherRemote ) { - suspend fun getTeachers(student: Student, semester: Semester, forceRefresh: Boolean = false): List { - return local.getTeachers(semester).filter { !forceRefresh }.ifEmpty { - val new = remote.getTeachers(student, semester) - val old = local.getTeachers(semester) - - local.deleteTeachers(old.uniqueSubtract(new)) - local.saveTeachers(new.uniqueSubtract(old)) - - local.getTeachers(semester) + fun getTeachers(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { local.getTeachers(semester) }, + fetch = { remote.getTeachers(student, semester) }, + saveFetchResult = { old, new -> + local.deleteTeachers(old uniqueSubtract new) + local.saveTeachers(new uniqueSubtract old) } - } + ) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocal.kt index a90c664c0..91a4b2617 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocal.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories.timetable import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Timetable +import kotlinx.coroutines.flow.Flow import org.threeten.bp.LocalDate import javax.inject.Inject import javax.inject.Singleton @@ -18,7 +19,7 @@ class TimetableLocal @Inject constructor(private val timetableDb: TimetableDao) timetableDb.deleteAll(timetables) } - suspend fun getTimetable(semester: Semester, startDate: LocalDate, endDate: LocalDate): List { + fun getTimetable(semester: Semester, startDate: LocalDate, endDate: LocalDate): Flow> { return timetableDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepository.kt index b34075524..54ddf10b1 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepository.kt @@ -2,11 +2,12 @@ package io.github.wulkanowy.data.repositories.timetable import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.flow.map import org.threeten.bp.LocalDate import javax.inject.Inject import javax.inject.Singleton @@ -18,11 +19,11 @@ class TimetableRepository @Inject constructor( private val schedulerHelper: TimetableNotificationSchedulerHelper ) { - suspend fun getTimetable(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): List { - return local.getTimetable(semester, start.monday, start.sunday).filter { !forceRefresh }.ifEmpty { - val new = remote.getTimetable(student, semester, start.monday, start.sunday) - val old = local.getTimetable(semester, start.monday, start.sunday) - + fun getTimetable(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { local.getTimetable(semester, start.monday, end.sunday).map { schedulerHelper.scheduleNotifications(it, student); it } }, + fetch = { remote.getTimetable(student, semester, start.monday, end.sunday) }, + saveFetchResult = { old, new -> local.deleteTimetable(old.uniqueSubtract(new).also { schedulerHelper.cancelScheduled(it) }) local.saveTimetable(new.uniqueSubtract(old).also { schedulerHelper.scheduleNotifications(it, student) }.map { item -> item.also { new -> @@ -34,8 +35,7 @@ class TimetableRepository @Inject constructor( } } }) - - local.getTimetable(semester, start.monday, start.sunday) - }.filter { it.date in start..end }.also { schedulerHelper.scheduleNotifications(it, student) } - } + }, + filterResult = { it.filter { item -> item.date in start..end } } + ) } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt index f4333f337..3d118e6f1 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt @@ -12,7 +12,7 @@ class AttendanceSummaryWork @Inject constructor( ) : Work { override fun create(student: Student, semester: Semester): Completable { - return rxCompletable { attendanceSummaryRepository.getAttendanceSummary(student, semester, -1, true) } + return rxCompletable { attendanceSummaryRepository.getAttendanceSummary(student, semester, -1, true).waitForResult() } } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt index 069b6c8f4..23cb1acd2 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.attendance.AttendanceRepository -import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.sunday import io.reactivex.Completable import kotlinx.coroutines.rx2.rxCompletable import org.threeten.bp.LocalDate.now @@ -13,6 +13,6 @@ import javax.inject.Inject class AttendanceWork @Inject constructor(private val attendanceRepository: AttendanceRepository) : Work { override fun create(student: Student, semester: Semester): Completable { - return rxCompletable { attendanceRepository.getAttendance(student, semester, now().monday, now().sunday, true) } + return rxCompletable { attendanceRepository.getAttendance(student, semester, now().monday, now().sunday, true).waitForResult() } } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt index 8914fd369..347d8bbb1 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.completedlessons.CompletedLessonsRepository -import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.sunday import io.reactivex.Completable import kotlinx.coroutines.rx2.rxCompletable import org.threeten.bp.LocalDate.now @@ -15,7 +15,6 @@ class CompletedLessonWork @Inject constructor( ) : Work { override fun create(student: Student, semester: Semester): Completable { - return rxCompletable { completedLessonsRepository.getCompletedLessons(student, semester, now().monday, now().sunday, true) } + return rxCompletable { completedLessonsRepository.getCompletedLessons(student, semester, now().monday, now().sunday, true).waitForResult() } } } - diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt index 0a4512958..a8c7fdb39 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt @@ -13,6 +13,6 @@ import javax.inject.Inject class ExamWork @Inject constructor(private val examRepository: ExamRepository) : Work { override fun create(student: Student, semester: Semester): Completable { - return rxCompletable { examRepository.getExams(student, semester, now().monday, now().sunday, true) } + return rxCompletable { examRepository.getExams(student, semester, now().monday, now().sunday, true).waitForResult() } } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt index 0a1a9eee5..cbfa2d9af 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt @@ -7,10 +7,17 @@ import io.reactivex.Completable import kotlinx.coroutines.rx2.rxCompletable import javax.inject.Inject -class GradeStatisticsWork @Inject constructor(private val gradeStatisticsRepository: GradeStatisticsRepository) : Work { +class GradeStatisticsWork @Inject constructor( + private val gradeStatisticsRepository: GradeStatisticsRepository +) : Work { override fun create(student: Student, semester: Semester): Completable { - return rxCompletable { gradeStatisticsRepository.getGradesStatistics(student, semester, "Wszystkie", false, forceRefresh = true) } + return rxCompletable { + with(gradeStatisticsRepository) { + getGradesStatistics(student, semester, "Wszystkie", isSemester = true, forceRefresh = true).waitForResult() + getGradesStatistics(student, semester, "Wszystkie", isSemester = false, forceRefresh = true).waitForResult() + getGradesPointsStatistics(student, semester, "Wszystkie", forceRefresh = true).waitForResult() + } + } } } - diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt index 252966d64..741809930 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt @@ -19,6 +19,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.getCompatColor import io.reactivex.Completable +import kotlinx.coroutines.flow.first import kotlinx.coroutines.rx2.rxCompletable import kotlinx.coroutines.rx2.rxSingle import javax.inject.Inject @@ -32,14 +33,14 @@ class GradeWork @Inject constructor( ) : Work { override fun create(student: Student, semester: Semester): Completable { - return rxCompletable { gradeRepository.getGrades(student, semester, true, preferencesRepository.isNotificationsEnable) } - .concatWith(Completable.concatArray(rxSingle { gradeRepository.getNotNotifiedGrades(semester) }.flatMapCompletable { + return rxCompletable { gradeRepository.getGrades(student, semester, true, preferencesRepository.isNotificationsEnable).waitForResult() } + .concatWith(Completable.concatArray(rxSingle { gradeRepository.getNotNotifiedGrades(semester).first() }.flatMapCompletable { if (it.isNotEmpty()) notifyDetails(it) rxCompletable { gradeRepository.updateGrades(it.onEach { grade -> grade.isNotified = true }) } - }, rxSingle { gradeRepository.getNotNotifiedPredictedGrades(semester) }.flatMapCompletable { + }, rxSingle { gradeRepository.getNotNotifiedPredictedGrades(semester).first() }.flatMapCompletable { if (it.isNotEmpty()) notifyPredicted(it) rxCompletable { gradeRepository.updateGradesSummary(it.onEach { grade -> grade.isPredictedGradeNotified = true }) } - }, rxSingle { gradeRepository.getNotNotifiedFinalGrades(semester) }.flatMapCompletable { + }, rxSingle { gradeRepository.getNotNotifiedFinalGrades(semester).first() }.flatMapCompletable { if (it.isNotEmpty()) notifyFinal(it) rxCompletable { gradeRepository.updateGradesSummary(it.onEach { grade -> grade.isFinalGradeNotified = true }) } })) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt index 2bf5315a2..8b69b7b6e 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.homework.HomeworkRepository -import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.sunday import io.reactivex.Completable import kotlinx.coroutines.rx2.rxCompletable import org.threeten.bp.LocalDate.now @@ -13,6 +13,6 @@ import javax.inject.Inject class HomeworkWork @Inject constructor(private val homeworkRepository: HomeworkRepository) : Work { override fun create(student: Student, semester: Semester): Completable { - return rxCompletable { homeworkRepository.getHomework(student, semester, now().monday, now().sunday, true) } + return rxCompletable { homeworkRepository.getHomework(student, semester, now().monday, now().sunday, true).waitForResult() } } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt index 1389566bd..9bf3de0c6 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt @@ -18,6 +18,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.getCompatColor import io.reactivex.Completable +import kotlinx.coroutines.flow.first import kotlinx.coroutines.rx2.rxCompletable import kotlinx.coroutines.rx2.rxMaybe import javax.inject.Inject @@ -31,8 +32,8 @@ class LuckyNumberWork @Inject constructor( ) : Work { override fun create(student: Student, semester: Semester): Completable { - return rxMaybe { luckyNumberRepository.getLuckyNumber(student, true, preferencesRepository.isNotificationsEnable) } - .flatMap { rxMaybe { luckyNumberRepository.getNotNotifiedLuckyNumber(student) } } + return rxMaybe { luckyNumberRepository.getLuckyNumber(student, true, preferencesRepository.isNotificationsEnable).waitForResult() } + .flatMap { rxMaybe { luckyNumberRepository.getNotNotifiedLuckyNumber(student).first() } } .flatMapCompletable { notify(it) rxCompletable { luckyNumberRepository.updateLuckyNumber(it.apply { isNotified = true }) } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt index a805fe6bc..28f6e3076 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt @@ -19,6 +19,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.getCompatColor import io.reactivex.Completable +import kotlinx.coroutines.flow.first import kotlinx.coroutines.rx2.rxCompletable import kotlinx.coroutines.rx2.rxSingle import javax.inject.Inject @@ -32,8 +33,8 @@ class MessageWork @Inject constructor( ) : Work { override fun create(student: Student, semester: Semester): Completable { - return rxSingle { messageRepository.getMessages(student, semester, RECEIVED, true, preferencesRepository.isNotificationsEnable) } - .flatMap { rxSingle { messageRepository.getNotNotifiedMessages(student) } } + return rxSingle { messageRepository.getMessages(student, semester, RECEIVED, true, preferencesRepository.isNotificationsEnable).waitForResult() } + .flatMap { rxSingle { messageRepository.getNotNotifiedMessages(student).first() } } .flatMapCompletable { if (it.isNotEmpty()) notify(it) rxCompletable { messageRepository.updateMessages(it.onEach { message -> message.isNotified = true }) } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt index f33c64023..029c9f98a 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt @@ -18,6 +18,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.getCompatColor import io.reactivex.Completable +import kotlinx.coroutines.flow.first import kotlinx.coroutines.rx2.rxCompletable import kotlinx.coroutines.rx2.rxSingle import javax.inject.Inject @@ -31,8 +32,8 @@ class NoteWork @Inject constructor( ) : Work { override fun create(student: Student, semester: Semester): Completable { - return rxSingle { noteRepository.getNotes(student, semester, true, preferencesRepository.isNotificationsEnable) } - .flatMap { rxSingle { noteRepository.getNotNotifiedNotes(student) } } + return rxSingle { noteRepository.getNotes(student, semester, true, preferencesRepository.isNotificationsEnable).waitForResult() } + .flatMap { rxSingle { noteRepository.getNotNotifiedNotes(student).first() } } .flatMapCompletable { if (it.isNotEmpty()) notify(it) rxCompletable { noteRepository.updateNotes(it.onEach { note -> note.isNotified = true }) } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt index 704150981..2a53e51ba 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt @@ -15,10 +15,11 @@ class RecipientWork @Inject constructor( ) : Work { override fun create(student: Student, semester: Semester): Completable { - return rxSingle { reportingUnitRepository.getReportingUnits(student, true) } + return rxSingle { reportingUnitRepository.refreshReportingUnits(student) } + .flatMap { rxSingle { reportingUnitRepository.getReportingUnits(student) } } .flatMapCompletable { units -> Completable.mergeDelayError(units.map { - rxCompletable { recipientRepository.getRecipients(student, 2, it, true) } + rxCompletable { recipientRepository.refreshRecipients(student, 2, it) } }) } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt index f3ebf9eea..9954db314 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt @@ -10,6 +10,6 @@ import javax.inject.Inject class TeacherWork @Inject constructor(private val teacherRepository: TeacherRepository) : Work { override fun create(student: Student, semester: Semester): Completable { - return rxCompletable { teacherRepository.getTeachers(student, semester, true) } + return rxCompletable { teacherRepository.getTeachers(student, semester, true).waitForResult() } } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt index 7d6438d7a..f77018569 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt @@ -13,6 +13,6 @@ import javax.inject.Inject class TimetableWork @Inject constructor(private val timetableRepository: TimetableRepository) : Work { override fun create(student: Student, semester: Semester): Completable { - return rxCompletable { timetableRepository.getTimetable(student, semester, now().monday, now().sunday, true) } + return rxCompletable { timetableRepository.getTimetable(student, semester, now().monday, now().sunday, true).waitForResult() } } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/Work.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/Work.kt index 1601a103b..8dc0b98d2 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/Work.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/Work.kt @@ -1,11 +1,19 @@ package io.github.wulkanowy.services.sync.works +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.reactivex.Completable +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.takeWhile interface Work { fun create(student: Student, semester: Semester): Completable -} + suspend fun Flow>.waitForResult() = takeWhile { + it.status == Status.LOADING + }.collect() +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt index d9dbc362b..b0fc0f6bc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt @@ -1,24 +1,39 @@ package io.github.wulkanowy.ui.base +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.utils.SchedulersProvider -import io.reactivex.Completable +import io.github.wulkanowy.utils.flowWithResource import io.reactivex.disposables.CompositeDisposable -import kotlinx.coroutines.rx2.rxCompletable -import kotlinx.coroutines.rx2.rxSingle +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import timber.log.Timber +import kotlin.coroutines.CoroutineContext open class BasePresenter( protected val errorHandler: ErrorHandler, protected val studentRepository: StudentRepository, protected val schedulers: SchedulersProvider -) { +) : CoroutineScope { + private var job: Job = Job() + + private val jobs = mutableMapOf() + + override val coroutineContext: CoroutineContext + get() = Dispatchers.Main + job + + @Deprecated("Use flow instead :)") val disposable = CompositeDisposable() var view: T? = null open fun onAttachView(view: T) { + job = Job() this.view = view errorHandler.apply { showErrorMessage = view::showError @@ -28,30 +43,48 @@ open class BasePresenter( } fun onExpiredLoginSelected() { - Timber.i("Attempt to switch the student after the session expires") - disposable.add(rxSingle { studentRepository.getCurrentStudent(false) } - .flatMapCompletable { rxCompletable { studentRepository.logoutStudent(it) } } - .andThen(rxSingle { studentRepository.getSavedStudents(false) }) - .flatMapCompletable { - if (it.isNotEmpty()) { - Timber.i("Switching current student") - rxCompletable { studentRepository.switchStudent(it[0]) } - } else Completable.complete() + flowWithResource { + val student = studentRepository.getCurrentStudent(false) + studentRepository.logoutStudent(student) + + val students = studentRepository.getSavedStudents(false) + if (students.isNotEmpty()) { + Timber.i("Switching current student") + studentRepository.switchStudent(students[0]) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - Timber.i("Switch student result: Open login view") - view?.openClearLoginView() - }, { - Timber.i("Switch student result: An exception occurred") - errorHandler.dispatch(it) - })) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Attempt to switch the student after the session expires") + Status.SUCCESS -> { + Timber.i("Switch student result: Open login view") + view?.openClearLoginView() + } + Status.ERROR -> { + Timber.i("Switch student result: An exception occurred") + errorHandler.dispatch(it.error!!) + } + } + }.launch("expired") + } + + fun Flow.launch(individualJobTag: String = "load"): Job { + jobs[individualJobTag]?.cancel() + val job = launchIn(this@BasePresenter) + jobs[individualJobTag] = job + Timber.d("Job $individualJobTag launched in ${this@BasePresenter.javaClass.simpleName}: $job") + return job + } + + fun cancelJobs(vararg names: String) { + names.forEach { + jobs[it]?.cancel() + } } open fun onDetachView() { view = null disposable.clear() + job.cancel() errorHandler.clear() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt index c88e4d87d..946e661b1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt @@ -24,10 +24,10 @@ open class ErrorHandler @Inject constructor(protected val resources: Resources, } protected open fun proceed(error: Throwable) { + showErrorMessage(resources.getString(error), error) when (error) { is ScramblerException, is BadCredentialsException -> onSessionExpired() is NoCurrentStudentException -> onNoCurrentStudent() - else -> showErrorMessage(resources.getString(error), error) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt index ae149fa14..c2238de7c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt @@ -1,12 +1,14 @@ package io.github.wulkanowy.ui.modules.about.contributor +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.pojos.Contributor import io.github.wulkanowy.data.repositories.appcreator.AppCreatorRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.SchedulersProvider -import kotlinx.coroutines.rx2.rxSingle +import io.github.wulkanowy.utils.flowWithResource +import kotlinx.coroutines.flow.onEach import javax.inject.Inject class ContributorPresenter @Inject constructor( @@ -31,10 +33,15 @@ class ContributorPresenter @Inject constructor( } private fun loadData() { - disposable.add(rxSingle { appCreatorRepository.getAppCreators() } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { view?.showProgress(false) } - .subscribe({ view?.run { updateData(it) } }, { errorHandler.dispatch(it) })) + flowWithResource { appCreatorRepository.getAppCreators() }.onEach { + when (it.status) { + Status.LOADING -> view?.showProgress(true) + Status.SUCCESS -> view?.run { + showProgress(false) + updateData(it.data!!) + } + Status.ERROR -> errorHandler.dispatch(it.error!!) + } + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseAdapter.kt index 07025c09f..6ae06bbe7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicenseAdapter.kt @@ -24,7 +24,7 @@ class LicenseAdapter @Inject constructor() : RecyclerView.Adapter(errorHandler, studentRepository, schedulers) { @@ -21,14 +28,22 @@ class LicensePresenter @Inject constructor( } fun onItemSelected(library: Library) { - view?.run { library.license?.licenseDescription?.let { openLicense(it) } } + view?.run { library.licenses?.firstOrNull()?.licenseDescription?.let { openLicense(it) } } } private fun loadData() { - disposable.add(Single.fromCallable { view?.appLibraries.orEmpty() } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnEvent { _, _ -> view?.showProgress(false) } - .subscribe({ view?.run { updateData(it) } }, { errorHandler.dispatch(it) })) + flowWithResource { + withContext(dispatchers.backgroundThread) { + view?.appLibraries.orEmpty() + } + }.onEach { + when (it.status) { + Status.LOADING -> Timber.d("License data load started") + Status.SUCCESS -> view?.updateData(it.data!!) + Status.ERROR -> errorHandler.dispatch(it.error!!) + } + }.afterLoading { + view?.showProgress(false) + }.launch() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt index e1ec23a12..50df763aa 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt @@ -1,11 +1,13 @@ package io.github.wulkanowy.ui.modules.about.logviewer +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.logger.LoggerRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.SchedulersProvider -import kotlinx.coroutines.rx2.rxSingle +import io.github.wulkanowy.utils.flowWithResource +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -23,16 +25,19 @@ class LogViewerPresenter @Inject constructor( } fun onShareLogsSelected(): Boolean { - disposable.add(rxSingle { loggerRepository.getLogFiles() } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ files -> - Timber.i("Loading logs files result: ${files.joinToString { it.name }}") - view?.shareLogs(files) - }, { - Timber.i("Loading logs files result: An exception occurred") - errorHandler.dispatch(it) - })) + flowWithResource { loggerRepository.getLogFiles() }.onEach { + when (it.status) { + Status.LOADING -> Timber.d("Loading logs files started") + Status.SUCCESS -> { + Timber.i("Loading logs files result: ${it.data!!.joinToString { file -> file.name }}") + view?.shareLogs(it.data) + } + Status.ERROR -> { + Timber.i("Loading logs files result: An exception occurred") + errorHandler.dispatch(it.error!!) + } + } + }.launch("share") return true } @@ -41,15 +46,18 @@ class LogViewerPresenter @Inject constructor( } private fun loadLogFile() { - disposable.add(rxSingle { loggerRepository.getLastLogLines() } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - Timber.i("Loading last log file result: load ${it.size} lines") - view?.setLines(it) - }, { - Timber.i("Loading last log file result: An exception occurred") - errorHandler.dispatch(it) - })) + flowWithResource { loggerRepository.getLastLogLines() }.onEach { + when (it.status) { + Status.LOADING -> Timber.d("Loading last log file started") + Status.SUCCESS -> { + Timber.i("Loading last log file result: load ${it.data!!.size} lines") + view?.setLines(it.data) + } + Status.ERROR -> { + Timber.i("Loading last log file result: An exception occurred") + errorHandler.dispatch(it.error!!) + } + } + }.launch("file") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt index 1dd32cf90..402394170 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt @@ -1,14 +1,15 @@ package io.github.wulkanowy.ui.modules.account +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.SchedulersProvider -import io.reactivex.Single -import kotlinx.coroutines.rx2.rxCompletable -import kotlinx.coroutines.rx2.rxSingle +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResource +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -37,20 +38,20 @@ class AccountPresenter @Inject constructor( } fun onLogoutConfirm() { - Timber.i("Attempt to logout current user ") - disposable.add(rxSingle { studentRepository.getCurrentStudent(false) } - .flatMapCompletable { rxCompletable { studentRepository.logoutStudent(it) } } - .andThen(rxSingle { studentRepository.getSavedStudents(false) }) - .flatMap { - if (it.isNotEmpty()) rxCompletable { studentRepository.switchStudent(it[0]) }.toSingle { it } - else Single.just(it) + flowWithResource { + val student = studentRepository.getCurrentStudent(false) + studentRepository.logoutStudent(student) + + val students = studentRepository.getSavedStudents(false) + if (students.isNotEmpty()) { + studentRepository.switchStudent(students[0]) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { view?.dismissView() } - .subscribe({ - view?.apply { - if (it.isEmpty()) { + students + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Attempt to logout current user ") + Status.SUCCESS -> view?.run { + if (it.data!!.isEmpty()) { Timber.i("Logout result: Open login view") syncManager.stopSyncWorker() openClearLoginView() @@ -59,30 +60,35 @@ class AccountPresenter @Inject constructor( recreateMainView() } } - }, { - Timber.i("Logout result: An exception occurred") - errorHandler.dispatch(it) - })) + Status.ERROR -> { + Timber.i("Logout result: An exception occurred") + errorHandler.dispatch(it.error!!) + } + } + }.afterLoading { + view?.dismissView() + }.launch("logout") } fun onItemSelected(student: Student) { Timber.i("Select student item ${student.id}") if (student.isCurrent) { view?.dismissView() - } else { - Timber.i("Attempt to change a student") - disposable.add(rxSingle { studentRepository.switchStudent(student) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { view?.dismissView() } - .subscribe({ + } else flowWithResource { studentRepository.switchStudent(student) }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Attempt to change a student") + Status.SUCCESS -> { Timber.i("Change a student result: Success") view?.recreateMainView() - }, { + } + Status.ERROR -> { Timber.i("Change a student result: An exception occurred") - errorHandler.dispatch(it) - })) - } + errorHandler.dispatch(it.error!!) + } + } + }.afterLoading { + view?.dismissView() + }.launch("switch") } private fun createAccountItems(items: List): List> { @@ -94,17 +100,18 @@ class AccountPresenter @Inject constructor( } private fun loadData() { - Timber.i("Loading account data started") - disposable.add(rxSingle { studentRepository.getSavedStudents(false) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .map { createAccountItems(it) } - .subscribe({ - Timber.i("Loading account result: Success") - view?.updateData(it) - }, { - Timber.i("Loading account result: An exception occurred") - errorHandler.dispatch(it) - })) + flowWithResource { studentRepository.getSavedStudents(false) }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading account data started") + Status.SUCCESS -> { + Timber.i("Loading account result: Success") + view?.updateData(createAccountItems(it.data!!)) + } + Status.ERROR -> { + Timber.i("Loading account result: An exception occurred") + errorHandler.dispatch(it.error!!) + } + } + }.launch() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt index f177d0191..0645c7a4f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.ui.modules.attendance import android.annotation.SuppressLint +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.repositories.attendance.AttendanceRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository @@ -10,13 +11,18 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResource +import io.github.wulkanowy.utils.flowWithResourceIn import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday 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.rx2.rxSingle +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onEach import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.ofEpochDay @@ -168,100 +174,93 @@ class AttendancePresenter @Inject constructor( } private fun setBaseDateOnHolidays() { - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { rxSingle { semesterRepository.getCurrentSemester(it) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) - currentDate = baseDate - reloadNavigation() - }) { - Timber.i("Loading semester result: An exception occurred") - }) + flow { + val student = studentRepository.getCurrentStudent() + emit(semesterRepository.getCurrentSemester(student)) + }.catch { + Timber.i("Loading semester result: An exception occurred") + }.onEach { + baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) + currentDate = baseDate + reloadNavigation() + }.launch("holidays") } private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { Timber.i("Loading attendance data started") currentDate = date - disposable.apply { - clear() - add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { student -> - rxSingle { semesterRepository.getCurrentSemester(student) }.flatMap { semester -> - rxSingle { attendanceRepository.getAttendance(student, semester, date, date, forceRefresh) } - } - } - .map { list -> - if (prefRepository.isShowPresent) list - else list.filter { !it.presence } - } - .map { items -> items.sortedBy { it.number } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - } - } - .subscribe({ + + flowWithResourceIn { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + attendanceRepository.getAttendance(student, semester, date, date, forceRefresh) + }.onEach { + when (it.status) { + Status.LOADING -> view?.showExcuseButton(false) + Status.SUCCESS -> { Timber.i("Loading attendance result: Success") view?.apply { - updateData(it) - showEmpty(it.isEmpty()) + updateData(it.data!!.let { items -> + if (prefRepository.isShowPresent) items + else items.filter { item -> !item.presence } + }.sortedBy { item -> item.number }) + showEmpty(it.data.isEmpty()) showErrorView(false) - showContent(it.isNotEmpty()) - showExcuseButton(it.any { item -> item.excusable }) + showContent(it.data.isNotEmpty()) + showExcuseButton(it.data.any { item -> item.excusable }) } analytics.logEvent( "load_data", "type" to "attendance", - "items" to it.size, - "force_refresh" to forceRefresh + "items" to it.data!!.size ) - }) { - Timber.i("Loading attendance result: An exception occurred") - errorHandler.dispatch(it) } - ) - } + Status.ERROR -> { + Timber.i("Loading attendance result: An exception occurred") + errorHandler.dispatch(it.error!!) + } + } + }.afterLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } + }.launch() } private fun excuseAbsence(reason: String?, toExcuseList: List) { - Timber.i("Excusing absence started") - disposable.apply { - add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { student -> - rxSingle { semesterRepository.getCurrentSemester(student) }.flatMap { semester -> - rxSingle { attendanceRepository.excuseForAbsence(student, semester, toExcuseList, reason) } - } + flowWithResource { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + attendanceRepository.excuseForAbsence(student, semester, toExcuseList, reason) + }.onEach { + when (it.status) { + Status.LOADING -> view?.run { + Timber.i("Excusing absence started") + showProgress(true) + showContent(false) + showExcuseButton(false) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { - view?.apply { - showProgress(true) - showContent(false) - showExcuseButton(false) - } - } - .subscribe({ + Status.SUCCESS -> { Timber.i("Excusing for absence result: Success") analytics.logEvent("excuse_absence", "items" to attendanceToExcuseList.size) attendanceToExcuseList.clear() - view?.apply { + view?.run { showExcuseButton(false) showMessage(excuseSuccessString) + showContent(true) + showProgress(false) } - loadData(currentDate, true) - }) { + loadData(currentDate, forceRefresh = true) + } + Status.ERROR -> { Timber.i("Excusing for absence result: An exception occurred") - view?.showProgress(false) - errorHandler.dispatch(it) - }) - } + errorHandler.dispatch(it.error!!) + loadData(currentDate) + } + } + }.launch("excuse") } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt index f694a8d0c..5d16b3143 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.attendance.summary +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Subject import io.github.wulkanowy.data.repositories.attendancesummary.AttendanceSummaryRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository @@ -9,7 +10,9 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider -import kotlinx.coroutines.rx2.rxSingle +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResourceIn +import kotlinx.coroutines.flow.onEach import org.threeten.bp.Month import timber.log.Timber import javax.inject.Inject @@ -73,46 +76,43 @@ class AttendanceSummaryPresenter @Inject constructor( } private fun loadData(subjectId: Int, forceRefresh: Boolean = false) { - Timber.i("Loading attendance summary data started") currentSubjectId = subjectId - disposable.apply { - clear() - add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { student -> - rxSingle { semesterRepository.getCurrentSemester(student) }.flatMap { - rxSingle { attendanceSummaryRepository.getAttendanceSummary(student, it, subjectId, forceRefresh) } - } - } - .map { items -> items.sortedByDescending { if (it.month.value <= Month.JUNE.value) it.month.value + 12 else it.month.value } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - } - } - .subscribe({ + + flowWithResourceIn { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + attendanceSummaryRepository.getAttendanceSummary(student, semester, subjectId, forceRefresh) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading attendance summary data started") + Status.SUCCESS -> { Timber.i("Loading attendance summary result: Success") view?.apply { - showEmpty(it.isEmpty()) - showContent(it.isNotEmpty()) - updateDataSet(it) + showEmpty(it.data!!.isEmpty()) + showContent(it.data.isNotEmpty()) + updateDataSet(it.data.sortedByDescending { item -> + if (item.month.value <= Month.JUNE.value) item.month.value + 12 else item.month.value + }) } analytics.logEvent( "load_data", "type" to "attendance_summary", - "items" to it.size, - "force_refresh" to forceRefresh, + "items" to it.data!!.size, "item_id" to subjectId ) - }) { - Timber.i("Loading attendance summary result: An exception occurred") - errorHandler.dispatch(it) } - ) - } + Status.ERROR -> { + Timber.i("Loading attendance summary result: An exception occurred") + errorHandler.dispatch(it.error!!) + } + } + }.afterLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } + }.launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -127,27 +127,27 @@ class AttendanceSummaryPresenter @Inject constructor( } private fun loadSubjects() { - Timber.i("Loading attendance summary subjects started") - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { student -> - rxSingle { semesterRepository.getCurrentSemester(student) }.flatMap { semester -> - rxSingle { subjectRepository.getSubjects(student, semester) } + flowWithResourceIn { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + subjectRepository.getSubjects(student, semester) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading attendance summary subjects started") + Status.SUCCESS -> { + subjects = it.data!! + + Timber.i("Loading attendance summary subjects result: Success") + view?.run { + view?.updateSubjects(ArrayList(it.data.map { subject -> subject.name })) + showSubjects(true) + } + } + Status.ERROR -> { + Timber.i("Loading attendance summary subjects result: An exception occurred") + errorHandler.dispatch(it.error!!) } } - .doOnSuccess { subjects = it } - .map { ArrayList(it.map { subject -> subject.name }) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - Timber.i("Loading attendance summary subjects result: Success") - view?.run { - view?.updateSubjects(it) - showSubjects(true) - } - }, { - Timber.i("Loading attendance summary subjects result: An exception occurred") - errorHandler.dispatch(it) - }) - ) + }.launch("subjects") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt index 844fb263a..21f7ae6e0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.exam +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.repositories.exam.ExamRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository @@ -8,13 +9,17 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider -import io.github.wulkanowy.utils.sunday +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.rx2.rxSingle +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onEach import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.ofEpochDay @@ -90,59 +95,54 @@ class ExamPresenter @Inject constructor( } private fun setBaseDateOnHolidays() { - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { rxSingle { semesterRepository.getCurrentSemester(it) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) - currentDate = baseDate - reloadNavigation() - }) { - Timber.i("Loading semester result: An exception occurred") - }) + flow { + val student = studentRepository.getCurrentStudent() + emit(semesterRepository.getCurrentSemester(student)) + }.catch { + Timber.i("Loading semester result: An exception occurred") + }.onEach { + baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) + currentDate = baseDate + reloadNavigation() + }.launch("holidays") } private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { - Timber.i("Loading exam data started") currentDate = date - disposable.apply { - clear() - add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { student -> - rxSingle { semesterRepository.getCurrentSemester(student) }.flatMap { semester -> - rxSingle { examRepository.getExams(student, semester, currentDate.monday, currentDate.sunday, forceRefresh) } - } - } - .map { createExamItems(it) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - } - } - .subscribe({ + + flowWithResourceIn { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + examRepository.getExams(student, semester, currentDate.monday, currentDate.sunday, forceRefresh) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading exam data started") + Status.SUCCESS -> { Timber.i("Loading exam result: Success") view?.apply { - updateData(it) - showEmpty(it.isEmpty()) + updateData(createExamItems(it.data!!)) + showEmpty(it.data.isEmpty()) showErrorView(false) - showContent(it.isNotEmpty()) + showContent(it.data.isNotEmpty()) } analytics.logEvent( "load_data", "type" to "exam", - "items" to it.size, - "force_refresh" to forceRefresh + "items" to it.data!!.size ) - }) { + } + Status.ERROR -> { Timber.i("Loading exam result: An exception occurred") - errorHandler.dispatch(it) - }) - } + errorHandler.dispatch(it.error!!) + } + } + }.afterLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } + }.launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt index ab6c507bb..5d0a310f2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt @@ -1,5 +1,7 @@ package io.github.wulkanowy.ui.modules.grade +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Semester @@ -13,8 +15,17 @@ 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.changeModifier +import io.github.wulkanowy.utils.flowWithResourceIn +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flatMapConcat +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import javax.inject.Inject +@OptIn(FlowPreview::class) class GradeAverageProvider @Inject constructor( private val semesterRepository: SemesterRepository, private val gradeRepository: GradeRepository, @@ -25,63 +36,64 @@ class GradeAverageProvider @Inject constructor( private val minusModifier get() = preferencesRepository.gradeMinusModifier - suspend fun getGradesDetailsWithAverage(student: Student, semesterId: Int, forceRefresh: Boolean = false): List { - return semesterRepository.getSemesters(student).let { semesters -> - when (preferencesRepository.gradeAverageMode) { - ONE_SEMESTER -> getSemesterDetailsWithAverage(student, semesters.single { it.semesterId == semesterId }, forceRefresh) - BOTH_SEMESTERS -> calculateBothSemestersAverage(student, semesters, semesterId, forceRefresh) - ALL_YEAR -> calculateAllYearAverage(student, semesters, semesterId, forceRefresh) - } - } - } + fun getGradesDetailsWithAverage(student: Student, semesterId: Int, forceRefresh: Boolean) = flowWithResourceIn { + val semesters = semesterRepository.getSemesters(student) - private suspend fun calculateBothSemestersAverage(student: Student, semesters: List, semesterId: Int, forceRefresh: Boolean): List { + when (preferencesRepository.gradeAverageMode) { + ONE_SEMESTER -> getSemesterDetailsWithAverage(student, semesters.single { it.semesterId == semesterId }, forceRefresh) + BOTH_SEMESTERS -> calculateBothSemestersAverage(student, semesters, semesterId, forceRefresh) + ALL_YEAR -> calculateAllYearAverage(student, semesters, semesterId, forceRefresh) + } + }.distinctUntilChanged() + + private fun calculateBothSemestersAverage(student: Student, semesters: List, semesterId: Int, forceRefresh: Boolean): Flow>> { val selectedSemester = semesters.single { it.semesterId == semesterId } val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 } - return getSemesterDetailsWithAverage(student, selectedSemester, forceRefresh).let { selectedDetails -> - val isAnyAverage = selectedDetails.any { it.average != .0 } + return getSemesterDetailsWithAverage(student, selectedSemester, forceRefresh).flatMapConcat { selectedDetails -> + val isAnyAverage = selectedDetails.data.orEmpty().any { it.average != .0 } if (selectedSemester != firstSemester) { - getSemesterDetailsWithAverage(student, firstSemester, forceRefresh).let { secondDetails -> - selectedDetails.map { selected -> - val second = secondDetails.singleOrNull { it.subject == selected.subject } + getSemesterDetailsWithAverage(student, firstSemester, forceRefresh).map { secondDetails -> + secondDetails.copy(data = selectedDetails.data?.map { selected -> + val second = secondDetails.data.orEmpty().singleOrNull { it.subject == selected.subject } selected.copy(average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) { val selectedGrades = selected.grades.updateModifiers(student).calcAverage() (selectedGrades + (second?.grades?.updateModifiers(student)?.calcAverage() ?: selectedGrades)) / 2 } else (selected.average + (second?.average ?: selected.average)) / 2) - } - } - } else selectedDetails + }) + }.filter { it.status != Status.LOADING }.filter { it.data != null } + } else flowOf(selectedDetails) } } - private suspend fun calculateAllYearAverage(student: Student, semesters: List, semesterId: Int, forceRefresh: Boolean): List { + private fun calculateAllYearAverage(student: Student, semesters: List, semesterId: Int, forceRefresh: Boolean): Flow>> { val selectedSemester = semesters.single { it.semesterId == semesterId } val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 } - return getSemesterDetailsWithAverage(student, selectedSemester, forceRefresh).let { selectedDetails -> - val isAnyAverage = selectedDetails.any { it.average != .0 } + return getSemesterDetailsWithAverage(student, selectedSemester, forceRefresh).flatMapConcat { selectedDetails -> + val isAnyAverage = selectedDetails.data.orEmpty().any { it.average != .0 } if (selectedSemester != firstSemester) { - getSemesterDetailsWithAverage(student, firstSemester, forceRefresh).let { secondDetails -> - selectedDetails.map { selected -> - val second = secondDetails.singleOrNull { it.subject == selected.subject } + getSemesterDetailsWithAverage(student, firstSemester, forceRefresh).map { secondDetails -> + secondDetails.copy(data = selectedDetails.data?.map { selected -> + val second = secondDetails.data.orEmpty().singleOrNull { it.subject == selected.subject } selected.copy(average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) { (selected.grades.updateModifiers(student) + second?.grades?.updateModifiers(student).orEmpty()).calcAverage() } else selected.average) - } - } - } else selectedDetails + }) + }.filter { it.status != Status.LOADING }.filter { it.data != null } + } else flowOf(selectedDetails) } } - private suspend fun getSemesterDetailsWithAverage(student: Student, semester: Semester, forceRefresh: Boolean): List { - return gradeRepository.getGrades(student, semester, forceRefresh).let { (details, summaries) -> - val isAnyAverage = summaries.any { it.average != .0 } - val allGrades = details.groupBy { it.subject } + private fun getSemesterDetailsWithAverage(student: Student, semester: Semester, forceRefresh: Boolean): Flow>> { + return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh).map { res -> + val (details, summaries) = res.data ?: null to null + val isAnyAverage = summaries.orEmpty().any { it.average != .0 } + val allGrades = details.orEmpty().groupBy { it.subject } - summaries.emulateEmptySummaries(student, semester, allGrades.toList(), isAnyAverage).map { summary -> + Resource(res.status, summaries?.emulateEmptySummaries(student, semester, allGrades.toList(), isAnyAverage)?.map { summary -> val grades = allGrades[summary.subject].orEmpty() GradeDetailsWithAverage( subject = summary.subject, @@ -92,7 +104,7 @@ class GradeAverageProvider @Inject constructor( summary = summary, grades = grades ) - } + }, res.error) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt index 65f6598d1..9dc39d85c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt @@ -1,5 +1,6 @@ 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.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.student.StudentRepository @@ -7,10 +8,11 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.getCurrentOrLast -import kotlinx.coroutines.rx2.rxSingle +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.onEach import timber.log.Timber -import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject class GradePresenter @Inject constructor( @@ -99,29 +101,33 @@ class GradePresenter @Inject constructor( } private fun loadData() { - Timber.i("Loading grade data started") - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { rxSingle { semesterRepository.getSemesters(it, refreshOnNoCurrent = true) } } - .delay(200, MILLISECONDS) - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - val current = it.getCurrentOrLast() - selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex - schoolYear = current.schoolYear - semesters = it.filter { semester -> semester.diaryId == current.diaryId } - view?.setCurrentSemesterName(current.semesterName, schoolYear) + flowWithResource { + val student = studentRepository.getCurrentStudent() + delay(200) + semesterRepository.getSemesters(student, refreshOnNoCurrent = true) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading grade data started") + Status.SUCCESS -> { + val current = it.data!!.getCurrentOrLast() + selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex + schoolYear = current.schoolYear + semesters = it.data.filter { semester -> semester.diaryId == current.diaryId } + view?.setCurrentSemesterName(current.semesterName, schoolYear) - view?.run { - Timber.i("Loading grade result: Attempt load index $currentPageIndex") - loadChild(currentPageIndex) - showErrorView(false) - showSemesterSwitch(true) + view?.run { + Timber.i("Loading grade result: Attempt load index $currentPageIndex") + loadChild(currentPageIndex) + showErrorView(false) + showSemesterSwitch(true) + } } - }) { - Timber.i("Loading grade result: An exception occurred") - errorHandler.dispatch(it) - }) + Status.ERROR -> { + Timber.i("Loading grade result: An exception occurred") + errorHandler.dispatch(it.error!!) + } + } + }.launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt index a99e3a547..5845d32c1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.grade.details +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.repositories.grade.GradeRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository @@ -11,8 +12,11 @@ import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider import io.github.wulkanowy.ui.modules.grade.GradeDetailsWithAverage import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider -import kotlinx.coroutines.rx2.rxCompletable -import kotlinx.coroutines.rx2.rxSingle +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResource +import io.github.wulkanowy.utils.flowWithResourceIn +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -41,7 +45,9 @@ class GradeDetailsPresenter @Inject constructor( fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) { currentSemesterId = semesterId + loadData(semesterId, forceRefresh) + if (!forceRefresh) view?.showErrorView(false) } fun onGradeItemSelected(grade: Grade, position: Int) { @@ -63,24 +69,24 @@ class GradeDetailsPresenter @Inject constructor( } fun onMarkAsReadSelected(): Boolean { - Timber.i("Select mark grades as read") - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { rxSingle { semesterRepository.getSemesters(it) } } - .flatMap { rxSingle { gradeRepository.getUnreadGrades(it.first { item -> item.semesterId == currentSemesterId }) } } - .map { it.map { grade -> grade.apply { isRead = true } } } - .flatMapCompletable { - Timber.i("Mark as read ${it.size} grades") - rxCompletable { gradeRepository.updateGrades(it) } + flowWithResource { + val student = studentRepository.getCurrentStudent() + val semesters = semesterRepository.getSemesters(student) + val semester = semesters.first { item -> item.semesterId == currentSemesterId } + val unreadGrades = gradeRepository.getUnreadGrades(semester).first() + + Timber.i("Mark as read ${unreadGrades.size} grades") + gradeRepository.updateGrades(unreadGrades.map { it.apply { isRead = true } }) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Select mark grades as read") + Status.SUCCESS -> Timber.i("Mark as read result: Success") + Status.ERROR -> { + Timber.i("Mark as read result: An exception occurred") + errorHandler.dispatch(it.error!!) + } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - Timber.i("Mark as read result: Success") - loadData(currentSemesterId, false) - }, { - Timber.i("Mark as read result: An exception occurred") - errorHandler.dispatch(it) - })) + }.launch("mark") return true } @@ -119,7 +125,7 @@ class GradeDetailsPresenter @Inject constructor( showEmpty(false) clearView() } - disposable.clear() + cancelJobs("load") } fun updateMarkAsDoneButton() { @@ -127,43 +133,46 @@ class GradeDetailsPresenter @Inject constructor( } private fun loadData(semesterId: Int, forceRefresh: Boolean) { - Timber.i("Loading grade details data started") - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { rxSingle { averageProvider.getGradesDetailsWithAverage(it, semesterId, forceRefresh) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded(semesterId) - } - } - .subscribe({ grades -> - Timber.i("Loading grade details result: Success") - newGradesAmount = grades.sumBy { it.grades.sumBy { grade -> if (!grade.isRead) 1 else 0 } } - updateMarkAsDoneButton() - view?.run { - showEmpty(grades.isEmpty()) - showErrorView(false) - showContent(grades.isNotEmpty()) - updateData( - data = createGradeItems(grades), - isGradeExpandable = preferencesRepository.isGradeExpandable, - gradeColorTheme = preferencesRepository.gradeColorTheme + flowWithResourceIn { + val student = studentRepository.getCurrentStudent() + averageProvider.getGradesDetailsWithAverage(student, semesterId, forceRefresh) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading grade details data started") + Status.SUCCESS -> { + Timber.i("Loading grade details result: Success") + newGradesAmount = it.data!!.sumBy { item -> item.grades.sumBy { grade -> if (!grade.isRead) 1 else 0 } } + updateMarkAsDoneButton() + val items = createGradeItems(it.data) + view?.run { + showEmpty(items.isEmpty()) + showErrorView(false) + showContent(items.isNotEmpty()) + updateData( + data = items, + isGradeExpandable = preferencesRepository.isGradeExpandable, + gradeColorTheme = preferencesRepository.gradeColorTheme + ) + } + analytics.logEvent( + "load_data", + "type" to "grade_details", + "items" to it.data.size ) } - analytics.logEvent( - "load_data", - "type" to "grade_details", - "items" to grades.size, - "force_refresh" to forceRefresh - ) - }) { - Timber.i("Loading grade details result: An exception occurred") - errorHandler.dispatch(it) - }) + Status.ERROR -> { + Timber.i("Loading grade details result: An exception occurred") + errorHandler.dispatch(it.error!!) + } + } + }.afterLoading { + view?.run { + showRefresh(false) + showProgress(false) + enableSwipe(true) + notifyParentDataLoaded(semesterId) + } + }.launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -197,15 +206,15 @@ class GradeDetailsPresenter @Inject constructor( } private fun updateGrade(grade: Grade) { - Timber.i("Attempt to update grade ${grade.id}") - disposable.add(rxCompletable { gradeRepository.updateGrade(grade) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - Timber.i("Update grade result: Success") - }) { error -> - Timber.i("Update grade result: An exception occurred") - errorHandler.dispatch(error) - }) + flowWithResource { gradeRepository.updateGrade(grade) }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Attempt to update grade ${grade.id}") + Status.SUCCESS -> Timber.i("Update grade result: Success") + Status.ERROR -> { + Timber.i("Update grade result: An exception occurred") + errorHandler.dispatch(it.error!!) + } + } + }.launch("update") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt index b2c56ed22..eb6ae8433 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.grade.statistics +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Subject import io.github.wulkanowy.data.repositories.gradestatistics.GradeStatisticsRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository @@ -10,7 +11,9 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider -import kotlinx.coroutines.rx2.rxSingle +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResourceIn +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -46,6 +49,7 @@ class GradeStatisticsPresenter @Inject constructor( fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) { currentSemesterId = semesterId loadSubjects() + if (!forceRefresh) view?.showErrorView(false) loadDataByType(semesterId, currentSubjectName, currentType, forceRefresh) } @@ -65,7 +69,7 @@ class GradeStatisticsPresenter @Inject constructor( showEmpty(false) clearView() } - disposable.clear() + cancelJobs("load") } fun onSwipeRefresh() { @@ -103,7 +107,7 @@ class GradeStatisticsPresenter @Inject constructor( fun onTypeChange() { val type = view?.currentType ?: ViewType.POINTS Timber.i("Select grade stats semester: $type") - disposable.clear() + cancelJobs("load") view?.run { showContent(false) showProgress(true) @@ -116,77 +120,77 @@ class GradeStatisticsPresenter @Inject constructor( } private fun loadSubjects() { - Timber.i("Loading grade stats subjects started") - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { student -> - rxSingle { semesterRepository.getCurrentSemester(student) }.flatMap { semester -> - rxSingle { subjectRepository.getSubjects(student, semester) } + flowWithResourceIn { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + subjectRepository.getSubjects(student, semester) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading grade stats subjects started") + Status.SUCCESS -> { + subjects = it.data!! + + Timber.i("Loading grade stats subjects result: Success") + view?.run { + view?.updateSubjects(ArrayList(it.data.map { subject -> subject.name })) + showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) + } + } + Status.ERROR -> { + Timber.i("Loading grade stats subjects result: An exception occurred") + errorHandler.dispatch(it.error!!) } } - .doOnSuccess { subjects = it } - .map { ArrayList(it.map { subject -> subject.name }) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - Timber.i("Loading grade stats subjects result: Success") - view?.updateSubjects(it) - }, { - Timber.i("Loading grade stats subjects result: An exception occurred") - errorHandler.dispatch(it) - }) - ) + }.launch("subjects") } private fun loadDataByType(semesterId: Int, subjectName: String, type: ViewType, forceRefresh: Boolean = false) { currentSubjectName = if (preferencesRepository.showAllSubjectsOnStatisticsList) "Wszystkie" else subjectName currentType = type - Timber.i("Loading grade stats data started") - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { student -> - rxSingle { semesterRepository.getSemesters(student) }.flatMap { semesters -> - val semester = semesters.first { item -> item.semesterId == semesterId } + flowWithResourceIn { + val student = studentRepository.getCurrentStudent() + val semesters = semesterRepository.getSemesters(student) + val semester = semesters.first { item -> item.semesterId == semesterId } - rxSingle { - with(gradeStatisticsRepository) { - when (type) { - ViewType.SEMESTER -> getGradesStatistics(student, semester, currentSubjectName, true, forceRefresh) - ViewType.PARTIAL -> getGradesStatistics(student, semester, currentSubjectName, false, forceRefresh) - ViewType.POINTS -> getGradesPointsStatistics(student, semester, currentSubjectName, forceRefresh) - } - } + with(gradeStatisticsRepository) { + when (type) { + ViewType.SEMESTER -> getGradesStatistics(student, semester, currentSubjectName, true, forceRefresh) + ViewType.PARTIAL -> getGradesStatistics(student, semester, currentSubjectName, false, forceRefresh) + ViewType.POINTS -> getGradesPointsStatistics(student, semester, currentSubjectName, forceRefresh) + } + } + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading grade stats data started") + Status.SUCCESS -> { + Timber.i("Loading grade stats result: Success") + view?.run { + showEmpty(it.data!!.isEmpty()) + showContent(it.data.isNotEmpty()) + showErrorView(false) + updateData(it.data, preferencesRepository.gradeColorTheme, 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") + errorHandler.dispatch(it.error!!) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded(semesterId) - } + }.afterLoading { + view?.run { + showRefresh(false) + showProgress(false) + enableSwipe(true) + notifyParentDataLoaded(semesterId) } - .subscribe({ - Timber.i("Loading grade stats result: Success") - view?.run { - showEmpty(it.isEmpty()) - showContent(it.isNotEmpty()) - showErrorView(false) - updateData(it, preferencesRepository.gradeColorTheme, preferencesRepository.showAllSubjectsOnStatisticsList) - showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) - } - analytics.logEvent( - "load_data", - "type" to "grade_statistics", - "items" to it.size, - "force_refresh" to forceRefresh - ) - }) { - Timber.i("Loading grade stats result: An exception occurred") - errorHandler.dispatch(it) - }) + }.launch("load") } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt index 62b95d2e9..96908c3c6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.grade.summary +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter @@ -8,7 +9,9 @@ import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider import io.github.wulkanowy.ui.modules.grade.GradeDetailsWithAverage import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider -import kotlinx.coroutines.rx2.rxSingle +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResourceIn +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -30,36 +33,45 @@ class GradeSummaryPresenter @Inject constructor( fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) { Timber.i("Loading grade summary data started") - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { rxSingle { averageProvider.getGradesDetailsWithAverage(it, semesterId, forceRefresh) } } - .map { createGradeSummaryItems(it) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded(semesterId) + + loadData(semesterId, forceRefresh) + if (!forceRefresh) view?.showErrorView(false) + } + + private fun loadData(semesterId: Int, forceRefresh: Boolean) { + flowWithResourceIn { + val student = studentRepository.getCurrentStudent() + averageProvider.getGradesDetailsWithAverage(student, semesterId, forceRefresh) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading grade summary started") + Status.SUCCESS -> { + Timber.i("Loading grade summary result: Success") + view?.run { + showEmpty(it.data!!.isEmpty()) + showContent(it.data.isNotEmpty()) + showErrorView(false) + updateData(createGradeSummaryItems(it.data)) + } + analytics.logEvent( + "load_data", + "type" to "grade_summary", + "items" to it.data!!.size + ) } - }.subscribe({ - Timber.i("Loading grade summary result: Success") - view?.run { - showEmpty(it.isEmpty()) - showContent(it.isNotEmpty()) - showErrorView(false) - updateData(it) + Status.ERROR -> { + Timber.i("Loading grade summary result: An exception occurred") + errorHandler.dispatch(it.error!!) } - analytics.logEvent( - "load_data", - "type" to "grade_summary", - "items" to it.size, - "force_refresh" to forceRefresh - ) - }) { - Timber.i("Loading grade summary result: An exception occurred") - errorHandler.dispatch(it) - }) + } + }.afterLoading { + view?.run { + showRefresh(false) + showProgress(false) + enableSwipe(true) + notifyParentDataLoaded(semesterId) + } + }.launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -105,7 +117,7 @@ class GradeSummaryPresenter @Inject constructor( showEmpty(false) clearView() } - disposable.clear() + cancelJobs("load") } private fun createGradeSummaryItems(items: List): List { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt index f30529575..ee2751d71 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt @@ -70,10 +70,6 @@ class HomeworkFragment : BaseFragment(R.layout.fragment } } - fun onReloadList() { - presenter.reloadData() - } - override fun clearData() { with(homeworkAdapter) { items = emptyList() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt index fe31dfae2..612da7499 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.homework +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.repositories.homework.HomeworkRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository @@ -8,13 +9,17 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider -import io.github.wulkanowy.utils.sunday +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.rx2.rxSingle +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onEach import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate.ofEpochDay import timber.log.Timber @@ -79,64 +84,54 @@ class HomeworkPresenter @Inject constructor( } private fun setBaseDateOnHolidays() { - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { rxSingle { semesterRepository.getCurrentSemester(it) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) - currentDate = baseDate - reloadNavigation() - }) { - Timber.i("Loading semester result: An exception occurred") - }) - } - - fun reloadData() { - loadData(currentDate, false) + flow { + val student = studentRepository.getCurrentStudent() + emit(semesterRepository.getCurrentSemester(student)) + }.catch { + Timber.i("Loading semester result: An exception occurred") + }.onEach { + baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) + currentDate = baseDate + reloadNavigation() + }.launch("holidays") } private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { - Timber.i("Loading homework data started") currentDate = date - disposable.apply { - clear() - add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { student -> - rxSingle { semesterRepository.getCurrentSemester(student) }.flatMap { semester -> - rxSingle { homeworkRepository.getHomework(student, semester, currentDate, currentDate, forceRefresh) } - } - } - .map { createHomeworkItem(it) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - } - } - .subscribe({ + + flowWithResourceIn { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + homeworkRepository.getHomework(student, semester, date, date, forceRefresh) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading homework data started") + Status.SUCCESS -> { Timber.i("Loading homework result: Success") view?.apply { - updateData(it) - showEmpty(it.isEmpty()) + updateData(createHomeworkItem(it.data!!)) + showEmpty(it.data.isEmpty()) showErrorView(false) - showContent(it.isNotEmpty()) + showContent(it.data.isNotEmpty()) } analytics.logEvent( "load_data", "type" to "homework", - "items" to it.size, - "force_refresh" to forceRefresh + "items" to it.data!!.size ) - }) { + } + Status.ERROR -> { Timber.i("Loading homework result: An exception occurred") - - errorHandler.dispatch(it) - }) - } + errorHandler.dispatch(it.error!!) + } + } + }.afterLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } + }.launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt index 7b3b9821a..eb85066a7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt @@ -73,7 +73,6 @@ class HomeworkDetailsDialog : BaseDialogFragment(), Homew } override fun updateMarkAsDoneLabel(isDone: Boolean) { - (parentFragment as? HomeworkFragment)?.onReloadList() binding.homeworkDialogRead.text = view?.context?.getString(if (isDone) R.string.homework_mark_as_undone else R.string.homework_mark_as_done) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt index c0475b7c5..2ca7a1f88 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt @@ -1,5 +1,6 @@ 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.repositories.homework.HomeworkRepository import io.github.wulkanowy.data.repositories.student.StudentRepository @@ -7,7 +8,8 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider -import kotlinx.coroutines.rx2.rxSingle +import io.github.wulkanowy.utils.flowWithResource +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -26,20 +28,19 @@ class HomeworkDetailsPresenter @Inject constructor( } fun toggleDone(homework: Homework) { - Timber.i("Homework details update start") - disposable.add(rxSingle { homeworkRepository.toggleDone(homework) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - Timber.i("Homework details update: Success") - view?.run { - updateMarkAsDoneLabel(homework.isDone) + flowWithResource { homeworkRepository.toggleDone(homework) }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Homework details update start") + Status.SUCCESS -> { + Timber.i("Homework details update: Success") + view?.updateMarkAsDoneLabel(homework.isDone) + analytics.logEvent("homework_mark_as_done") + } + Status.ERROR -> { + Timber.i("Homework details update result: An exception occurred") + errorHandler.dispatch(it.error!!) } - analytics.logEvent("homework_mark_as_done") - }) { - Timber.i("Homework details update result: An exception occurred") - errorHandler.dispatch(it) } - ) + }.launch("toggle") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt index 27205a2af..8f187ac83 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.login.advanced +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.sdk.Sdk @@ -7,9 +8,10 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank -import io.reactivex.Single -import kotlinx.coroutines.rx2.rxSingle +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -126,35 +128,42 @@ class LoginAdvancedPresenter @Inject constructor( fun onSignInClick() { if (!validateCredentials()) return - disposable.add(getStudentsAppropriatesToLoginType() - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { - view?.apply { + flowWithResource { getStudentsAppropriatesToLoginType() }.onEach { + when (it.status) { + Status.LOADING -> view?.run { + Timber.i("Login started") hideSoftKeyboard() showProgress(true) showContent(false) } - Timber.i("Login started") - } - .doFinally { - view?.apply { - showProgress(false) - showContent(true) + Status.SUCCESS -> { + Timber.i("Login result: Success") + analytics.logEvent("registration_form", + "success" to true, + "students" to it.data!!.size, + "error" to "No error" + ) + view?.notifyParentAccountLogged(it.data) + } + Status.ERROR -> { + Timber.i("Login result: An exception occurred") + analytics.logEvent( + "registration_form", + "success" to false, "students" to -1, + "error" to it.error!!.message.ifNullOrBlank { "No message" } + ) + loginErrorHandler.dispatch(it.error) } } - .subscribe({ - Timber.i("Login result: Success") - analytics.logEvent("registration_form", "success" to true, "students" to it.size, "error" to "No error") - view?.notifyParentAccountLogged(it) - }, { - Timber.i("Login result: An exception occurred") - analytics.logEvent("registration_form", "success" to false, "students" to -1, "error" to it.message.ifNullOrBlank { "No message" }) - loginErrorHandler.dispatch(it) - })) + }.afterLoading { + view?.apply { + showProgress(false) + showContent(true) + } + }.launch("login") } - private fun getStudentsAppropriatesToLoginType(): Single> { + private suspend fun getStudentsAppropriatesToLoginType(): List { val email = view?.formUsernameValue.orEmpty() val password = view?.formPassValue.orEmpty() val endpoint = view?.formHostValue.orEmpty() @@ -163,12 +172,10 @@ class LoginAdvancedPresenter @Inject constructor( val symbol = view?.formSymbolValue.orEmpty() val token = view?.formTokenValue.orEmpty() - return rxSingle { - when (Sdk.Mode.valueOf(view?.formLoginType ?: "")) { - Sdk.Mode.API -> studentRepository.getStudentsApi(pin, symbol, token) - Sdk.Mode.SCRAPPER -> studentRepository.getStudentsScrapper(email, password, endpoint, symbol) - Sdk.Mode.HYBRID -> studentRepository.getStudentsHybrid(email, password, endpoint, symbol) - } + return when (Sdk.Mode.valueOf(view?.formLoginType.orEmpty())) { + Sdk.Mode.API -> studentRepository.getStudentsApi(pin, symbol, token) + Sdk.Mode.SCRAPPER -> studentRepository.getStudentsScrapper(email, password, endpoint, symbol) + Sdk.Mode.HYBRID -> studentRepository.getStudentsHybrid(email, password, endpoint, symbol) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt index ccbe4bbbe..9e43bf77a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt @@ -1,12 +1,15 @@ package io.github.wulkanowy.ui.modules.login.form +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank -import kotlinx.coroutines.rx2.rxSingle +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -75,34 +78,44 @@ class LoginFormPresenter @Inject constructor( if (!validateCredentials(email, password, host)) return - disposable.add(rxSingle { studentRepository.getStudentsScrapper(email, password, host, symbol) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { - view?.apply { + flowWithResource { studentRepository.getStudentsScrapper(email, password, host, symbol) }.onEach { + when (it.status) { + Status.LOADING -> view?.run { + Timber.i("Login started") hideSoftKeyboard() showProgress(true) showContent(false) } - Timber.i("Login started") - } - .doFinally { - view?.apply { - showProgress(false) - showContent(true) + Status.SUCCESS -> { + Timber.i("Login result: Success") + analytics.logEvent( + "registration_form", + "success" to true, + "students" to it.data!!.size, + "scrapperBaseUrl" to host, + "error" to "No error" + ) + view?.notifyParentAccountLogged(it.data, Triple(email, password, host)) + } + Status.ERROR -> { + Timber.i("Login result: An exception occurred") + analytics.logEvent( + "registration_form", + "success" to false, + "students" to -1, + "scrapperBaseUrl" to host, + "error" to it.error!!.message.ifNullOrBlank { "No message" }) + loginErrorHandler.dispatch(it.error) + lastError = it.error + view?.showContact(true) } } - .subscribe({ - Timber.i("Login result: Success") - analytics.logEvent("registration_form", "success" to true, "students" to it.size, "scrapperBaseUrl" to host, "error" to "No error") - view?.notifyParentAccountLogged(it, Triple(email, password, host)) - }, { - Timber.i("Login result: An exception occurred") - analytics.logEvent("registration_form", "success" to false, "students" to -1, "scrapperBaseUrl" to host, "error" to it.message.ifNullOrBlank { "No message" }) - loginErrorHandler.dispatch(it) - lastError = it - view?.showContact(true) - })) + }.afterLoading { + view?.apply { + showProgress(false) + showContent(true) + } + }.launch("login") } fun onFaqClick() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt index 84d5af06b..0ef183ccf 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt @@ -1,12 +1,15 @@ package io.github.wulkanowy.ui.modules.login.recover +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.recover.RecoverRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank -import kotlinx.coroutines.rx2.rxSingle +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -56,24 +59,22 @@ class LoginRecoverPresenter @Inject constructor( if (!validateInput(username, host)) return - disposable.add(rxSingle { recoverRepository.getReCaptchaSiteKey(host, symbol.ifBlank { "Default" }) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { - view?.run { + flowWithResource { recoverRepository.getReCaptchaSiteKey(host, symbol.ifBlank { "Default" }) }.onEach { + when (it.status) { + Status.LOADING -> view?.run { hideSoftKeyboard() showRecoverForm(false) showProgress(true) showErrorView(false) showCaptcha(false) } + Status.SUCCESS -> view?.loadReCaptcha(siteKey = it.data!!.first, url = it.data.second) + Status.ERROR -> { + Timber.i("Obtain captcha site key result: An exception occurred") + errorHandler.dispatch(it.error!!) + } } - .subscribe({ (resetUrl, siteKey) -> - view?.loadReCaptcha(siteKey, resetUrl) - }) { - Timber.i("Obtain captcha site key result: An exception occurred") - errorHandler.dispatch(it) - }) + }.launch("captcha") } private fun validateInput(username: String, host: String): Boolean { @@ -97,35 +98,28 @@ class LoginRecoverPresenter @Inject constructor( val host = view?.recoverHostValue.orEmpty() val symbol = view?.formHostSymbol.ifNullOrBlank { "Default" } - with(disposable) { - clear() - add(rxSingle { recoverRepository.sendRecoverRequest(host, symbol, username, reCaptchaResponse) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { - view?.run { - showProgress(true) - showRecoverForm(false) - showCaptcha(false) - } + flowWithResource { recoverRepository.sendRecoverRequest(host, symbol, username, reCaptchaResponse) }.onEach { + when (it.status) { + Status.LOADING -> view?.run { + showProgress(true) + showRecoverForm(false) + showCaptcha(false) } - .doFinally { - view?.showProgress(false) - } - .subscribe({ - view?.run { - showSuccessView(true) - setSuccessTitle(it.substringBefore(". ")) - setSuccessMessage(it.substringAfter(". ")) - } - + Status.SUCCESS -> view?.run { + showSuccessView(true) + setSuccessTitle(it.data!!.substringBefore(". ")) + setSuccessMessage(it.data.substringAfter(". ")) analytics.logEvent("account_recover", "register" to host, "symbol" to symbol, "success" to true) - }) { + } + Status.ERROR -> { Timber.i("Send recover request result: An exception occurred") - errorHandler.dispatch(it) + errorHandler.dispatch(it.error!!) analytics.logEvent("account_recover", "register" to host, "symbol" to symbol, "success" to false) - }) - } + } + } + }.afterLoading { + view?.showProgress(false) + }.launch("verified") } fun onDetailsClick() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt index 91d3e66cd..99ee7d30b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt @@ -1,14 +1,15 @@ package io.github.wulkanowy.ui.modules.login.studentselect +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank -import kotlinx.coroutines.rx2.rxCompletable -import kotlinx.coroutines.rx2.rxSingle +import kotlinx.coroutines.flow.onEach import timber.log.Timber import java.io.Serializable import javax.inject.Inject @@ -28,7 +29,7 @@ class LoginStudentSelectPresenter @Inject constructor( fun onAttachView(view: LoginStudentSelectView, students: Serializable?) { super.onAttachView(view) - view.run { + with(view) { initView() showContact(false) enableSignIn(false) @@ -73,22 +74,20 @@ class LoginStudentSelectPresenter @Inject constructor( private fun loadData(students: List) { resetSelectedState() this.students = students - disposable.add(rxSingle { studentRepository.getSavedStudents(false) } - .map { savedStudents -> - students.map { student -> - student to savedStudents.any { compareStudents(student, it) } + + flowWithResource { studentRepository.getSavedStudents(false) }.onEach { + when (it.status) { + Status.LOADING -> Timber.d("Login student select students load started") + Status.SUCCESS -> view?.updateData(students.map { student -> + student to it.data!!.any { item -> compareStudents(student, item) } + }) + Status.ERROR -> { + errorHandler.dispatch(it.error!!) + lastError = it.error + view?.updateData(students.map { student -> student to false }) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - view?.updateData(it) - }, { - errorHandler.dispatch(it) - lastError = it - view?.updateData(students.map { student -> student to false }) - }) - ) + }.launch() } private fun resetSelectedState() { @@ -97,33 +96,35 @@ class LoginStudentSelectPresenter @Inject constructor( } private fun registerStudents(students: List) { - disposable.add(rxSingle { studentRepository.saveStudents(students) } - .map { students.first().apply { id = it.first() } } - .flatMapCompletable { rxCompletable { studentRepository.switchStudent(it) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { - view?.apply { + flowWithResource { + val savedStudents = studentRepository.saveStudents(students) + val firstRegistered = students.first().apply { id = savedStudents.first() } + studentRepository.switchStudent(firstRegistered) + }.onEach { + when (it.status) { + Status.LOADING -> view?.run { + Timber.i("Registration started") showProgress(true) showContent(false) } - Timber.i("Registration started") - } - .subscribe({ - students.forEach { analytics.logEvent("registration_student_select", "success" to true, "scrapperBaseUrl" to it.scrapperBaseUrl, "symbol" to it.symbol, "error" to "No error") } - Timber.i("Registration result: Success") - view?.openMainView() - }, { error -> - students.forEach { analytics.logEvent("registration_student_select", "success" to false, "scrapperBaseUrl" to it.scrapperBaseUrl, "symbol" to it.symbol, "error" to error.message.ifNullOrBlank { "No message" }) } - Timber.i("Registration result: An exception occurred ") - loginErrorHandler.dispatch(error) - lastError = error - view?.apply { - showProgress(false) - showContent(true) - showContact(true) + Status.SUCCESS -> { + Timber.i("Registration result: Success") + view?.openMainView() + logRegisterEvent(students) } - })) + Status.ERROR -> { + Timber.i("Registration result: An exception occurred ") + view?.apply { + showProgress(false) + showContent(true) + showContact(true) + } + lastError = it.error + loginErrorHandler.dispatch(it.error!!) + logRegisterEvent(students, it.error) + } + } + }.launch("register") } fun onDiscordClick() { @@ -133,4 +134,15 @@ class LoginStudentSelectPresenter @Inject constructor( fun onEmailClick() { view?.openEmail(lastError?.message.ifNullOrBlank { "empty" }) } + + private fun logRegisterEvent(students: List, error: Throwable? = null) { + students.forEach { student -> + analytics.logEvent( + "registration_student_select", + "success" to (error != null), + "scrapperBaseUrl" to student.scrapperBaseUrl, + "symbol" to student.symbol, + "error" to (error?.message?.ifBlank { "No message" } ?: "No error")) + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt index b7687ed3f..c0f5803f9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt @@ -1,13 +1,15 @@ package io.github.wulkanowy.ui.modules.login.symbol +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank -import io.reactivex.Single -import kotlinx.coroutines.rx2.rxSingle +import kotlinx.coroutines.flow.onEach import timber.log.Timber import java.io.Serializable import javax.inject.Inject @@ -47,44 +49,55 @@ class LoginSymbolPresenter @Inject constructor( return } - disposable.add( - Single.fromCallable { loginData } - .flatMap { rxSingle { studentRepository.getStudentsScrapper(it.first, it.second, it.third, symbol) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { - view?.apply { - hideSoftKeyboard() - showProgress(true) - showContent(false) - } + flowWithResource { studentRepository.getStudentsScrapper(loginData!!.first, loginData!!.second, loginData!!.third, symbol) }.onEach { + when (it.status) { + Status.LOADING -> view?.run { Timber.i("Login with symbol started") + hideSoftKeyboard() + showProgress(true) + showContent(false) } - .doFinally { - view?.apply { - showProgress(false) - showContent(true) - } - } - .subscribe({ - analytics.logEvent("registration_symbol", "success" to true, "students" to it.size, "scrapperBaseUrl" to loginData?.third, "symbol" to symbol, "error" to "No error") - view?.apply { - if (it.isEmpty()) { + Status.SUCCESS -> { + view?.run { + if (it.data!!.isEmpty()) { Timber.i("Login with symbol result: Empty student list") setErrorSymbolIncorrect() view?.showContact(true) } else { Timber.i("Login with symbol result: Success") - notifyParentAccountLogged(it) + notifyParentAccountLogged(it.data) } } - }, { + analytics.logEvent( + "registration_symbol", + "success" to true, + "students" to it.data!!.size, + "scrapperBaseUrl" to loginData?.third, + "symbol" to symbol, + "error" to "No error" + ) + } + Status.ERROR -> { Timber.i("Login with symbol result: An exception occurred") - analytics.logEvent("registration_symbol", "success" to false, "students" to -1, "scrapperBaseUrl" to loginData?.third, "symbol" to symbol, "error" to it.message.ifNullOrBlank { "No message" }) - loginErrorHandler.dispatch(it) - lastError = it + analytics.logEvent( + "registration_symbol", + "success" to false, + "students" to -1, + "scrapperBaseUrl" to loginData?.third, + "symbol" to symbol, + "error" to it.error!!.message.ifNullOrBlank { "No message" } + ) + loginErrorHandler.dispatch(it.error) + lastError = it.error view?.showContact(true) - })) + } + } + }.afterLoading { + view?.apply { + showProgress(false) + showContent(true) + } + }.launch("login") } fun onParentInitSymbolView(loginData: Triple) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt index 1273a54c5..64df3db8a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt @@ -1,13 +1,15 @@ package io.github.wulkanowy.ui.modules.luckynumber +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.luckynumber.LuckyNumberRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider -import kotlinx.coroutines.rx2.rxMaybe -import kotlinx.coroutines.rx2.rxSingle +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResourceIn +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -34,47 +36,47 @@ class LuckyNumberPresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading lucky number started") - disposable.apply { - clear() - add(rxSingle { studentRepository.getCurrentStudent() } - .flatMapMaybe { rxMaybe { luckyNumberRepository.getLuckyNumber(it, forceRefresh) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) + flowWithResourceIn { + val student = studentRepository.getCurrentStudent() + luckyNumberRepository.getLuckyNumber(student, forceRefresh) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading lucky number started") + Status.SUCCESS -> { + if (it.data != null) { + Timber.i("Loading lucky number result: Success") + view?.apply { + updateData(it.data) + showContent(true) + showEmpty(false) + showErrorView(false) + } + analytics.logEvent( + "load_item", + "type" to "lucky_number", + "number" to it.data.luckyNumber + ) + } else { + Timber.i("Loading lucky number result: No lucky number found") + view?.run { + showContent(false) + showEmpty(true) + showErrorView(false) + } } } - .subscribe({ - Timber.i("Loading lucky number result: Success") - view?.apply { - updateData(it) - showContent(true) - showEmpty(false) - showErrorView(false) - } - analytics.logEvent( - "load_item", - "type" to "lucky_number", - "number" to it.luckyNumber, - "force_refresh" to forceRefresh - ) - }, { + Status.ERROR -> { Timber.i("Loading lucky number result: An exception occurred") - errorHandler.dispatch(it) - }, { - Timber.i("Loading lucky number result: No lucky number found") - view?.run { - showContent(false) - showEmpty(true) - showErrorView(false) - } - }) - ) - } + errorHandler.dispatch(it.error!!) + } + } + }.afterLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } + }.launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt index bb7ea75b2..5bcdc8a1b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.luckynumberwidget +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.student.StudentRepository @@ -8,7 +9,9 @@ import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider.Companion.getStudentWidgetKey import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider.Companion.getThemeWidgetKey import io.github.wulkanowy.utils.SchedulersProvider -import kotlinx.coroutines.rx2.rxSingle +import io.github.wulkanowy.utils.flowWithResource +import kotlinx.coroutines.flow.onEach +import timber.log.Timber import javax.inject.Inject class LuckyNumberWidgetConfigurePresenter @Inject constructor( @@ -46,23 +49,25 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor( } private fun loadData() { - disposable.add(rxSingle { studentRepository.getSavedStudents(false) } - .map { it to appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } } - .map { (students, currentStudentId) -> - students.map { student -> student to (student.id == currentStudentId) } - } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - when { - it.isEmpty() -> view?.openLoginView() - it.size == 1 -> { - selectedStudent = it.single().first - view?.showThemeDialog() + flowWithResource { studentRepository.getSavedStudents(false) }.onEach { + when (it.status) { + Status.LOADING -> Timber.d("Lucky number widget configure students data load") + Status.SUCCESS -> { + val widgetId = appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } + when { + it.data!!.isEmpty() -> view?.openLoginView() + it.data.size == 1 -> { + selectedStudent = it.data.single() + view?.showThemeDialog() + } + else -> view?.updateData(it.data.map { student -> + student to (student.id == widgetId) + }) } - else -> view?.updateData(it) } - }, { errorHandler.dispatch(it) })) + Status.ERROR -> errorHandler.dispatch(it.error!!) + } + }.launch() } private fun registerStudent(student: Student?) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt index 204fc79ad..cf8395f3d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt @@ -15,6 +15,7 @@ import android.view.View.VISIBLE import android.widget.RemoteViews import dagger.android.AndroidInjection import io.github.wulkanowy.R +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.exceptions.NoCurrentStudentException @@ -24,6 +25,8 @@ import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.SchedulersProvider import io.reactivex.Maybe +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.rx2.rxMaybe import kotlinx.coroutines.rx2.rxSingle import timber.log.Timber @@ -157,7 +160,7 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { else -> Maybe.empty() } } - .flatMap { rxMaybe { luckyNumberRepository.getLuckyNumber(it) } } + .flatMap { rxMaybe { luckyNumberRepository.getLuckyNumber(it, false).takeWhile { it.status == Status.LOADING }.first().data } } .subscribeOn(schedulers.backgroundThread) .blockingGet() } catch (e: Exception) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt index 4a9d217b0..1e48f71b5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt @@ -5,7 +5,6 @@ import android.view.View import android.view.View.INVISIBLE import android.view.View.VISIBLE import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.repositories.message.MessageFolder.RECEIVED import io.github.wulkanowy.data.repositories.message.MessageFolder.SENT import io.github.wulkanowy.data.repositories.message.MessageFolder.TRASHED @@ -77,10 +76,6 @@ class MessageFragment : BaseFragment(R.layout.fragment_m binding.messageProgress.visibility = if (show) VISIBLE else INVISIBLE } - fun onDeleteMessage(message: Message) { - presenter.onDeleteMessage(message) - } - fun onChildFragmentLoaded() { presenter.onChildViewLoaded() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt index 0f5598b23..ea482c623 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt @@ -1,13 +1,12 @@ package io.github.wulkanowy.ui.modules.message -import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.SchedulersProvider -import io.reactivex.Completable +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import timber.log.Timber -import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject class MessagePresenter @Inject constructor( @@ -18,12 +17,12 @@ class MessagePresenter @Inject constructor( override fun onAttachView(view: MessageView) { super.onAttachView(view) - disposable.add(Completable.timer(150, MILLISECONDS, schedulers.mainThread) - .subscribe { - view.initView() - Timber.i("Message view was initialized") - loadData() - }) + launch { + delay(150) + view.initView() + Timber.i("Message view was initialized") + loadData() + } } fun onPageSelected(index: Int) { @@ -46,15 +45,6 @@ class MessagePresenter @Inject constructor( } } - fun onDeleteMessage(message: Message) { - view?.notifyChildMessageDeleted( - when (message.removed) { - true -> 2 - else -> message.folderId - 1 - } - ) - } - fun onSendMessageButtonClicked() { view?.openSendMessage() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt index 575db75b9..ec743cd7b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt @@ -203,10 +203,6 @@ class MessagePreviewFragment : (activity as MainActivity).popView() } - override fun notifyParentMessageDeleted(message: Message) { - parentFragmentManager.fragments.forEach { if (it is MessageFragment) it.onDeleteMessage(message) } - } - override fun onSaveInstanceState(outState: Bundle) { outState.putSerializable(MESSAGE_ID_KEY, presenter.message) super.onSaveInstanceState(outState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt index b94c46123..4d3b83f1b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt @@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.message.preview import android.annotation.SuppressLint import android.os.Build +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageAttachment import io.github.wulkanowy.data.repositories.message.MessageRepository @@ -11,8 +12,11 @@ import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResource +import io.github.wulkanowy.utils.flowWithResourceIn import io.github.wulkanowy.utils.toFormattedString -import kotlinx.coroutines.rx2.rxSingle +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -54,33 +58,35 @@ class MessagePreviewPresenter @Inject constructor( } private fun loadData(message: Message) { - Timber.i("Loading message ${message.messageId} preview started") - disposable.apply { - clear() - add(rxSingle { studentRepository.getStudentById(message.studentId) } - .flatMap { rxSingle { messageRepository.getMessage(it, message, true) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { view?.showProgress(false) } - .subscribe({ message -> - Timber.i("Loading message ${message.message.messageId} preview result: Success ") - this@MessagePreviewPresenter.message = message.message - this@MessagePreviewPresenter.attachments = message.attachments + flowWithResourceIn { + val student = studentRepository.getStudentById(message.studentId) + messageRepository.getMessage(student, message, true) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading message ${message.messageId} preview started") + Status.SUCCESS -> { + Timber.i("Loading message ${it.data!!.message.messageId} preview result: Success ") + this@MessagePreviewPresenter.message = it.data.message + this@MessagePreviewPresenter.attachments = it.data.attachments view?.apply { - setMessageWithAttachment(message) + setMessageWithAttachment(it.data) initOptions() } analytics.logEvent( "load_item", "type" to "message_preview", - "length" to message.message.content.length + "length" to it.data.message.content.length ) - }) { + } + Status.ERROR -> { Timber.i("Loading message ${message.messageId} preview result: An exception occurred ") retryCallback = { onMessageLoadRetry(message) } - errorHandler.dispatch(it) - }) - } + errorHandler.dispatch(it.error!!) + } + } + }.afterLoading { + view?.showProgress(false) + }.launch() } fun onReply(): Boolean { @@ -152,34 +158,37 @@ class MessagePreviewPresenter @Inject constructor( } private fun deleteMessage() { - message?.let { message -> - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { rxSingle { messageRepository.deleteMessage(it, message) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { + message ?: return + + view?.run { + showContent(false) + showProgress(true) + showOptions(false) + showErrorView(false) + } + + flowWithResource { + val student = studentRepository.getCurrentStudent() + messageRepository.deleteMessage(student, message!!) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.d("Message ${message?.id} delete started") + Status.SUCCESS -> { + Timber.d("Message ${message?.id} delete success") view?.run { - showContent(false) - showProgress(true) - showOptions(false) - showErrorView(false) - } - } - .doFinally { - view?.showProgress(false) - } - .subscribe({ - view?.run { - notifyParentMessageDeleted(message) showMessage(deleteMessageSuccessString) popView() } - }, { error -> + } + Status.ERROR -> { + Timber.d("Message ${message?.id} delete failed") retryCallback = { onMessageDelete() } - errorHandler.dispatch(error) - }) - ) - } + errorHandler.dispatch(it.error!!) + } + } + }.afterLoading { + view?.showProgress(false) + }.launch("delete") } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt index 0fdb4bda3..fa6d735ed 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt @@ -22,8 +22,6 @@ interface MessagePreviewView : BaseView { fun showContent(show: Boolean) - fun notifyParentMessageDeleted(message: Message) - fun showErrorView(show: Boolean) fun setErrorDetails(message: String) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt index 545409c6d..c31fd79a7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt @@ -1,8 +1,8 @@ package io.github.wulkanowy.ui.modules.message.send +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Recipient -import io.github.wulkanowy.data.db.entities.ReportingUnit import io.github.wulkanowy.data.repositories.message.MessageRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.recipient.RecipientRepository @@ -13,10 +13,10 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.toFormattedString -import io.reactivex.Completable -import kotlinx.coroutines.rx2.rxMaybe -import kotlinx.coroutines.rx2.rxSingle +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -37,7 +37,7 @@ class SendMessagePresenter @Inject constructor( view.initView() Timber.i("Send message view was initialized") loadData(message, reply) - view.apply { + with(view) { message?.let { setSubject(when (reply) { true -> "RE: " @@ -95,90 +95,89 @@ class SendMessagePresenter @Inject constructor( } private fun loadData(message: Message?, reply: Boolean?) { - var reportingUnit: ReportingUnit? = null - var recipientChips: List = emptyList() - var selectedRecipientChips: List = emptyList() + flowWithResource { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + val unit = reportingUnitRepository.getReportingUnit(student, semester.unitId) - Timber.i("Loading recipients started") - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { rxSingle { semesterRepository.getCurrentSemester(it) }.map { semester -> it to semester } } - .flatMapCompletable { (student, semester) -> - rxMaybe { reportingUnitRepository.getReportingUnit(student, semester.unitId) } - .doOnSuccess { reportingUnit = it } - .flatMap { rxMaybe { recipientRepository.getRecipients(student, 2, it) } } - .doOnSuccess { - Timber.i("Loading recipients result: Success, fetched %d recipients", it.size) - recipientChips = createChips(it) - } - .flatMapCompletable { - if (message == null || reply != true) Completable.complete() - else rxSingle { recipientRepository.getMessageRecipients(student, message) } - .doOnSuccess { - Timber.i("Loaded message recipients to reply result: Success, fetched %d recipients", it.size) - selectedRecipientChips = createChips(it) - } - .ignoreElement() - } - } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { - view?.run { + Timber.i("Loading recipients started") + val recipients = when { + unit != null -> recipientRepository.getRecipients(student, 2, unit) + else -> listOf() + }.let { createChips(it) } + Timber.i("Loading recipients result: Success, fetched %d recipients", recipients.size) + + Timber.i("Loading message recipients started") + val messageRecipients = when { + message != null && reply == true -> recipientRepository.getMessageRecipients(student, message) + else -> emptyList() + }.let { createChips(it) } + Timber.i("Loaded message recipients to reply result: Success, fetched %d recipients", messageRecipients.size) + + Triple(unit, recipients, messageRecipients) + }.onEach { + when (it.status) { + Status.LOADING -> view?.run { + Timber.i("Loading recipients started") showProgress(true) showContent(false) } - } - .doFinally { view?.run { showProgress(false) } } - .subscribe({ - view?.run { - if (reportingUnit !== null) { - reportingUnit?.let { setReportingUnit(it) } - setRecipients(recipientChips) - if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients(selectedRecipientChips) - showContent(true) - } else { - Timber.i("Loading recipients result: Can't find the reporting unit") - view?.showEmpty(true) + Status.SUCCESS -> it.data!!.let { (reportingUnit, recipientChips, selectedRecipientChips) -> + view?.run { + if (reportingUnit != null) { + setReportingUnit(reportingUnit) + setRecipients(recipientChips) + if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients(selectedRecipientChips) + showContent(true) + } else { + Timber.i("Loading recipients result: Can't find the reporting unit") + view?.showEmpty(true) + } } } - }, { - Timber.i("Loading recipients result: An exception occurred") - view?.showContent(true) - errorHandler.dispatch(it) - })) + Status.ERROR -> { + Timber.i("Loading recipients result: An exception occurred") + view?.showContent(true) + errorHandler.dispatch(it.error!!) + } + } + }.afterLoading { + view?.run { showProgress(false) } + }.launch() } private fun sendMessage(subject: String, content: String, recipients: List) { - Timber.i("Sending message started") - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { rxSingle { messageRepository.sendMessage(it, subject, content, recipients) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doOnSubscribe { - view?.run { + flowWithResource { + val student = studentRepository.getCurrentStudent() + messageRepository.sendMessage(student, subject, content, recipients) + }.onEach { + when (it.status) { + Status.LOADING -> view?.run { + Timber.i("Sending message started") showSoftInput(false) showContent(false) showProgress(true) showActionBar(false) } + Status.SUCCESS -> { + Timber.i("Sending message result: Success") + view?.run { + showMessage(messageSuccess) + popView() + } + analytics.logEvent("send_message", "recipients" to recipients.size) + } + Status.ERROR -> { + Timber.i("Sending message result: An exception occurred") + view?.run { + showContent(true) + showProgress(false) + showActionBar(true) + } + errorHandler.dispatch(it.error!!) + } } - .subscribe({ - Timber.i("Sending message result: Success") - analytics.logEvent("send_message", "recipients" to recipients.size) - view?.run { - showMessage(messageSuccess) - popView() - } - }, { - Timber.i("Sending message result: An exception occurred") - view?.run { - showContent(true) - showProgress(false) - showActionBar(true) - } - errorHandler.dispatch(it) - }) - ) + }.launch("send") } private fun createChips(recipients: List): List { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt index 0e96836b1..3c9f04447 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.message.tab +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.repositories.message.MessageFolder import io.github.wulkanowy.data.repositories.message.MessageRepository @@ -9,13 +10,20 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResourceIn import io.github.wulkanowy.utils.toFormattedString -import io.reactivex.subjects.PublishSubject -import kotlinx.coroutines.rx2.rxSingle +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import me.xdrop.fuzzywuzzy.FuzzySearch import timber.log.Timber import java.util.Locale -import java.util.concurrent.TimeUnit import javax.inject.Inject import kotlin.math.pow @@ -36,7 +44,7 @@ class MessageTabPresenter @Inject constructor( private var messages = emptyList() - private val searchQuery = PublishSubject.create() + private val searchChannel = Channel() fun onAttachView(view: MessageTabView, folder: MessageFolder) { super.onAttachView(view) @@ -64,7 +72,7 @@ class MessageTabPresenter @Inject constructor( } fun onDeleteMessage() { - loadData(false) + loadData(true) } fun onParentViewLoadData(forceRefresh: Boolean) { @@ -83,36 +91,37 @@ class MessageTabPresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean) { - Timber.i("Loading $folder message data started") - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { student -> - rxSingle { semesterRepository.getCurrentSemester(student) } - .flatMap { rxSingle { messageRepository.getMessages(student, it, folder, forceRefresh) } } - } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded() + flowWithResourceIn { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + messageRepository.getMessages(student, semester, folder, forceRefresh) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading $folder message data started") + Status.SUCCESS -> { + Timber.i("Loading $folder message result: Success") + messages = it.data!! + updateData(getFilteredData(lastSearchQuery)) + analytics.logEvent( + "load_data", + "type" to "messages", + "items" to it.data.size, + "folder" to folder.name + ) + } + Status.ERROR -> { + Timber.i("Loading $folder message result: An exception occurred") + errorHandler.dispatch(it.error!!) } } - .subscribe({ - Timber.i("Loading $folder message result: Success") - messages = it - view?.updateData(getFilteredData(lastSearchQuery)) - analytics.logEvent( - "load_data", - "type" to "messages", - "items" to it.size, - "folder" to folder.name - ) - }) { - Timber.i("Loading $folder message result: An exception occurred") - errorHandler.dispatch(it) - }) + }.afterLoading { + view?.run { + showRefresh(false) + showProgress(false) + enableSwipe(true) + notifyParentDataLoaded() + } + }.launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -127,23 +136,25 @@ class MessageTabPresenter @Inject constructor( } fun onSearchQueryTextChange(query: String) { - if (query != searchQuery.toString()) - searchQuery.onNext(query) + launch { + searchChannel.send(query) + } } private fun initializeSearchStream() { - disposable.add(searchQuery - .debounce(250, TimeUnit.MILLISECONDS) - .map { query -> - lastSearchQuery = query - getFilteredData(query) - } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - Timber.d("Applying filter. Full list: ${messages.size}, filtered: ${it.size}") - updateData(it) - }) { Timber.e(it) }) + launch { + searchChannel.consumeAsFlow() + .debounce(250) + .map { query -> + lastSearchQuery = query + getFilteredData(query) + } + .catch { Timber.e(it) } + .collect { + Timber.d("Applying filter. Full list: ${messages.size}, filtered: ${it.size}") + updateData(it) + } + } } private fun getFilteredData(query: String): List { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt index b1dea5dff..e665a15bf 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.mobiledevice +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.MobileDevice import io.github.wulkanowy.data.repositories.mobiledevice.MobileDeviceRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository @@ -8,7 +9,11 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider -import kotlinx.coroutines.rx2.rxSingle +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResource +import io.github.wulkanowy.utils.flowWithResourceIn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -48,39 +53,39 @@ class MobileDevicePresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading mobile devices data started") - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { student -> - rxSingle { semesterRepository.getCurrentSemester(student) }.flatMap { semester -> - rxSingle { mobileDeviceRepository.getDevices(student, semester, forceRefresh) } + flowWithResourceIn { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + mobileDeviceRepository.getDevices(student, semester, forceRefresh) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading mobile devices data started") + Status.SUCCESS -> { + Timber.i("Loading mobile devices result: Success") + view?.run { + updateData(it.data!!) + showContent(it.data.isNotEmpty()) + showEmpty(it.data.isEmpty()) + showErrorView(false) + } + analytics.logEvent( + "load_data", + "type" to "devices", + "items" to it.data!!.size + ) + } + Status.ERROR -> { + Timber.i("Loading mobile devices result: An exception occurred") + errorHandler.dispatch(it.error!!) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - } - }.subscribe({ - Timber.i("Loading mobile devices result: Success") - view?.run { - updateData(it) - showContent(it.isNotEmpty()) - showEmpty(it.isEmpty()) - showErrorView(false) - } - analytics.logEvent( - "load_data", - "type" to "devices", - "items" to it.size, - "force_refresh" to forceRefresh - ) - }) { - Timber.i("Loading mobile devices result: An exception occurred") - errorHandler.dispatch(it) - }) + }.afterLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } + }.launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -114,33 +119,25 @@ class MobileDevicePresenter @Inject constructor( } fun onUnregisterConfirmed(device: MobileDevice) { - Timber.i("Unregister device started") - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { student -> - rxSingle { semesterRepository.getCurrentSemester(student) }.flatMap { semester -> - rxSingle { mobileDeviceRepository.unregisterDevice(student, semester, device) } - .flatMap { rxSingle { mobileDeviceRepository.getDevices(student, semester, it) } } + flowWithResource { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + mobileDeviceRepository.unregisterDevice(student, semester, device) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Unregister device started") + Status.SUCCESS -> { + Timber.i("Unregister device result: Success") + view?.run { + showProgress(false) + enableSwipe(true) + } + } + Status.ERROR -> { + Timber.i("Unregister device result: An exception occurred") + errorHandler.dispatch(it.error!!) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - showProgress(false) - enableSwipe(true) - } - } - .subscribe({ - Timber.i("Unregister device result: Success") - view?.run { - updateData(it) - showContent(it.isNotEmpty()) - showEmpty(it.isEmpty()) - } - }) { - Timber.i("Unregister device result: An exception occurred") - errorHandler.dispatch(it) - } - ) + }.launchIn(this) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt index 1c0506f0d..f5fb7db32 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.mobiledevice.token +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.mobiledevice.MobileDeviceRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.student.StudentRepository @@ -7,7 +8,9 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider -import kotlinx.coroutines.rx2.rxSingle +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResource +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -28,28 +31,29 @@ class MobileDeviceTokenPresenter @Inject constructor( } private fun loadData() { - Timber.i("Mobile device registration data started") - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { student -> - rxSingle { semesterRepository.getCurrentSemester(student) }.flatMap { semester -> - rxSingle { mobileDeviceRepository.getToken(student, semester) } + flowWithResource { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + mobileDeviceRepository.getToken(student, semester) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Mobile device registration data started") + Status.SUCCESS -> { + Timber.i("Mobile device registration result: Success") + view?.run { + updateData(it.data!!) + showContent() + } + analytics.logEvent("device_register", "symbol" to it.data!!.token.substring(0, 3)) + } + Status.ERROR -> { + Timber.i("Mobile device registration result: An exception occurred") + view?.closeDialog() + errorHandler.dispatch(it.error!!) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { view?.hideLoading() } - .subscribe({ - Timber.i("Mobile device registration result: Success") - view?.run { - updateData(it) - showContent() - } - analytics.logEvent("device_register", "symbol" to it.token.substring(0, 3)) - }) { - Timber.i("Mobile device registration result: An exception occurred") - view?.closeDialog() - errorHandler.dispatch(it) - } - ) + }.afterLoading { + view?.hideLoading() + }.launch() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt index 4009b4f6c..8e5661adc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.note +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.repositories.note.NoteRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository @@ -8,7 +9,11 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider -import kotlinx.coroutines.rx2.rxSingle +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResource +import io.github.wulkanowy.utils.flowWithResourceIn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -49,38 +54,39 @@ class NotePresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading note data started") - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { rxSingle { semesterRepository.getCurrentSemester(it) }.map { semester -> semester to it } } - .flatMap { rxSingle { noteRepository.getNotes(it.second, it.first, forceRefresh) } } - .map { items -> items.sortedByDescending { it.date } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) + flowWithResourceIn { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + noteRepository.getNotes(student, semester, forceRefresh) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading note data started") + Status.SUCCESS -> { + Timber.i("Loading note result: Success") + view?.apply { + updateData(it.data!!.sortedByDescending { item -> item.date }) + showEmpty(it.data.isEmpty()) + showErrorView(false) + showContent(it.data.isNotEmpty()) + } + analytics.logEvent( + "load_data", + "type" to "note", + "items" to it.data!!.size + ) } - }.subscribe({ - Timber.i("Loading note result: Success") - view?.apply { - updateData(it) - showEmpty(it.isEmpty()) - showErrorView(false) - showContent(it.isNotEmpty()) + Status.ERROR -> { + Timber.i("Loading note result: An exception occurred") + errorHandler.dispatch(it.error!!) } - analytics.logEvent( - "load_data", - "type" to "note", - "items" to it.size, - "force_refresh" to forceRefresh - ) - }, { - Timber.i("Loading note result: An exception occurred") - errorHandler.dispatch(it) - }) - ) + } + }.afterLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } + }.launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -107,14 +113,15 @@ class NotePresenter @Inject constructor( } private fun updateNote(note: Note) { - Timber.i("Attempt to update note ${note.id}") - disposable.add(rxSingle { noteRepository.updateNote(note) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ Timber.i("Update note result: Success") }) - { error -> - Timber.i("Update note result: An exception occurred") - errorHandler.dispatch(error) - }) + flowWithResource { noteRepository.updateNote(note) }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Attempt to update note ${note.id}") + Status.SUCCESS -> Timber.i("Update note result: Success") + Status.ERROR -> { + Timber.i("Update note result: An exception occurred") + errorHandler.dispatch(it.error!!) + } + } + }.launchIn(this) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersPresenter.kt index 1856803c6..324f2c37b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersPresenter.kt @@ -4,9 +4,9 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.SchedulersProvider -import io.reactivex.Completable +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import timber.log.Timber -import java.util.concurrent.TimeUnit import javax.inject.Inject class SchoolAndTeachersPresenter @Inject constructor( @@ -17,12 +17,12 @@ class SchoolAndTeachersPresenter @Inject constructor( override fun onAttachView(view: SchoolAndTeachersView) { super.onAttachView(view) - disposable.add(Completable.timer(150, TimeUnit.MILLISECONDS, schedulers.mainThread) - .subscribe { - view.initView() - Timber.i("Message view was initialized") - loadData() - }) + launch { + delay(150) + view.initView() + Timber.i("Message view was initialized") + loadData() + } } fun onPageSelected(index: Int) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt index 334c60a32..9c10c6ed7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.schoolandteachers.school +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.school.SchoolRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.student.StudentRepository @@ -7,8 +8,9 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider -import kotlinx.coroutines.rx2.rxMaybe -import kotlinx.coroutines.rx2.rxSingle +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResourceIn +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -64,48 +66,46 @@ class SchoolPresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading school info started") - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMapMaybe { student -> - rxSingle { semesterRepository.getCurrentSemester(student) }.flatMapMaybe { - rxMaybe { schoolRepository.getSchoolInfo(student, it, forceRefresh) } - } - } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded() - } - }.subscribe({ - Timber.i("Loading teachers result: Success") - view?.run { - address = it.address.ifBlank { null } - contact = it.contact.ifBlank { null } - updateData(it) - showContent(true) - showEmpty(false) - showErrorView(false) - } - analytics.logEvent( - "load_item", - "type" to "school", - "force_refresh" to forceRefresh - ) - }, { - Timber.i("Loading school result: An exception occurred") - errorHandler.dispatch(it) - }, { - Timber.i("Loading school result: No school info found") - view?.run { + flowWithResourceIn { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + schoolRepository.getSchoolInfo(student, semester, forceRefresh) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading school info started") + Status.SUCCESS -> if (it.data != null) { + Timber.i("Loading teachers result: Success") + view?.run { + address = it.data.address.ifBlank { null } + contact = it.data.contact.ifBlank { null } + updateData(it.data) + showContent(true) + showEmpty(false) + showErrorView(false) + } + analytics.logEvent( + "load_item", + "type" to "school" + ) + } else view?.run { + Timber.i("Loading school result: No school info found") showContent(!isViewEmpty) showEmpty(isViewEmpty) showErrorView(false) } - })) + Status.ERROR -> { + Timber.i("Loading school result: An exception occurred") + errorHandler.dispatch(it.error!!) + } + } + }.afterLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + notifyParentDataLoaded() + } + }.launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt index 2ccba71f9..886f5c689 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.schoolandteachers.teacher +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.teacher.TeacherRepository @@ -7,7 +8,9 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider -import kotlinx.coroutines.rx2.rxSingle +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResourceIn +import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -39,7 +42,7 @@ class TeacherPresenter @Inject constructor( showErrorView(false) showProgress(true) } - loadData(true) + loadData() } fun onDetailsClick() { @@ -51,41 +54,40 @@ class TeacherPresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading teachers data started") - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { student -> - rxSingle { semesterRepository.getCurrentSemester(student) }.flatMap { semester -> - rxSingle { teacherRepository.getTeachers(student, semester, forceRefresh) } + flowWithResourceIn { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + teacherRepository.getTeachers(student, semester, forceRefresh) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading teachers data started") + Status.SUCCESS -> { + Timber.i("Loading teachers result: Success") + view?.run { + updateData(it.data!!.filter { item -> item.name.isNotBlank() }) + showContent(it.data.isNotEmpty()) + showEmpty(it.data.isEmpty()) + showErrorView(false) + } + analytics.logEvent( + "load_data", + "type" to "teachers", + "items" to it.data!!.size + ) + } + Status.ERROR -> { + Timber.i("Loading teachers result: An exception occurred") + errorHandler.dispatch(it.error!!) } } - .map { it.filter { teacher -> teacher.name.isNotBlank() } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded() - } - }.subscribe({ - Timber.i("Loading teachers result: Success") - view?.run { - updateData(it) - showContent(it.isNotEmpty()) - showEmpty(it.isEmpty()) - showErrorView(false) - } - analytics.logEvent( - "load_data", - "type" to "teachers", - "items" to it.size, - "force_refresh" to forceRefresh - ) - }) { - Timber.i("Loading teachers result: An exception occurred") - errorHandler.dispatch(it) - }) + }.afterLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + notifyParentDataLoaded() + } + }.launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt index bfdd17660..7ffbf41af 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt @@ -1,10 +1,13 @@ package io.github.wulkanowy.ui.modules.splash +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.SchedulersProvider -import kotlinx.coroutines.rx2.rxSingle +import io.github.wulkanowy.utils.flowWithResource +import kotlinx.coroutines.flow.onEach +import timber.log.Timber import javax.inject.Inject class SplashPresenter @Inject constructor( @@ -15,14 +18,15 @@ class SplashPresenter @Inject constructor( override fun onAttachView(view: SplashView) { super.onAttachView(view) - disposable.add(rxSingle { studentRepository.isCurrentStudentSet() } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - view.apply { - if (it) openMainView() + flowWithResource { studentRepository.isCurrentStudentSet() }.onEach { + when (it.status) { + Status.LOADING -> Timber.d("Is current user set check started") + Status.SUCCESS -> with(view) { + if (it.data!!) openMainView() else openLoginView() } - }, { errorHandler.dispatch(it) })) + Status.ERROR -> errorHandler.dispatch(it.error!!) + } + }.launch() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt index da76854a2..bc7e26890 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.ui.modules.timetable import android.annotation.SuppressLint +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository @@ -10,13 +11,17 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider +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.nextOrSameSchoolDay import io.github.wulkanowy.utils.nextSchoolDay import io.github.wulkanowy.utils.previousSchoolDay import io.github.wulkanowy.utils.toFormattedString -import kotlinx.coroutines.rx2.rxSingle +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onEach import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.of @@ -112,60 +117,60 @@ class TimetablePresenter @Inject constructor( } private fun setBaseDateOnHolidays() { - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { rxSingle { semesterRepository.getCurrentSemester(it) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) - currentDate = baseDate - reloadNavigation() - }) { - Timber.i("Loading semester result: An exception occurred") - }) + flow { + val student = studentRepository.getCurrentStudent() + emit(semesterRepository.getCurrentSemester(student)) + }.catch { + Timber.i("Loading semester result: An exception occurred") + }.onEach { + baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) + currentDate = baseDate + reloadNavigation() + }.launch("holidays") } private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { - Timber.i("Loading timetable data started") currentDate = date - disposable.apply { - clear() - add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { student -> - rxSingle { semesterRepository.getCurrentSemester(student) }.flatMap { semester -> - rxSingle { timetableRepository.getTimetable(student, semester, currentDate, currentDate, forceRefresh) } - } - } - .map { items -> items.filter { if (prefRepository.showWholeClassPlan == "no") it.isStudentPlan else true } } - .map { items -> items.sortedWith(compareBy({ it.number }, { !it.isStudentPlan })) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - } - } - .subscribe({ + + flowWithResourceIn { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + timetableRepository.getTimetable(student, semester, date, date, forceRefresh) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading timetable data started") + Status.SUCCESS -> { Timber.i("Loading timetable result: Success") view?.apply { - updateData(it, prefRepository.showWholeClassPlan, prefRepository.showTimetableTimers) - showEmpty(it.isEmpty()) + updateData( + showWholeClassPlanType = prefRepository.showWholeClassPlan, + showTimetableTimers = prefRepository.showTimetableTimers, + data = it.data!! + .filter { item -> if (prefRepository.showWholeClassPlan == "no") item.isStudentPlan else true } + .sortedWith(compareBy({ item -> item.number }, { item -> !item.isStudentPlan })) + ) + showEmpty(it.data.isEmpty()) showErrorView(false) - showContent(it.isNotEmpty()) + showContent(it.data.isNotEmpty()) } analytics.logEvent( "load_data", "type" to "timetable", - "items" to it.size, - "force_refresh" to forceRefresh + "items" to it.data!!.size ) - }) { + } + Status.ERROR -> { Timber.i("Loading timetable result: An exception occurred") - errorHandler.dispatch(it) - }) - } + errorHandler.dispatch(it.error!!) + } + } + }.afterLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } + }.launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt index c6a2cf84d..0bab7795e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.ui.modules.timetable.completed import android.annotation.SuppressLint +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.CompletedLesson import io.github.wulkanowy.data.repositories.completedlessons.CompletedLessonsRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository @@ -8,13 +9,17 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider +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.nextOrSameSchoolDay import io.github.wulkanowy.utils.nextSchoolDay import io.github.wulkanowy.utils.previousSchoolDay import io.github.wulkanowy.utils.toFormattedString -import kotlinx.coroutines.rx2.rxSingle +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onEach import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.ofEpochDay @@ -94,59 +99,54 @@ class CompletedLessonsPresenter @Inject constructor( } private fun setBaseDateOnHolidays() { - disposable.add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { rxSingle { semesterRepository.getCurrentSemester(it) } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) - currentDate = baseDate - reloadNavigation() - }) { - Timber.i("Loading semester result: An exception occurred") - }) + flow { + val student = studentRepository.getCurrentStudent() + emit(semesterRepository.getCurrentSemester(student)) + }.catch { + Timber.i("Loading semester result: An exception occurred") + }.onEach { + baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) + currentDate = baseDate + reloadNavigation() + }.launch("holidays") } private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { - Timber.i("Loading completed lessons data started") currentDate = date - disposable.apply { - clear() - add(rxSingle { studentRepository.getCurrentStudent() } - .flatMap { student -> - rxSingle { semesterRepository.getCurrentSemester(student) }.flatMap { semester -> - rxSingle { completedLessonsRepository.getCompletedLessons(student, semester, currentDate, currentDate, forceRefresh) } - } - } - .map { items -> items.sortedBy { it.number } } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - } - } - .subscribe({ + + flowWithResourceIn { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + completedLessonsRepository.getCompletedLessons(student, semester, date, date, forceRefresh) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading completed lessons data started") + Status.SUCCESS -> { Timber.i("Loading completed lessons lessons result: Success") view?.apply { - updateData(it) - showEmpty(it.isEmpty()) + updateData(it.data!!.sortedBy { item -> item.number }) + showEmpty(it.data.isEmpty()) showErrorView(false) - showContent(it.isNotEmpty()) + showContent(it.data.isNotEmpty()) } analytics.logEvent( "load_data", "type" to "completed_lessons", - "items" to it.size, - "force_refresh" to forceRefresh + "items" to it.data!!.size ) - }) { + } + Status.ERROR -> { Timber.i("Loading completed lessons result: An exception occurred") - completedLessonsErrorHandler.dispatch(it) - }) - } + completedLessonsErrorHandler.dispatch(it.error!!) + } + } + }.afterLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } + }.launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt index cc2ac4bb0..28eef06e2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.timetablewidget +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.student.StudentRepository @@ -8,7 +9,9 @@ import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getThemeWidgetKey import io.github.wulkanowy.utils.SchedulersProvider -import kotlinx.coroutines.rx2.rxSingle +import io.github.wulkanowy.utils.flowWithResource +import kotlinx.coroutines.flow.onEach +import timber.log.Timber import javax.inject.Inject class TimetableWidgetConfigurePresenter @Inject constructor( @@ -51,23 +54,25 @@ class TimetableWidgetConfigurePresenter @Inject constructor( } private fun loadData() { - disposable.add(rxSingle { studentRepository.getSavedStudents(false) } - .map { it to appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } } - .map { (students, currentStudentId) -> - students.map { student -> student to (student.id == currentStudentId) } - } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .subscribe({ - when { - it.isEmpty() -> view?.openLoginView() - it.size == 1 && !isFromProvider -> { - selectedStudent = it.single().first - view?.showThemeDialog() + flowWithResource { studentRepository.getSavedStudents(false) }.onEach { + when (it.status) { + Status.LOADING -> Timber.d("Timetable widget configure students data load") + Status.SUCCESS -> { + val widgetId = appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } + when { + it.data!!.isEmpty() -> view?.openLoginView() + it.data.size == 1 && !isFromProvider -> { + selectedStudent = it.data.single() + view?.showThemeDialog() + } + else -> view?.updateData(it.data.map { student -> + student to (student.id == widgetId) + }) } - else -> view?.updateData(it) } - }, { errorHandler.dispatch(it) })) + Status.ERROR -> errorHandler.dispatch(it.error!!) + } + }.launch() } private fun registerStudent(student: Student?) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt index 6c043e9e7..de09968cd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt @@ -12,6 +12,7 @@ import android.widget.AdapterView.INVALID_POSITION import android.widget.RemoteViews import android.widget.RemoteViewsService import io.github.wulkanowy.R +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository @@ -25,6 +26,8 @@ import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.toFormattedString import io.reactivex.Maybe +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.rx2.rxMaybe import kotlinx.coroutines.rx2.rxSingle import org.threeten.bp.LocalDate @@ -113,7 +116,7 @@ class TimetableWidgetFactory( } .flatMap { student -> rxMaybe { semesterRepository.getCurrentSemester(student) }.flatMap { semester -> - rxMaybe { timetableRepository.getTimetable(student, semester, date, date) } + rxMaybe { timetableRepository.getTimetable(student, semester, date, date, true).takeWhile { it.status == Status.LOADING }.first().data } } } .map { items -> items.sortedWith(compareBy({ it.number }, { !it.isStudentPlan })) } diff --git a/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt new file mode 100644 index 000000000..6551606ba --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt @@ -0,0 +1,93 @@ +package io.github.wulkanowy.utils + +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.Status +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach + +inline fun networkBoundResource( + showSavedOnLoading: Boolean = true, + crossinline query: () -> Flow, + crossinline fetch: suspend (ResultType) -> RequestType, + crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit, + crossinline onFetchFailed: (Throwable) -> Unit = { Unit }, + crossinline shouldFetch: (ResultType) -> Boolean = { true }, + crossinline filterResult: (ResultType) -> ResultType = { it } +) = flow { + emit(Resource.loading()) + + val data = query().first() + emitAll(if (shouldFetch(data)) { + if (showSavedOnLoading) emit(Resource.loading(filterResult(data))) + + try { + saveFetchResult(data, fetch(data)) + query().map { Resource.success(filterResult(it)) } + } catch (throwable: Throwable) { + onFetchFailed(throwable) + query().map { Resource.error(throwable, filterResult(it)) } + } + } else { + query().map { Resource.success(filterResult(it)) } + }) +} + +@JvmName("networkBoundResourceWithMap") +inline fun networkBoundResource( + showSavedOnLoading: Boolean = true, + crossinline query: () -> Flow, + crossinline fetch: suspend (ResultType) -> RequestType, + crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit, + crossinline onFetchFailed: (Throwable) -> Unit = { Unit }, + crossinline shouldFetch: (ResultType) -> Boolean = { true }, + crossinline mapResult: (ResultType) -> T +) = flow { + emit(Resource.loading()) + + val data = query().first() + emitAll(if (shouldFetch(data)) { + if (showSavedOnLoading) emit(Resource.loading(mapResult(data))) + + try { + saveFetchResult(data, fetch(data)) + query().map { Resource.success(mapResult(it)) } + } catch (throwable: Throwable) { + onFetchFailed(throwable) + query().map { Resource.error(throwable, mapResult(it)) } + } + } else { + query().map { Resource.success(mapResult(it)) } + }) +} + +fun flowWithResource(block: suspend () -> T) = flow { + emit(Resource.loading()) + try { + emit(Resource.success(block())) + } catch (e: Throwable) { + emit(Resource.error(e)) + } +} + +fun flowWithResourceIn(block: suspend () -> Flow>) = flow { + emit(Resource.loading()) + + try { + block().collect { + if (it.status != Status.LOADING) { // LOADING is already emitted + emit(it) + } + } + } catch (e: Throwable) { + emit(Resource.error(e)) + } +} + +fun Flow>.afterLoading(callback: () -> Unit) = onEach { + if (it.status != Status.LOADING) callback() +} diff --git a/app/src/main/res/layout/fragment_license.xml b/app/src/main/res/layout/fragment_license.xml index 80ecd6b10..f4105355c 100644 --- a/app/src/main/res/layout/fragment_license.xml +++ b/app/src/main/res/layout/fragment_license.xml @@ -1,5 +1,6 @@ @@ -14,5 +15,6 @@ + android:layout_height="match_parent" + tools:listitem="@layout/item_license" /> diff --git a/app/src/test/java/io/github/wulkanowy/MainCoroutineRule.kt b/app/src/test/java/io/github/wulkanowy/MainCoroutineRule.kt new file mode 100644 index 000000000..10724868a --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/MainCoroutineRule.kt @@ -0,0 +1,26 @@ +package io.github.wulkanowy + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.rules.TestWatcher +import org.junit.runner.Description + +@OptIn(ExperimentalCoroutinesApi::class) +class MainCoroutineRule( + private val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher() +) : TestWatcher() { + + override fun starting(description: Description?) { + super.starting(description) + Dispatchers.setMain(testDispatcher) + } + + override fun finished(description: Description?) { + super.finished(description) + Dispatchers.resetMain() + testDispatcher.cleanupTestCoroutines() + } +} diff --git a/app/src/test/java/io/github/wulkanowy/TestDispatchersProvider.kt b/app/src/test/java/io/github/wulkanowy/TestDispatchersProvider.kt new file mode 100644 index 000000000..e60b1d7a2 --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/TestDispatchersProvider.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy + +import io.github.wulkanowy.utils.DispatchersProvider +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers + +class TestDispatchersProvider : DispatchersProvider() { + + override val backgroundThread: CoroutineDispatcher + get() = Dispatchers.Unconfined +} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt index 977e82057..3c01a94c1 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.data.repositories.message -import androidx.room.EmptyResultSetException +import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.MessageWithAttachment import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.getMessageEntity @@ -10,6 +10,8 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.impl.annotations.MockK import io.mockk.just +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Before @@ -39,10 +41,10 @@ class MessageRepositoryTest { @Test fun `throw error when message is not in the db`() { val testMessage = getMessageEntity(1, "", false) - coEvery { local.getMessageWithAttachment(student, testMessage) } throws EmptyResultSetException("No message in database") + coEvery { local.getMessageWithAttachment(student, testMessage) } throws NullPointerException("No message in database") - val message = runCatching { runBlocking { repo.getMessage(student, testMessage) } } - assertEquals(EmptyResultSetException::class.java, message.exceptionOrNull()?.javaClass) + val message = runCatching { runBlocking { repo.getMessage(student, testMessage).toList()[1] } } + assertEquals(NullPointerException::class.java, message.exceptionOrNull()?.javaClass) } @Test @@ -50,11 +52,12 @@ class MessageRepositoryTest { val testMessage = getMessageEntity(123, "Test", false) val messageWithAttachment = MessageWithAttachment(testMessage, emptyList()) - coEvery { local.getMessageWithAttachment(student, testMessage) } returns messageWithAttachment + coEvery { local.getMessageWithAttachment(student, testMessage) } returns flowOf(messageWithAttachment) - val message = runBlocking { repo.getMessage(student, testMessage) } + val message = runBlocking { repo.getMessage(student, testMessage).toList() } - assertEquals("Test", message.message.content) + assertEquals(Status.SUCCESS, message[1].status) + assertEquals("Test", message[1].data!!.message.content) } @Test @@ -65,14 +68,15 @@ class MessageRepositoryTest { val mWa = MessageWithAttachment(testMessage, emptyList()) val mWaWithContent = MessageWithAttachment(testMessageWithContent, emptyList()) - coEvery { local.getMessageWithAttachment(student, testMessage) } returnsMany listOf(mWa, mWaWithContent) - coEvery { remote.getMessagesContentDetails(student, testMessageWithContent) } returns ("Test" to emptyList()) + coEvery { local.getMessageWithAttachment(student, testMessage) } returnsMany listOf(flowOf(mWa), flowOf(mWaWithContent)) + coEvery { remote.getMessagesContentDetails(student, any(), any()) } returns ("Test" to emptyList()) coEvery { local.updateMessages(any()) } just Runs coEvery { local.saveMessageAttachments(any()) } just Runs - val message = runBlocking { repo.getMessage(student, testMessage) } + val message = runBlocking { repo.getMessage(student, testMessage).toList() } - assertEquals("Test", message.message.content) + assertEquals(Status.SUCCESS, message[2].status) + assertEquals("Test", message[2].data!!.message.content) coVerify { local.updateMessages(listOf(testMessageWithContent)) } } @@ -83,7 +87,7 @@ class MessageRepositoryTest { coEvery { local.getMessageWithAttachment(student, testMessage) } throws UnknownHostException() - val message = runCatching { runBlocking { repo.getMessage(student, testMessage) } } + val message = runCatching { runBlocking { repo.getMessage(student, testMessage).toList()[1] } } assertEquals(UnknownHostException::class.java, message.exceptionOrNull()?.javaClass) } @@ -94,7 +98,7 @@ class MessageRepositoryTest { coEvery { local.getMessageWithAttachment(student, testMessage) } throws UnknownHostException() - val message = runCatching { runBlocking { repo.getMessage(student, testMessage) } } + val message = runCatching { runBlocking { repo.getMessage(student, testMessage).toList()[1] } } assertEquals(UnknownHostException::class.java, message.exceptionOrNull()?.javaClass) } } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRepositoryTest.kt index 665185a6b..89f5ba16e 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRepositoryTest.kt @@ -9,6 +9,8 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.impl.annotations.MockK import io.mockk.just +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Test @@ -42,12 +44,12 @@ class MobileDeviceRepositoryTest { getDeviceEntity(2) ) - coEvery { mobileDeviceLocal.getDevices(semester) } returns emptyList() + coEvery { mobileDeviceLocal.getDevices(semester) } returns flowOf(emptyList()) coEvery { mobileDeviceLocal.deleteDevices(emptyList()) } just Runs coEvery { mobileDeviceLocal.saveDevices(devices) } just Runs coEvery { mobileDeviceRemote.getDevices(student, semester) } returns devices - runBlocking { mobileDeviceRepository.getDevices(student, semester) } + runBlocking { mobileDeviceRepository.getDevices(student, semester, true).toList() } coVerify { mobileDeviceLocal.deleteDevices(emptyList()) } coVerify { mobileDeviceLocal.saveDevices(devices) } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/semester/SemesterRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/semester/SemesterRepositoryTest.kt index 161ce744f..2a2031034 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/semester/SemesterRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/semester/SemesterRepositoryTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories.semester +import io.github.wulkanowy.TestDispatchersProvider import io.github.wulkanowy.createSemesterEntity import io.github.wulkanowy.data.db.entities.Student import io.mockk.MockKAnnotations @@ -32,7 +33,7 @@ class SemesterRepositoryTest { @Before fun initTest() { MockKAnnotations.init(this) - semesterRepository = SemesterRepository(semesterRemote, semesterLocal) + semesterRepository = SemesterRepository(semesterRemote, semesterLocal, TestDispatchersProvider()) every { student.loginMode } returns "SCRAPPER" } diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt index 85f85a37c..819930815 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.ui.modules.grade import io.github.wulkanowy.createSemesterEntity +import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Student @@ -8,10 +9,13 @@ import io.github.wulkanowy.data.repositories.grade.GradeRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.flowWithResource import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.every import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Before @@ -22,6 +26,8 @@ import org.threeten.bp.LocalDateTime class GradeAverageProviderTest { + private suspend fun Flow>.getResult() = toList()[1].data!! + @MockK lateinit var preferencesRepository: PreferencesRepository @@ -93,14 +99,27 @@ class GradeAverageProviderTest { gradeAverageProvider = GradeAverageProvider(semesterRepository, gradeRepository, preferencesRepository) } + @Test + fun `force calc average on no grades`() { + every { preferencesRepository.gradeAverageForceCalc } returns true + every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS + + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { emptyList() to emptyList() } + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { emptyList() to emptyList() } + + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + + assertEquals(0, items.size) + } + @Test fun `force calc current semester average with default modifiers in scraper mode`() { every { preferencesRepository.gradeAverageForceCalc } returns true every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER coEvery { semesterRepository.getSemesters(student) } returns semesters - coEvery { gradeRepository.getGrades(student, semesters[2]) } returns (secondGradeWithModifier to secondSummariesWithModifier) + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGradeWithModifier to secondSummariesWithModifier } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId) } + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0) // from details and after set custom plus/minus } @@ -115,9 +134,9 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER coEvery { semesterRepository.getSemesters(student) } returns semesters - coEvery { gradeRepository.getGrades(student, semesters[2]) } returns (secondGradeWithModifier to secondSummariesWithModifier) + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGradeWithModifier to secondSummariesWithModifier } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId) } + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0) // from details and after set custom plus/minus } @@ -132,9 +151,9 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER coEvery { semesterRepository.getSemesters(student) } returns semesters - coEvery { gradeRepository.getGrades(student, semesters[2]) } returns (secondGradeWithModifier to secondSummariesWithModifier) + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGradeWithModifier to secondSummariesWithModifier } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId) } + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0) // (from details): 3.375 } @@ -149,9 +168,9 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER coEvery { semesterRepository.getSemesters(student) } returns semesters - coEvery { gradeRepository.getGrades(student, semesters[2]) } returns (secondGradeWithModifier to secondSummariesWithModifier) + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGradeWithModifier to secondSummariesWithModifier } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId) } + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0) // (from details): 3.375 } @@ -160,9 +179,9 @@ class GradeAverageProviderTest { fun `calc current semester average`() { every { preferencesRepository.gradeAverageForceCalc } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER - coEvery { gradeRepository.getGrades(student, semesters[2]) } returns (secondGrades to secondSummaries) + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to secondSummaries } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId) } + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } assertEquals(2, items.size) assertEquals(2.9, items.single { it.subject == "Matematyka" }.average, .0) // from summary: 2,9 @@ -173,9 +192,9 @@ class GradeAverageProviderTest { fun `force calc current semester average`() { every { preferencesRepository.gradeAverageForceCalc } returns true every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER - coEvery { gradeRepository.getGrades(student, semesters[2]) } returns (secondGrades to secondSummaries) + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to secondSummaries } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId) } + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } assertEquals(2, items.size) assertEquals(2.5, items.single { it.subject == "Matematyka" }.average, .0) // from details: 2,5 @@ -186,9 +205,9 @@ class GradeAverageProviderTest { fun `force calc full year average when current is first`() { every { preferencesRepository.gradeAverageForceCalc } returns true every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR - coEvery { gradeRepository.getGrades(student, semesters[1]) } returns (firstGrades to firstSummaries) + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to firstSummaries } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[1].semesterId) } + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[1].semesterId, true).getResult() } assertEquals(2, items.size) assertEquals(3.5, items.single { it.subject == "Matematyka" }.average, .0) // (from summary): 3,5 @@ -199,16 +218,20 @@ class GradeAverageProviderTest { fun `calc both semesters average`() { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS every { preferencesRepository.gradeAverageForceCalc } returns false - coEvery { gradeRepository.getGrades(student, semesters[1]) } returns (firstGrades to listOf( - getSummary(22, "Matematyka", 3.0), - getSummary(22, "Fizyka", 3.5) - )) - coEvery { gradeRepository.getGrades(student, semesters[2]) } returns (secondGrades to listOf( - getSummary(22, "Matematyka", 3.5), - getSummary(22, "Fizyka", 4.0) - )) + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { + firstGrades to listOf( + getSummary(22, "Matematyka", 3.0), + getSummary(22, "Fizyka", 3.5) + ) + } + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + secondGrades to listOf( + getSummary(22, "Matematyka", 3.5), + getSummary(22, "Fizyka", 4.0) + ) + } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId) } + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } assertEquals(2, items.size) assertEquals(3.25, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries ↑): 3,0 + 3,5 → 3,25 @@ -219,13 +242,15 @@ class GradeAverageProviderTest { fun `force calc full year average`() { every { preferencesRepository.gradeAverageForceCalc } returns true every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR - coEvery { gradeRepository.getGrades(student, semesters[1]) } returns (firstGrades to firstSummaries) - coEvery { gradeRepository.getGrades(student, semesters[2]) } returns (secondGrades to listOf( - getSummary(22, "Matematyka", 1.1), - getSummary(22, "Fizyka", 7.26) - )) + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to firstSummaries } + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + secondGrades to listOf( + getSummary(22, "Matematyka", 1.1), + getSummary(22, "Fizyka", 7.26) + ) + } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId) } + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } assertEquals(2, items.size) assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0 @@ -237,10 +262,10 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns true every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - coEvery { gradeRepository.getGrades(student, semesters[1]) } returns (firstGrades to emptyList()) - coEvery { gradeRepository.getGrades(student, semesters[2]) } returns (secondGrades to emptyList()) + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to emptyList() } + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to emptyList() } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId) } + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } assertEquals(2, items.size) assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0 @@ -252,10 +277,10 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns true every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR - coEvery { gradeRepository.getGrades(student, semesters[1]) } returns (firstGrades to emptyList()) - coEvery { gradeRepository.getGrades(student, semesters[2]) } returns (secondGrades to emptyList()) + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to emptyList() } + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to emptyList() } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId) } + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } assertEquals(2, items.size) assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0 @@ -267,14 +292,18 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - coEvery { gradeRepository.getGrades(student, semesters[1]) } returns (firstGrades to listOf( - getSummary(22, "Matematyka", 4.0) - )) - coEvery { gradeRepository.getGrades(student, semesters[2]) } returns (secondGrades to listOf( - getSummary(23, "Matematyka", 3.0) - )) + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { + firstGrades to listOf( + getSummary(22, "Matematyka", 4.0) + ) + } + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + secondGrades to listOf( + getSummary(23, "Matematyka", 3.0) + ) + } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId) } + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } assertEquals(2, items.size) assertEquals(3.5, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries ↑): 4,0 + 3,0 → 3,5 @@ -286,10 +315,10 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - coEvery { gradeRepository.getGrades(student, semesters[1]) } returns (firstGrades to firstSummaries) - coEvery { gradeRepository.getGrades(student, semesters[2]) } returns (secondGrades to secondSummaries.dropLast(1)) + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to firstSummaries } + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to secondSummaries.dropLast(1) } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId) } + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } assertEquals(2, items.size) assertEquals(3.4, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries): 3,9 + 2,9 → 3,4 @@ -301,10 +330,10 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - coEvery { gradeRepository.getGrades(student, semesters[1]) } returns (firstGrades to firstSummaries.dropLast(1)) - coEvery { gradeRepository.getGrades(student, semesters[2]) } returns (secondGrades to secondSummaries) + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to firstSummaries.dropLast(1) } + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to secondSummaries } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId) } + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } assertEquals(2, items.size) assertEquals(3.4, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries): 3,9 + 2,9 → 3,4 @@ -316,10 +345,10 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns true every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR - coEvery { gradeRepository.getGrades(student, semesters[1]) } returns (firstGrades to firstSummaries.dropLast(1)) - coEvery { gradeRepository.getGrades(student, semesters[2]) } returns (secondGrades to secondSummaries) + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to firstSummaries.dropLast(1) } + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to secondSummaries } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId) } + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } assertEquals(2, items.size) assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0 @@ -331,23 +360,27 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns true every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - coEvery { gradeRepository.getGrades(student, semesters[1]) } returns (listOf( - getGrade(22, "Fizyka", 5.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 5.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0) - ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0))) - coEvery { gradeRepository.getGrades(student, semesters[2]) } returns (listOf( - getGrade(23, "Fizyka", 5.0, weight = 1.0), - getGrade(23, "Fizyka", 5.0, weight = 2.0), - getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0) - ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0))) + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { + listOf( + getGrade(22, "Fizyka", 5.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 5.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0) + ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) + } + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + listOf( + getGrade(23, "Fizyka", 5.0, weight = 1.0), + getGrade(23, "Fizyka", 5.0, weight = 2.0), + getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0) + ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)) + } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId) } + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } assertEquals(5.2296, items.single { it.subject == "Fizyka" }.average, .0001) // (from details): 5.72727272 + 4,732 → 5.229636363636364 } @@ -357,23 +390,27 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns true every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR - coEvery { gradeRepository.getGrades(student, semesters[1]) } returns (listOf( - getGrade(22, "Fizyka", 5.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 5.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0) - ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0))) - coEvery { gradeRepository.getGrades(student, semesters[2]) } returns (listOf( - getGrade(23, "Fizyka", 5.0, weight = 1.0), - getGrade(23, "Fizyka", 5.0, weight = 2.0), - getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0) - ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0))) + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { + listOf( + getGrade(22, "Fizyka", 5.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 5.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0) + ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) + } + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + listOf( + getGrade(23, "Fizyka", 5.0, weight = 1.0), + getGrade(23, "Fizyka", 5.0, weight = 2.0), + getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0) + ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)) + } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId) } + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } assertEquals(5.5429, items.single { it.subject == "Fizyka" }.average, .0001) // (from details): 5.72727272 + 4,732 → .average() } @@ -389,23 +426,27 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS coEvery { semesterRepository.getSemesters(student) } returns semesters - coEvery { gradeRepository.getGrades(student, semesters[1]) } returns (listOf( - getGrade(22, "Fizyka", 5.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 5.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0) - ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0))) - coEvery { gradeRepository.getGrades(student, semesters[2]) } returns (listOf( - getGrade(23, "Fizyka", 5.0, weight = 1.0), - getGrade(23, "Fizyka", 5.0, weight = 2.0), - getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0) - ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0))) + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { + listOf( + getGrade(22, "Fizyka", 5.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 5.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0) + ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) + } + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + listOf( + getGrade(23, "Fizyka", 5.0, weight = 1.0), + getGrade(23, "Fizyka", 5.0, weight = 2.0), + getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0) + ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)) + } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId) } + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } assertEquals(5.2636, items.single { it.subject == "Fizyka" }.average, .0001) // (from details): 5.72727272 + 4,8 → 5.26363636 } @@ -421,23 +462,27 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR coEvery { semesterRepository.getSemesters(student) } returns semesters - coEvery { gradeRepository.getGrades(student, semesters[1]) } returns (listOf( - getGrade(22, "Fizyka", 5.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 5.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0) - ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0))) - coEvery { gradeRepository.getGrades(student, semesters[2]) } returns (listOf( - getGrade(23, "Fizyka", 5.0, weight = 1.0), - getGrade(23, "Fizyka", 5.0, weight = 2.0), - getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0) - ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0))) + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { + listOf( + getGrade(22, "Fizyka", 5.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 5.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0) + ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) + } + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + listOf( + getGrade(23, "Fizyka", 5.0, weight = 1.0), + getGrade(23, "Fizyka", 5.0, weight = 2.0), + getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0) + ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)) + } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId) } + val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } assertEquals(5.5555, items.single { it.subject == "Fizyka" }.average, .0001) // (from details): 5.72727272 + 4,8 → .average() } diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt index 6b15fb08b..02d7da34b 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.login.form +import io.github.wulkanowy.MainCoroutineRule import io.github.wulkanowy.TestSchedulersProvider import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.student.StudentRepository @@ -7,19 +8,22 @@ import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.mockk.MockKAnnotations import io.mockk.Runs -import io.mockk.clearAllMocks import io.mockk.coEvery import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.just import io.mockk.verify -import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.threeten.bp.LocalDateTime.now +import java.io.IOException class LoginFormPresenterTest { + @get:Rule + val coroutineRule = MainCoroutineRule() + @MockK(relaxed = true) lateinit var loginFormView: LoginFormView @@ -35,7 +39,7 @@ class LoginFormPresenterTest { private lateinit var presenter: LoginFormPresenter @Before - fun initPresenter() { + fun setUp() { MockKAnnotations.init(this) every { loginFormView.initView() } just Runs @@ -52,11 +56,6 @@ class LoginFormPresenterTest { presenter.onAttachView(loginFormView) } - @After - fun tearDown() { - clearAllMocks() - } - @Test fun initViewTest() { verify { loginFormView.initView() } @@ -111,9 +110,9 @@ class LoginFormPresenterTest { verify { loginFormView.hideSoftKeyboard() } verify { loginFormView.showProgress(true) } -// verify { loginFormView.showProgress(false) } -// verify { loginFormView.showContent(false) } -// verify { loginFormView.showContent(true) } + verify { loginFormView.showProgress(false) } + verify { loginFormView.showContent(false) } + verify { loginFormView.showContent(true) } } @Test @@ -151,7 +150,7 @@ class LoginFormPresenterTest { @Test fun loginErrorTest() { - val testException = RuntimeException("test") + val testException = IOException("test") coEvery { repository.getStudentsScrapper(any(), any(), any(), any()) } throws testException every { loginFormView.formUsernameValue } returns "@" every { loginFormView.formPassValue } returns "123456" @@ -162,10 +161,9 @@ class LoginFormPresenterTest { presenter.onSignInClick() verify { loginFormView.hideSoftKeyboard() } - verify { loginFormView.showProgress(true) } -// verify { loginFormView.showProgress(false) } -// verify { loginFormView.showContent(false) } -// verify { loginFormView.showContent(true) } -// verify { errorHandler.dispatch(testException) } + verify { loginFormView.showProgress(false) } + verify { loginFormView.showContent(false) } + verify { loginFormView.showContent(true) } + verify { errorHandler.dispatch(match { it.message == testException.message }) } } } diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt index e37642fd0..8b2523471 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.login.studentselect +import io.github.wulkanowy.MainCoroutineRule import io.github.wulkanowy.TestSchedulersProvider import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.student.StudentRepository @@ -12,15 +13,17 @@ import io.mockk.coEvery import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.just -import io.mockk.unmockkAll import io.mockk.verify -import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.threeten.bp.LocalDateTime.now class LoginStudentSelectPresenterTest { + @get:Rule + val coroutineRule = MainCoroutineRule() + @MockK(relaxed = true) lateinit var errorHandler: LoginErrorHandler @@ -40,8 +43,9 @@ class LoginStudentSelectPresenterTest { private val testException by lazy { RuntimeException("Problem") } @Before - fun initPresenter() { + fun setUp() { MockKAnnotations.init(this) + clearMocks(studentRepository, loginStudentSelectView) every { loginStudentSelectView.initView() } just Runs every { loginStudentSelectView.showContact(any()) } just Runs @@ -53,11 +57,6 @@ class LoginStudentSelectPresenterTest { presenter.onAttachView(loginStudentSelectView, null) } - @After - fun tearDown() { - unmockkAll() - } - @Test fun initViewTest() { verify { loginStudentSelectView.initView() } @@ -73,7 +72,7 @@ class LoginStudentSelectPresenterTest { verify { loginStudentSelectView.showContent(false) } verify { loginStudentSelectView.showProgress(true) } -// verify { loginStudentSelectView.openMainView() } + verify { loginStudentSelectView.openMainView() } } @Test @@ -84,6 +83,6 @@ class LoginStudentSelectPresenterTest { presenter.onSignIn() verify { loginStudentSelectView.showContent(false) } verify { loginStudentSelectView.showProgress(true) } - verify { errorHandler.dispatch(testException) } + verify { errorHandler.dispatch(match { testException.message == it.message }) } } } diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt index 9c7d605e1..eb4ac6386 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.splash +import io.github.wulkanowy.MainCoroutineRule import io.github.wulkanowy.TestSchedulersProvider import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.ErrorHandler @@ -8,10 +9,14 @@ import io.mockk.coEvery import io.mockk.impl.annotations.MockK import io.mockk.verify import org.junit.Before +import org.junit.Rule import org.junit.Test class SplashPresenterTest { + @get:Rule + val coroutineRule = MainCoroutineRule() + @MockK(relaxed = true) lateinit var splashView: SplashView @@ -24,7 +29,7 @@ class SplashPresenterTest { private lateinit var presenter: SplashPresenter @Before - fun initPresenter() { + fun setUp() { MockKAnnotations.init(this) presenter = SplashPresenter(TestSchedulersProvider(), errorHandler, studentRepository) }