Compare commits

...

6 Commits

Author SHA1 Message Date
Faierbel
2bae532f6d Merge branch 'develop' into bugfix/classid-zero 2024-05-05 10:46:47 +02:00
Faierbel
157e04b239 Add clearing all tables dependent on classId 2024-04-21 04:01:21 +02:00
Faierbel
2a0ac7f91e Add test for StudentDao 2024-04-14 17:48:44 +02:00
Faierbel
d7b1a08098 Merge branch 'develop' into bugfix/classid-zero 2024-04-14 16:32:24 +02:00
Faierbel
985be92a4d Fix tests 2024-04-14 02:57:05 +02:00
Faierbel
6616a313e2 Fix checking classId condition when is eduOne 2024-04-14 02:48:26 +02:00
24 changed files with 324 additions and 102 deletions

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.School import io.github.wulkanowy.data.db.entities.School
import io.github.wulkanowy.data.db.entities.Student
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import javax.inject.Singleton import javax.inject.Singleton
@ -11,5 +12,16 @@ import javax.inject.Singleton
interface SchoolDao : BaseDao<School> { interface SchoolDao : BaseDao<School> {
@Query("SELECT * FROM School WHERE student_id = :studentId AND class_id = :classId") @Query("SELECT * FROM School WHERE student_id = :studentId AND class_id = :classId")
fun load(studentId: Int, classId: Int): Flow<School?> fun loadWithClassId(studentId: Int, classId: Int): Flow<School?>
@Query("SELECT * FROM School WHERE student_id = :studentId")
fun loadNoClassId(studentId: Int): Flow<School?>
fun load(student: Student): Flow<School?> {
return if (student.isEduOne == true) {
loadNoClassId(student.studentId)
} else {
loadWithClassId(student.studentId, student.classId)
}
}
} }

View File

@ -5,6 +5,7 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
@ -14,6 +15,17 @@ interface SemesterDao : BaseDao<Semester> {
@Insert(onConflict = OnConflictStrategy.IGNORE) @Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertSemesters(items: List<Semester>): List<Long> suspend fun insertSemesters(items: List<Semester>): List<Long>
@Query("SELECT * FROM Semesters WHERE (student_id = :studentId AND class_id = :classId) OR (student_id = :studentId AND class_id = 0)") @Query("SELECT * FROM Semesters WHERE (student_id = :studentId AND class_id = :classId)")
suspend fun loadAll(studentId: Int, classId: Int): List<Semester> suspend fun loadAllWithClassId(studentId: Int, classId: Int): List<Semester>
@Query("SELECT * FROM Semesters WHERE student_id = :studentId")
suspend fun loadAllNoClassId(studentId: Int): List<Semester>
suspend fun loadAll(student: Student): List<Semester> {
return if (student.isEduOne == true) {
loadAllNoClassId(student.studentId)
} else {
loadAllWithClassId(student.studentId, student.classId)
}
}
} }

View File

@ -47,13 +47,9 @@ abstract class StudentDao {
abstract suspend fun loadAll(): List<Student> abstract suspend fun loadAll(): List<Student>
@Transaction @Transaction
@Query("SELECT * FROM Students JOIN Semesters ON (Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id) OR (Students.student_id = Semesters.student_id AND Semesters.class_id = 0)") @Query("SELECT * FROM Students JOIN Semesters ON (Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id)")
abstract suspend fun loadStudentsWithSemesters(): Map<Student, List<Semester>> abstract suspend fun loadStudentsWithSemesters(): Map<Student, List<Semester>>
@Transaction
@Query("SELECT * FROM Students JOIN Semesters ON (Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id) OR (Students.student_id = Semesters.student_id AND Semesters.class_id = 0) WHERE Students.id = :id")
abstract suspend fun loadStudentWithSemestersById(id: Long): Map<Student, List<Semester>>
@Query("UPDATE Students SET is_current = 1 WHERE id = :id") @Query("UPDATE Students SET is_current = 1 WHERE id = :id")
abstract suspend fun updateCurrent(id: Long) abstract suspend fun updateCurrent(id: Long)

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Teacher import io.github.wulkanowy.data.db.entities.Teacher
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import javax.inject.Singleton import javax.inject.Singleton
@ -11,5 +12,16 @@ import javax.inject.Singleton
interface TeacherDao : BaseDao<Teacher> { interface TeacherDao : BaseDao<Teacher> {
@Query("SELECT * FROM Teachers WHERE student_id = :studentId AND class_id = :classId") @Query("SELECT * FROM Teachers WHERE student_id = :studentId AND class_id = :classId")
fun loadAll(studentId: Int, classId: Int): Flow<List<Teacher>> fun loadAllWithClassId(studentId: Int, classId: Int): Flow<List<Teacher>>
@Query("SELECT * FROM Teachers WHERE student_id = :studentId")
fun loadAllNoClassId(studentId: Int): Flow<List<Teacher>>
fun loadAll(student: Student): Flow<List<Teacher>> {
return if (student.isEduOne == true) {
loadAllNoClassId(student.studentId)
} else {
loadAllWithClassId(student.studentId, student.classId)
}
}
} }

View File

@ -6,6 +6,15 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration63 : AutoMigrationSpec { class Migration63 : AutoMigrationSpec {
override fun onPostMigrate(db: SupportSQLiteDatabase) { override fun onPostMigrate(db: SupportSQLiteDatabase) {
db.execSQL("UPDATE Students SET is_edu_one = NULL WHERE is_edu_one = 0") db.execSQL("DROP TABLE IF EXISTS `Semesters`")
db.execSQL("DROP TABLE IF EXISTS `School`")
db.execSQL("DROP TABLE IF EXISTS `Teachers`")
db.execSQL("CREATE TABLE IF NOT EXISTS `Semesters` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)")
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `Semesters` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)")
db.execSQL("CREATE TABLE IF NOT EXISTS `School` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)")
db.execSQL("CREATE TABLE IF NOT EXISTS `Teachers` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)")
db.execSQL("UPDATE Students SET is_edu_one = NULL")
} }
} }

View File

@ -0,0 +1,4 @@
package io.github.wulkanowy.data.exceptions
class NoSuchStudentException(id: Long) :
Exception("There is no student with id $id in database")

View File

@ -36,7 +36,7 @@ class SchoolRepository @Inject constructor(
) )
it == null || forceRefresh || isExpired it == null || forceRefresh || isExpired
}, },
query = { schoolDb.load(semester.studentId, semester.classId) }, query = { schoolDb.load(student) },
fetch = { fetch = {
wulkanowySdkFactory.create(student, semester) wulkanowySdkFactory.create(student, semester)
.getSchool() .getSchool()

View File

@ -27,11 +27,11 @@ class SemesterRepository @Inject constructor(
forceRefresh: Boolean = false, forceRefresh: Boolean = false,
refreshOnNoCurrent: Boolean = false refreshOnNoCurrent: Boolean = false
) = withContext(dispatchers.io) { ) = withContext(dispatchers.io) {
val semesters = semesterDb.loadAll(student.studentId, student.classId) val semesters = semesterDb.loadAll(student)
if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) { if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) {
refreshSemesters(student) refreshSemesters(student)
semesterDb.loadAll(student.studentId, student.classId) semesterDb.loadAll(student)
} else semesters } else semesters
} }
@ -69,7 +69,7 @@ class SemesterRepository @Inject constructor(
return return
} }
val old = semesterDb.loadAll(student.studentId, student.classId) val old = semesterDb.loadAll(student)
semesterDb.removeOldAndSaveNew( semesterDb.removeOldAndSaveNew(
oldItems = old uniqueSubtract new, oldItems = old uniqueSubtract new,
newItems = new uniqueSubtract old, newItems = new uniqueSubtract old,

View File

@ -12,6 +12,7 @@ import io.github.wulkanowy.data.db.entities.StudentName
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
import io.github.wulkanowy.data.exceptions.NoSuchStudentException
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.mappers.mapToPojo import io.github.wulkanowy.data.mappers.mapToPojo
import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.pojos.RegisterUser
@ -65,7 +66,8 @@ class StudentRepository @Inject constructor(
.mapToPojo(password) .mapToPojo(password)
.also { it.logErrors() } .also { it.logErrors() }
suspend fun getSavedStudents(decryptPass: Boolean = true): List<StudentWithSemesters> { @Deprecated("Semesters are not synced within this method and students with empty semesters are not returned")
suspend fun getSavedStudentsWithSemesters(decryptPass: Boolean = true): List<StudentWithSemesters> {
return studentDb.loadStudentsWithSemesters().map { (student, semesters) -> return studentDb.loadStudentsWithSemesters().map { (student, semesters) ->
StudentWithSemesters( StudentWithSemesters(
student = student.apply { student = student.apply {
@ -80,22 +82,25 @@ class StudentRepository @Inject constructor(
} }
} }
suspend fun getSavedStudentById(id: Long, decryptPass: Boolean = true): StudentWithSemesters? = suspend fun getSavedStudents(decryptPass: Boolean = true): List<Student> {
studentDb.loadStudentWithSemestersById(id).let { res -> val students = studentDb.loadAll()
StudentWithSemesters( if (!decryptPass) return students
student = res.keys.firstOrNull() ?: return null,
semesters = res.values.first(), return students.map { student ->
) if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) {
}.apply { return@map student
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { }
student.password = withContext(dispatchers.io) {
student.apply {
password = withContext(dispatchers.io) {
scrambler.decrypt(student.password) scrambler.decrypt(student.password)
} }
} }
} }
}
suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student { suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student {
val student = studentDb.loadById(id) ?: throw NoCurrentStudentException() val student = studentDb.loadById(id) ?: throw NoSuchStudentException(id)
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
student.password = withContext(dispatchers.io) { student.password = withContext(dispatchers.io) {
@ -123,7 +128,7 @@ class StudentRepository @Inject constructor(
return return
} }
val currentStudentSemesters = semesterDb.loadAll(student.studentId, student.classId) val currentStudentSemesters = semesterDb.loadAll(student)
if (currentStudentSemesters.isEmpty()) { if (currentStudentSemesters.isEmpty()) {
Timber.d("Check isAuthorized: apply empty semesters workaround") Timber.d("Check isAuthorized: apply empty semesters workaround")
semesterDb.insertSemesters( semesterDb.insertSemesters(
@ -181,8 +186,8 @@ class StudentRepository @Inject constructor(
} }
} }
suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) { suspend fun switchStudent(student: Student) {
studentDb.switchCurrent(studentWithSemesters.student.id) studentDb.switchCurrent(student.id)
} }
suspend fun logoutStudent(student: Student) = studentDb.delete(student) suspend fun logoutStudent(student: Student) = studentDb.delete(student)
@ -190,8 +195,8 @@ class StudentRepository @Inject constructor(
suspend fun updateStudentNickAndAvatar(studentNickAndAvatar: StudentNickAndAvatar) = suspend fun updateStudentNickAndAvatar(studentNickAndAvatar: StudentNickAndAvatar) =
studentDb.update(studentNickAndAvatar) studentDb.update(studentNickAndAvatar)
suspend fun isOneUniqueStudent() = getSavedStudents(false) suspend fun isOneUniqueStudent() = studentDb.loadAll()
.distinctBy { it.student.studentName }.size == 1 .distinctBy { it.studentName }.size == 1
suspend fun authorizePermission(student: Student, semester: Semester, pesel: String) = suspend fun authorizePermission(student: Student, semester: Semester, pesel: String) =
wulkanowySdkFactory.create(student, semester) wulkanowySdkFactory.create(student, semester)
@ -199,7 +204,7 @@ class StudentRepository @Inject constructor(
suspend fun refreshStudentAfterAuthorize(student: Student, semester: Semester) { suspend fun refreshStudentAfterAuthorize(student: Student, semester: Semester) {
val wulkanowySdk = wulkanowySdkFactory.create(student, semester) val wulkanowySdk = wulkanowySdkFactory.create(student, semester)
val newCurrentApiStudent = runCatching { wulkanowySdk.getCurrentStudent() } val newCurrentApiStudent = runCatching { wulkanowySdk.getCurrentStudent() }
.onFailure { Timber.e(it, "Can't find student with id ${student.studentId}") } .onFailure { Timber.e(it, "Can't find student with id ${student.studentId}") }
.getOrNull() ?: return .getOrNull() ?: return
@ -209,7 +214,7 @@ class StudentRepository @Inject constructor(
studentDb.update(studentName) studentDb.update(studentName)
semesterDb.removeOldAndSaveNew( semesterDb.removeOldAndSaveNew(
oldItems = semesterDb.loadAll(student.studentId, semester.classId), oldItems = semesterDb.loadAll(student),
newItems = newCurrentApiStudent.semesters.mapToEntities(newCurrentApiStudent.studentId) newItems = newCurrentApiStudent.semesters.mapToEntities(newCurrentApiStudent.studentId)
) )
} }

View File

@ -35,7 +35,7 @@ class TeacherRepository @Inject constructor(
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
}, },
query = { teacherDb.loadAll(semester.studentId, semester.classId) }, query = { teacherDb.loadAll(student) },
fetch = { fetch = {
wulkanowySdkFactory.create(student, semester) wulkanowySdkFactory.create(student, semester)
.getTeachers() .getTeachers()

View File

@ -33,7 +33,7 @@ class AccountPresenter @Inject constructor(
} }
private fun loadData() { private fun loadData() {
resourceFlow { studentRepository.getSavedStudents(false) } resourceFlow { studentRepository.getSavedStudentsWithSemesters(false) }
.logResourceStatus("load account data") .logResourceStatus("load account data")
.onResourceSuccess { view?.updateData(createAccountItems(it)) } .onResourceSuccess { view?.updateData(createAccountItems(it)) }
.onResourceError(errorHandler::dispatch) .onResourceError(errorHandler::dispatch)

View File

@ -1,9 +1,15 @@
package io.github.wulkanowy.ui.modules.account.accountdetails package io.github.wulkanowy.ui.modules.account.accountdetails
import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.logResourceStatus
import io.github.wulkanowy.data.onResourceError
import io.github.wulkanowy.data.onResourceLoading
import io.github.wulkanowy.data.onResourceNotLoading
import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.services.sync.SyncManager
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
@ -14,6 +20,7 @@ import javax.inject.Inject
class AccountDetailsPresenter @Inject constructor( class AccountDetailsPresenter @Inject constructor(
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val semeRepository: SemesterRepository,
private val syncManager: SyncManager private val syncManager: SyncManager
) : BasePresenter<AccountDetailsView>(errorHandler, studentRepository) { ) : BasePresenter<AccountDetailsView>(errorHandler, studentRepository) {
@ -46,7 +53,12 @@ class AccountDetailsPresenter @Inject constructor(
} }
private fun loadData() { private fun loadData() {
resourceFlow { studentRepository.getSavedStudentById(studentId ?: -1) } resourceFlow {
val student = studentRepository.getStudentById(studentId ?: -1)
val semesters = semeRepository.getSemesters(student)
StudentWithSemesters(student, semesters)
}
.logResourceStatus("loading account details view") .logResourceStatus("loading account details view")
.onResourceLoading { .onResourceLoading {
view?.run { view?.run {
@ -85,7 +97,7 @@ class AccountDetailsPresenter @Inject constructor(
Timber.i("Select student ${studentWithSemesters!!.student.id}") Timber.i("Select student ${studentWithSemesters!!.student.id}")
resourceFlow { studentRepository.switchStudent(studentWithSemesters!!) } resourceFlow { studentRepository.switchStudent(studentWithSemesters!!.student) }
.logResourceStatus("change student") .logResourceStatus("change student")
.onResourceSuccess { view?.recreateMainView() } .onResourceSuccess { view?.recreateMainView() }
.onResourceNotLoading { view?.popViewToMain() } .onResourceNotLoading { view?.popViewToMain() }
@ -122,10 +134,12 @@ class AccountDetailsPresenter @Inject constructor(
syncManager.stopSyncWorker() syncManager.stopSyncWorker()
openClearLoginView() openClearLoginView()
} }
studentWithSemesters?.student?.isCurrent == true -> { studentWithSemesters?.student?.isCurrent == true -> {
Timber.i("Logout result: Logout student and switch to another") Timber.i("Logout result: Logout student and switch to another")
recreateMainView() recreateMainView()
} }
else -> { else -> {
Timber.i("Logout result: Logout student") Timber.i("Logout result: Logout student")
recreateMainView() recreateMainView()

View File

@ -1,8 +1,12 @@
package io.github.wulkanowy.ui.modules.account.accountquick package io.github.wulkanowy.ui.modules.account.accountquick
import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.logResourceStatus
import io.github.wulkanowy.data.onResourceError
import io.github.wulkanowy.data.onResourceNotLoading
import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.account.AccountItem import io.github.wulkanowy.ui.modules.account.AccountItem
@ -40,7 +44,7 @@ class AccountQuickPresenter @Inject constructor(
return return
} }
resourceFlow { studentRepository.switchStudent(studentWithSemesters) } resourceFlow { studentRepository.switchStudent(studentWithSemesters.student) }
.logResourceStatus("change student") .logResourceStatus("change student")
.onResourceSuccess { view?.recreateMainView() } .onResourceSuccess { view?.recreateMainView() }
.onResourceNotLoading { view?.popView() } .onResourceNotLoading { view?.popView() }

View File

@ -80,7 +80,7 @@ class LoginStudentSelectPresenter @Inject constructor(
private fun loadData() { private fun loadData() {
resetSelectedState() resetSelectedState()
resourceFlow { studentRepository.getSavedStudents(false) }.onEach { resourceFlow { studentRepository.getSavedStudentsWithSemesters(false) }.onEach {
students = it.dataOrNull.orEmpty() students = it.dataOrNull.orEmpty()
when (it) { when (it) {
is Resource.Loading -> Timber.d("Login student select students load started") is Resource.Loading -> Timber.d("Login student select students load started")

View File

@ -35,7 +35,7 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor(
} }
private fun loadData() { private fun loadData() {
resourceFlow { studentRepository.getSavedStudents(false) }.onEach { resourceFlow { studentRepository.getSavedStudentsWithSemesters(false) }.onEach {
when (it) { when (it) {
is Resource.Loading -> Timber.d("Lucky number widget configure students data load") is Resource.Loading -> Timber.d("Lucky number widget configure students data load")
is Resource.Success -> { is Resource.Success -> {

View File

@ -132,7 +132,7 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
private fun getLuckyNumber(studentId: Long, appWidgetId: Int) = runBlocking { private fun getLuckyNumber(studentId: Long, appWidgetId: Int) = runBlocking {
try { try {
val students = studentRepository.getSavedStudents() val students = studentRepository.getSavedStudents()
val student = students.singleOrNull { it.student.id == studentId }?.student val student = students.singleOrNull { it.id == studentId }
val currentStudent = when { val currentStudent = when {
student != null -> student student != null -> student
studentId != 0L && studentRepository.isCurrentStudentSet() -> { studentId != 0L && studentRepository.isCurrentStudentSet() -> {

View File

@ -85,7 +85,7 @@ class MainPresenter @Inject constructor(
return return
} }
resourceFlow { studentRepository.getSavedStudents(false) } resourceFlow { studentRepository.getSavedStudentsWithSemesters(false) }
.logResourceStatus("load student avatar") .logResourceStatus("load student avatar")
.onResourceSuccess { .onResourceSuccess {
studentsWitSemesters = it studentsWitSemesters = it

View File

@ -42,22 +42,24 @@ class TimetableWidgetConfigurePresenter @Inject constructor(
} }
private fun loadData() { private fun loadData() {
resourceFlow { studentRepository.getSavedStudents(false) }.onEach { resourceFlow { studentRepository.getSavedStudentsWithSemesters(false) }
when (it) { .onEach {
is Resource.Loading -> Timber.d("Timetable widget configure students data load") when (it) {
is Resource.Success -> { is Resource.Loading -> Timber.d("Timetable widget configure students data load")
val selectedStudentId = appWidgetId?.let { id -> is Resource.Success -> {
sharedPref.getLong(getStudentWidgetKey(id), 0) val selectedStudentId = appWidgetId?.let { id ->
} ?: -1 sharedPref.getLong(getStudentWidgetKey(id), 0)
when { } ?: -1
it.data.isEmpty() -> view?.openLoginView() when {
it.data.size == 1 && !isFromProvider -> onItemSelect(it.data.single().student) it.data.isEmpty() -> view?.openLoginView()
else -> view?.updateData(it.data, selectedStudentId) it.data.size == 1 && !isFromProvider -> onItemSelect(it.data.single().student)
else -> view?.updateData(it.data, selectedStudentId)
}
} }
is Resource.Error -> errorHandler.dispatch(it.error)
} }
is Resource.Error -> errorHandler.dispatch(it.error) }.launch()
}
}.launch()
} }
private fun registerStudent(student: Student?) { private fun registerStudent(student: Student?) {

View File

@ -95,7 +95,7 @@ class TimetableWidgetFactory(
private suspend fun getStudent(studentId: Long): Student? { private suspend fun getStudent(studentId: Long): Student? {
val students = studentRepository.getSavedStudents() val students = studentRepository.getSavedStudents()
return students.singleOrNull { it.student.id == studentId }?.student return students.singleOrNull { it.id == studentId }
} }
private suspend fun getLessons( private suspend fun getLessons(

View File

@ -2,7 +2,11 @@ package io.github.wulkanowy.ui.modules.timetablewidget
import android.app.PendingIntent import android.app.PendingIntent
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetManager.* import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_DELETED
import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE
import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID
import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS
import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -22,7 +26,14 @@ import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.services.widgets.TimetableWidgetService import io.github.wulkanowy.services.widgets.TimetableWidgetService
import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.splash.SplashActivity import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.PendingIntentCompat
import io.github.wulkanowy.utils.capitalise
import io.github.wulkanowy.utils.nextOrSameSchoolDay
import io.github.wulkanowy.utils.nextSchoolDay
import io.github.wulkanowy.utils.nickOrName
import io.github.wulkanowy.utils.previousSchoolDay
import io.github.wulkanowy.utils.toFormattedString
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -244,7 +255,7 @@ class TimetableWidgetProvider : BroadcastReceiver() {
private suspend fun getStudent(studentId: Long, appWidgetId: Int) = try { private suspend fun getStudent(studentId: Long, appWidgetId: Int) = try {
val students = studentRepository.getSavedStudents(false) val students = studentRepository.getSavedStudents(false)
val student = students.singleOrNull { it.student.id == studentId }?.student val student = students.singleOrNull { it.id == studentId }
when { when {
student != null -> student student != null -> student
studentId != 0L && studentRepository.isCurrentStudentSet() -> { studentId != 0L && studentRepository.isCurrentStudentSet() -> {
@ -263,7 +274,10 @@ class TimetableWidgetProvider : BroadcastReceiver() {
} }
private fun setupAccountView( private fun setupAccountView(
context: Context, student: Student, remoteViews: RemoteViews, widgetId: Int context: Context,
student: Student,
remoteViews: RemoteViews,
widgetId: Int
) { ) {
val accountInitials = getAccountInitials(student.nickOrName) val accountInitials = getAccountInitials(student.nickOrName)
val accountPickerPendingIntent = createAccountPickerPendingIntent(context, widgetId) val accountPickerPendingIntent = createAccountPickerPendingIntent(context, widgetId)

View File

@ -0,0 +1,161 @@
package io.github.wulkanowy.data.db.dao
import android.content.Context
import android.os.Build
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import dagger.hilt.android.testing.HiltTestApplication
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.getSemesterEntity
import io.github.wulkanowy.getStudentEntity
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.test.Test
import kotlin.test.assertEquals
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.O_MR1], application = HiltTestApplication::class)
class StudentDaoTest {
private lateinit var studentDao: StudentDao
private lateinit var semesterDao: SemesterDao
private lateinit var db: AppDatabase
@Before
fun createDb() {
val context: Context = ApplicationProvider.getApplicationContext()
db = Room.inMemoryDatabaseBuilder(
context = context,
klass = AppDatabase::class.java
).build()
studentDao = db.studentDao
semesterDao = db.semesterDao
}
@Test
fun `get students associated with correct semester with same studentId`() = runTest {
val notEduOneStudent = getStudentEntity()
.copy(
isEduOne = false,
classId = 42,
studentId = 100
)
.apply { id = 1 }
val eduOneStudent = getStudentEntity()
.copy(
isEduOne = true,
classId = 0,
studentId = 100
)
.apply { id = 2 }
val semesterAssociatedWithNotEduOneStudent = getSemesterEntity()
.copy(
studentId = notEduOneStudent.studentId,
classId = notEduOneStudent.classId,
diaryId = 1 // make semester unique
)
.apply { id = 0 }
val semesterAssociatedWithEduOneStudent = getSemesterEntity()
.copy(
studentId = eduOneStudent.studentId,
classId = eduOneStudent.classId,
diaryId = 2 // make semester unique
)
.apply { id = 0 }
studentDao.insertAll(listOf(notEduOneStudent, eduOneStudent))
semesterDao.insertAll(
listOf(
semesterAssociatedWithNotEduOneStudent,
semesterAssociatedWithEduOneStudent
)
)
val studentsWithSemesters = studentDao.loadStudentsWithSemesters()
val notEduOneSemestersResult = studentsWithSemesters.entries
.find { (student, _) -> student.id == notEduOneStudent.id }
?.value
val eduOneSemestersResult = studentsWithSemesters.entries
.find { (student, _) -> student.id == eduOneStudent.id }
?.value
assertEquals(2, studentsWithSemesters.size)
assertEquals(1, notEduOneSemestersResult?.size)
assertEquals(1, eduOneSemestersResult?.size)
assertEquals(semesterAssociatedWithEduOneStudent, eduOneSemestersResult?.firstOrNull())
assertEquals(
semesterAssociatedWithNotEduOneStudent,
notEduOneSemestersResult?.firstOrNull()
)
}
@Test
fun `get students associated with correct semester with different studentId`() = runTest {
val notEduOneStudent = getStudentEntity()
.copy(
isEduOne = false,
classId = 42,
studentId = 100
)
.apply { id = 1 }
val eduOneStudent = getStudentEntity()
.copy(
isEduOne = true,
classId = 0,
studentId = 101
)
.apply { id = 2 }
val semesterAssociatedWithNotEduOneStudent = getSemesterEntity()
.copy(
studentId = notEduOneStudent.studentId,
classId = notEduOneStudent.classId,
)
.apply { id = 0 }
val semesterAssociatedWithEduOneStudent = getSemesterEntity()
.copy(
studentId = eduOneStudent.studentId,
classId = eduOneStudent.classId,
)
.apply { id = 0 }
studentDao.insertAll(listOf(notEduOneStudent, eduOneStudent))
semesterDao.insertAll(
listOf(
semesterAssociatedWithNotEduOneStudent,
semesterAssociatedWithEduOneStudent
)
)
val studentsWithSemesters = studentDao.loadStudentsWithSemesters()
val notEduOneSemestersResult = studentsWithSemesters.entries
.find { (student, _) -> student.id == notEduOneStudent.id }
?.value
val eduOneSemestersResult = studentsWithSemesters.entries
.find { (student, _) -> student.id == eduOneStudent.id }
?.value
assertEquals(2, studentsWithSemesters.size)
assertEquals(1, notEduOneSemestersResult?.size)
assertEquals(1, eduOneSemestersResult?.size)
assertEquals(semesterAssociatedWithEduOneStudent, eduOneSemestersResult?.firstOrNull())
assertEquals(
semesterAssociatedWithNotEduOneStudent,
notEduOneSemestersResult?.firstOrNull()
)
}
@After
fun closeDb() {
db.close()
}
}

View File

@ -12,9 +12,7 @@ import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config import org.robolectric.annotation.Config
import kotlin.random.Random import kotlin.random.Random
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertNotNull
import kotlin.test.assertNull import kotlin.test.assertNull
import kotlin.test.assertTrue
@HiltAndroidTest @HiltAndroidTest
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@ -22,9 +20,10 @@ import kotlin.test.assertTrue
class Migration63Test : AbstractMigrationTest() { class Migration63Test : AbstractMigrationTest() {
@Test @Test
fun `update is_edu_one to null if 0`() = runTest { fun `update is_edu_one to null`() = runTest {
with(helper.createDatabase(dbName, 62)) { with(helper.createDatabase(dbName, 62)) {
createStudent(1, 0) createStudent(1, 0)
createStudent(2, 1)
close() close()
} }
@ -32,31 +31,15 @@ class Migration63Test : AbstractMigrationTest() {
val database = getMigratedRoomDatabase() val database = getMigratedRoomDatabase()
val studentDb = database.studentDao val studentDb = database.studentDao
val student = studentDb.loadById(1) val student1 = studentDb.loadById(1)
val student2 = studentDb.loadById(2)
assertNull(student!!.isEduOne) assertNull(student1!!.isEduOne)
assertNull(student2!!.isEduOne)
database.close() database.close()
} }
@Test
fun `check is_edu_one is stay same`() = runTest {
with(helper.createDatabase(dbName, 62)) {
createStudent(1, 1)
close()
}
helper.runMigrationsAndValidate(dbName, 63, true)
val database = getMigratedRoomDatabase()
val studentDb = database.studentDao
val student = studentDb.loadById(1)
val isEduOne = assertNotNull(student!!.isEduOne)
assertTrue(isEduOne)
database.close()
}
private fun SupportSQLiteDatabase.createStudent(id: Long, isEduOneValue: Int) { private fun SupportSQLiteDatabase.createStudent(id: Long, isEduOneValue: Int) {
insert("Students", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { insert("Students", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
put("scrapper_base_url", "https://fakelog.cf") put("scrapper_base_url", "https://fakelog.cf")

View File

@ -50,7 +50,7 @@ class SemesterRepositoryTest {
getSemesterPojo(1, 2, now().minusMonths(3), now()) getSemesterPojo(1, 2, now().minusMonths(3), now())
) )
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() coEvery { semesterDb.loadAll(student) } returns emptyList()
coEvery { sdk.getSemesters() } returns semesters coEvery { sdk.getSemesters() } returns semesters
coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs
@ -77,10 +77,7 @@ class SemesterRepositoryTest {
) )
coEvery { coEvery {
semesterDb.loadAll( semesterDb.loadAll(student)
student.studentId,
student.classId
)
} returns badSemesters.mapToEntities(student.studentId) } returns badSemesters.mapToEntities(student.studentId)
coEvery { sdk.getSemesters() } returns goodSemesters coEvery { sdk.getSemesters() } returns goodSemesters
coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs
@ -103,7 +100,7 @@ class SemesterRepositoryTest {
getSemesterPojo(2, 3, now(), now().plusMonths(6)), getSemesterPojo(2, 3, now(), now().plusMonths(6)),
) )
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returnsMany listOf( coEvery { semesterDb.loadAll(any()) } returnsMany listOf(
badSemesters.mapToEntities(student.studentId), badSemesters.mapToEntities(student.studentId),
badSemesters.mapToEntities(student.studentId), badSemesters.mapToEntities(student.studentId),
goodSemesters.mapToEntities(student.studentId) goodSemesters.mapToEntities(student.studentId)
@ -125,7 +122,7 @@ class SemesterRepositoryTest {
getSemesterEntity(1, 2, now().minusMonths(6), now().minusMonths(1)) getSemesterEntity(1, 2, now().minusMonths(6), now().minusMonths(1))
) )
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters coEvery { semesterDb.loadAll(student) } returns semesters
val items = runBlocking { semesterRepository.getSemesters(student) } val items = runBlocking { semesterRepository.getSemesters(student) }
assertEquals(2, items.size) assertEquals(2, items.size)
@ -138,7 +135,7 @@ class SemesterRepositoryTest {
getSemesterEntity(1, 2, now().minusMonths(3), now()) getSemesterEntity(1, 2, now().minusMonths(3), now())
) )
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters coEvery { semesterDb.loadAll(student) } returns semesters
val items = runBlocking { semesterRepository.getSemesters(student) } val items = runBlocking { semesterRepository.getSemesters(student) }
assertEquals(2, items.size) assertEquals(2, items.size)
@ -151,7 +148,7 @@ class SemesterRepositoryTest {
getSemesterEntity(1, 2, now(), now()) getSemesterEntity(1, 2, now(), now())
) )
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters coEvery { semesterDb.loadAll(student) } returns semesters
val items = runBlocking { semesterRepository.getSemesters(student) } val items = runBlocking { semesterRepository.getSemesters(student) }
assertEquals(2, items.size) assertEquals(2, items.size)
@ -164,7 +161,7 @@ class SemesterRepositoryTest {
getSemesterPojo(1, 2, now().minusMonths(3), now()) getSemesterPojo(1, 2, now().minusMonths(3), now())
) )
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() coEvery { semesterDb.loadAll(student) } returns emptyList()
coEvery { sdk.getSemesters() } returns semesters coEvery { sdk.getSemesters() } returns semesters
coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs
@ -194,10 +191,7 @@ class SemesterRepositoryTest {
) )
coEvery { coEvery {
semesterDb.loadAll( semesterDb.loadAll(student)
student.studentId,
student.classId
)
} returns semestersWithNoCurrent } returns semestersWithNoCurrent
coEvery { sdk.getSemesters() } returns newSemesters coEvery { sdk.getSemesters() } returns newSemesters
coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs
@ -214,7 +208,7 @@ class SemesterRepositoryTest {
getSemesterEntity(1, 2, now().minusMonths(1), now().plusMonths(1)) getSemesterEntity(1, 2, now().minusMonths(1), now().plusMonths(1))
) )
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters coEvery { semesterDb.loadAll(student) } returns semesters
val items = semesterRepository.getSemesters(student, refreshOnNoCurrent = true) val items = semesterRepository.getSemesters(student, refreshOnNoCurrent = true)
assertEquals(2, items.size) assertEquals(2, items.size)
@ -227,14 +221,14 @@ class SemesterRepositoryTest {
getSemesterEntity(1, 1, now(), now()) getSemesterEntity(1, 1, now(), now())
) )
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters coEvery { semesterDb.loadAll(student) } returns semesters
runBlocking { semesterRepository.getCurrentSemester(student) } runBlocking { semesterRepository.getCurrentSemester(student) }
} }
@Test(expected = RuntimeException::class) @Test(expected = RuntimeException::class)
fun getCurrentSemester_emptyList() { fun getCurrentSemester_emptyList() {
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() coEvery { semesterDb.loadAll(student) } returns emptyList()
coEvery { sdk.getSemesters() } returns emptyList() coEvery { sdk.getSemesters() } returns emptyList()
runBlocking { semesterRepository.getCurrentSemester(student) } runBlocking { semesterRepository.getCurrentSemester(student) }