Merge branch 'release/1.8.1'

This commit is contained in:
Mikołaj Pich 2022-11-20 00:05:38 +01:00
commit e03aae2d56
23 changed files with 441 additions and 128 deletions

View File

@ -34,7 +34,7 @@ Neoficiální klient deníku VULCAN UONET+ pro žáka a rodiče
* podpora více účtů s možností přejmenování žáků * podpora více účtů s možností přejmenování žáků
* tmavý a černý (AMOLED) motiv * tmavý a černý (AMOLED) motiv
* offline režim * offline režim
* žádné reklamy * volitelné reklamy na podporu projektu
## Stáhnout ## Stáhnout

View File

@ -21,7 +21,7 @@ Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre
* Prozentsatz der Anwesenheit * Prozentsatz der Anwesenheit
* Prüfungen * Prüfungen
* Stundenplan * Stundenplan
* Unterricht abgeschlossen * abgeschlossene Unterrichtsstunden
* Nachrichten * Nachrichten
* Hausaufgaben * Hausaufgaben
* Anmerkungen * Anmerkungen
@ -34,7 +34,7 @@ Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre
* Unterstützung für mehrere Konten mit der Möglichkeit, den Namen des Schülers zu ändern * Unterstützung für mehrere Konten mit der Möglichkeit, den Namen des Schülers zu ändern
* dunkles und schwarzes (AMOLED) Thema * dunkles und schwarzes (AMOLED) Thema
* Offline-Modus * Offline-Modus
* keine Werbung * optionale Werbungen, die es uns ermöglichen das Projekt zu unterstützen
## Herunterladen ## Herunterladen

View File

@ -34,7 +34,7 @@ Unofficial android VULCAN UONET+ register client for both students and their par
* support for multiple accounts with the ability to rename students * support for multiple accounts with the ability to rename students
* dark and black (AMOLED) theme * dark and black (AMOLED) theme
* offline mode * offline mode
* no ads * optional ads which allow to support the project
## Download ## Download

View File

@ -34,7 +34,7 @@ Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica
* obsługa wielu kont wraz z możliwością zmiany nazwy ucznia * obsługa wielu kont wraz z możliwością zmiany nazwy ucznia
* ciemny i czarny (AMOLED) motyw * ciemny i czarny (AMOLED) motyw
* tryb offline * tryb offline
* brak reklam * opcjonalne reklamy umożliwiające wsparcie projektu
## Pobierz ## Pobierz

View File

@ -34,7 +34,7 @@ Neoficiálny klient denníka VULCAN UONET+ pre žiaka a rodičov
* podpora viacerých účtov s možnosťou premenovania žiakov * podpora viacerých účtov s možnosťou premenovania žiakov
* tmavý a čierny (AMOLED) motív * tmavý a čierny (AMOLED) motív
* offline režim * offline režim
* žiadne reklamy * voliteľné reklamy na podporu projektu
## Stiahnuť ## Stiahnuť

View File

@ -23,8 +23,8 @@ android {
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 32 targetSdkVersion 32
versionCode 115 versionCode 116
versionName "1.8.0" versionName "1.8.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy" resValue "string", "app_name", "Wulkanowy"
@ -161,7 +161,7 @@ play {
defaultToAppBundles = false defaultToAppBundles = false
track = 'production' track = 'production'
releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS
userFraction = 0.25d userFraction = 0.10d
updatePriority = 4 updatePriority = 4
enabled.set(false) enabled.set(false)
} }
@ -186,7 +186,7 @@ ext {
} }
dependencies { dependencies {
implementation "io.github.wulkanowy:sdk:1.8.0" implementation "io.github.wulkanowy:sdk:1.8.1"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8'

View File

@ -49,8 +49,8 @@ fun <T, U> Resource<T>.mapData(block: (T) -> U) = when (this) {
fun <T> Flow<Resource<T>>.logResourceStatus(name: String, showData: Boolean = false) = onEach { fun <T> Flow<Resource<T>>.logResourceStatus(name: String, showData: Boolean = false) = onEach {
val description = when (it) { val description = when (it) {
is Resource.Loading -> "started"
is Resource.Intermediate -> "intermediate data received" + if (showData) " (data: `${it.data}`)" else "" is Resource.Intermediate -> "intermediate data received" + if (showData) " (data: `${it.data}`)" else ""
is Resource.Loading -> "started"
is Resource.Success -> "success" + if (showData) " (data: `${it.data}`)" else "" is Resource.Success -> "success" + if (showData) " (data: `${it.data}`)" else ""
is Resource.Error -> "exception occurred: ${it.error}" is Resource.Error -> "exception occurred: ${it.error}"
} }

View File

@ -13,4 +13,7 @@ interface TimetableDao : BaseDao<Timetable> {
@Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") @Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<Timetable>> fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<Timetable>>
@Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List<Timetable>
} }

View File

@ -3,17 +3,22 @@ package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.sdk.pojo.Attendance as SdkAttendance import io.github.wulkanowy.sdk.pojo.Attendance as SdkAttendance
import io.github.wulkanowy.sdk.pojo.AttendanceSummary as SdkAttendanceSummary import io.github.wulkanowy.sdk.pojo.AttendanceSummary as SdkAttendanceSummary
fun List<SdkAttendance>.mapToEntities(semester: Semester) = map { fun List<SdkAttendance>.mapToEntities(semester: Semester, lessons: List<Timetable>) = map {
Attendance( Attendance(
studentId = semester.studentId, studentId = semester.studentId,
diaryId = semester.diaryId, diaryId = semester.diaryId,
date = it.date, date = it.date,
timeId = it.timeId, timeId = it.timeId,
number = it.number, number = it.number,
subject = it.subject, subject = it.subject.ifBlank {
lessons.find { lesson ->
lesson.date == it.date && lesson.number == it.number
}?.subject.orEmpty()
},
name = it.name, name = it.name,
presence = it.presence, presence = it.presence,
absence = it.absence, absence = it.absence,

View File

@ -6,12 +6,18 @@ import io.github.wulkanowy.sdk.pojo.Message as SdkMessage
import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
fun List<SdkMessage>.mapToEntities(student: Student, mailbox: Mailbox?, allMailboxes: List<Mailbox>) = map { fun List<SdkMessage>.mapToEntities(
student: Student,
mailbox: Mailbox?,
allMailboxes: List<Mailbox>
): List<Message> = map {
Message( Message(
messageGlobalKey = it.globalKey, messageGlobalKey = it.globalKey,
mailboxKey = mailbox?.globalKey ?: allMailboxes.find { box -> mailboxKey = mailbox?.globalKey ?: allMailboxes.find { box ->
box.fullName == it.mailbox box.fullName == it.mailbox
}?.globalKey!!, }?.globalKey.let { mailboxKey ->
requireNotNull(mailboxKey) { "Can't find ${it.mailbox} in $allMailboxes" }
},
email = student.email, email = student.email,
messageId = it.id, messageId = it.id,
correspondents = it.correspondents, correspondents = it.correspondents,

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.AttendanceDao import io.github.wulkanowy.data.db.dao.AttendanceDao
import io.github.wulkanowy.data.db.dao.TimetableDao
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
@ -9,8 +10,10 @@ import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Absent import io.github.wulkanowy.sdk.pojo.Absent
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.withContext
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.LocalTime import java.time.LocalTime
@ -20,6 +23,7 @@ import javax.inject.Singleton
@Singleton @Singleton
class AttendanceRepository @Inject constructor( class AttendanceRepository @Inject constructor(
private val attendanceDb: AttendanceDao, private val attendanceDb: AttendanceDao,
private val timetableDb: TimetableDao,
private val sdk: Sdk, private val sdk: Sdk,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
) { ) {
@ -48,10 +52,15 @@ class AttendanceRepository @Inject constructor(
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
}, },
fetch = { fetch = {
val lessons = withContext(Dispatchers.IO) {
timetableDb.load(
semester.diaryId, semester.studentId, start.monday, end.sunday
)
}
sdk.init(student) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getAttendance(start.monday, end.sunday, semester.semesterId) .getAttendance(start.monday, end.sunday, semester.semesterId)
.mapToEntities(semester) .mapToEntities(semester, lessons)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
attendanceDb.deleteAll(old uniqueSubtract new) attendanceDb.deleteAll(old uniqueSubtract new)

View File

@ -3,7 +3,7 @@ package io.github.wulkanowy.data.repositories
import android.content.Context import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.dao.MailboxDao import io.github.wulkanowy.data.db.dao.MailboxDao
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
@ -14,9 +14,7 @@ import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
import io.github.wulkanowy.data.enums.MessageFolder.TRASHED import io.github.wulkanowy.data.enums.MessageFolder.TRASHED
import io.github.wulkanowy.data.mappers.mapFromEntities import io.github.wulkanowy.data.mappers.mapFromEntities
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.pojos.MessageDraft import io.github.wulkanowy.data.pojos.MessageDraft
import io.github.wulkanowy.data.toFirstResult
import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Folder import io.github.wulkanowy.sdk.pojo.Folder
@ -194,7 +192,9 @@ class MessageRepository @Inject constructor(
it.isEmpty() || isExpired || forceRefresh it.isEmpty() || isExpired || forceRefresh
}, },
query = { mailboxDao.loadAll(student.email, student.symbol, student.schoolSymbol) }, query = { mailboxDao.loadAll(student.email, student.symbol, student.schoolSymbol) },
fetch = { sdk.init(student).getMailboxes().mapToEntities(student) }, fetch = {
sdk.init(student).getMailboxes().mapToEntities(student)
},
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
mailboxDao.deleteAll(old uniqueSubtract new) mailboxDao.deleteAll(old uniqueSubtract new)
mailboxDao.insertAll(new uniqueSubtract old) mailboxDao.insertAll(new uniqueSubtract old)
@ -207,7 +207,11 @@ class MessageRepository @Inject constructor(
val mailbox = getMailboxByStudentUseCase(student) val mailbox = getMailboxByStudentUseCase(student)
return if (mailbox == null) { return if (mailbox == null) {
getMailboxes(student, forceRefresh = true).toFirstResult() getMailboxes(student, forceRefresh = true)
.onResourceError { throw it }
.onResourceSuccess { Timber.i("Found ${it.size} new mailboxes") }
.waitForResult()
getMailboxByStudentUseCase(student) getMailboxByStudentUseCase(student)
} else mailbox } else mailbox
} }

View File

@ -21,6 +21,8 @@ class GetMailboxByStudentUseCase @Inject constructor(
it.studentName.normalizeStudentName() == normalizedStudentName it.studentName.normalizeStudentName() == normalizedStudentName
} ?: singleOrNull { } ?: singleOrNull {
it.studentName.getFirstAndLastPart() == normalizedStudentName.getFirstAndLastPart() it.studentName.getFirstAndLastPart() == normalizedStudentName.getFirstAndLastPart()
} ?: singleOrNull {
it.studentName.getReversedName() == normalizedStudentName
} ?: singleOrNull { } ?: singleOrNull {
it.studentName.getUnauthorizedVersion() == normalizedStudentName it.studentName.getUnauthorizedVersion() == normalizedStudentName
} }
@ -43,6 +45,14 @@ class GetMailboxByStudentUseCase @Inject constructor(
return endParts.joinToString(" ") return endParts.joinToString(" ")
} }
private fun String.getReversedName(): String {
val parts = normalizeStudentName().split(" ")
return parts
.asReversed()
.joinToString(" ")
}
private fun String.getUnauthorizedVersion(): String { private fun String.getUnauthorizedVersion(): String {
return normalizeStudentName().split(" ") return normalizeStudentName().split(" ")
.joinToString(" ") { .joinToString(" ") {

View File

@ -33,18 +33,27 @@ sealed class DashboardItem(val type: Type) {
} }
data class HorizontalGroup( data class HorizontalGroup(
val unreadMessagesCount: Int? = null, val unreadMessagesCount: Cell<Int?>? = null,
val attendancePercentage: Double? = null, val attendancePercentage: Cell<Double>? = null,
val luckyNumber: Int? = null, val luckyNumber: Cell<Int>? = null,
override val error: Throwable? = null, override val error: Throwable? = null,
override val isLoading: Boolean = false override val isLoading: Boolean = false
) : DashboardItem(Type.HORIZONTAL_GROUP) { ) : DashboardItem(Type.HORIZONTAL_GROUP) {
data class Cell<T>(
val data: T?,
val error: Boolean,
val isLoading: Boolean,
) {
val isHidden: Boolean
get() = data == null && !error && !isLoading
}
override val isDataLoaded override val isDataLoaded
get() = unreadMessagesCount != null || attendancePercentage != null || luckyNumber != null get() = unreadMessagesCount?.isLoading == false || attendancePercentage?.isLoading == false || luckyNumber?.isLoading == false
val isFullDataLoaded val isFullDataLoaded
get() = luckyNumber != -1 && attendancePercentage != -1.0 && unreadMessagesCount != -1 get() = luckyNumber?.isLoading != true && attendancePercentage?.isLoading != true && unreadMessagesCount?.isLoading != true
} }
data class Grades( data class Grades(

View File

@ -226,50 +226,71 @@ class DashboardPresenter @Inject constructor(
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) { private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
flow { flow {
val semester = semesterRepository.getCurrentSemester(student)
val mailbox = messageRepository.getMailboxByStudent(student)
val selectedTiles = preferencesRepository.selectedDashboardTiles val selectedTiles = preferencesRepository.selectedDashboardTiles
val flowSuccess = flowOf(Resource.Success(null)) val flowSuccess = flowOf(Resource.Success(null))
val luckyNumberFlow = luckyNumberRepository.getLuckyNumber(student, forceRefresh) val luckyNumberFlow = luckyNumberRepository.getLuckyNumber(student, forceRefresh)
.mapResourceData { .mapResourceData {
it ?: LuckyNumber(0, LocalDate.now(), 0) it ?: LuckyNumber(0, LocalDate.now(), 0)
} }
.onResourceError { errorHandler.dispatch(it) }
.takeIf { DashboardItem.Tile.LUCKY_NUMBER in selectedTiles } ?: flowSuccess .takeIf { DashboardItem.Tile.LUCKY_NUMBER in selectedTiles } ?: flowSuccess
val messageFLow = messageRepository.getMessages( val messageFLow = flatResourceFlow {
student = student, val mailbox = messageRepository.getMailboxByStudent(student)
mailbox = mailbox,
folder = MessageFolder.RECEIVED,
forceRefresh = forceRefresh
).takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess
val attendanceFlow = attendanceSummaryRepository.getAttendanceSummary( messageRepository.getMessages(
student = student, student = student,
semester = semester, mailbox = mailbox,
subjectId = -1, folder = MessageFolder.RECEIVED,
forceRefresh = forceRefresh forceRefresh = forceRefresh
).takeIf { DashboardItem.Tile.ATTENDANCE in selectedTiles } ?: flowSuccess )
}
.onResourceError { errorHandler.dispatch(it) }
.takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess
val attendanceFlow = flatResourceFlow {
val semester = semesterRepository.getCurrentSemester(student)
attendanceSummaryRepository.getAttendanceSummary(
student = student,
semester = semester,
subjectId = -1,
forceRefresh = forceRefresh
)
}
.onResourceError { errorHandler.dispatch(it) }
.takeIf { DashboardItem.Tile.ATTENDANCE in selectedTiles } ?: flowSuccess
emitAll( emitAll(
combine( combine(
luckyNumberFlow, flow = luckyNumberFlow,
messageFLow, flow2 = messageFLow,
attendanceFlow flow3 = attendanceFlow,
) { luckyNumberResource, messageResource, attendanceResource -> ) { luckyNumberResource, messageResource, attendanceResource ->
val resList = listOf(luckyNumberResource, messageResource, attendanceResource) val resList = listOf(luckyNumberResource, messageResource, attendanceResource)
resList.firstNotNullOfOrNull { it.errorOrNull }?.let { throw it }
val isLoading = resList.any { it is Resource.Loading }
val luckyNumber = luckyNumberResource.dataOrNull?.luckyNumber
val messageCount = messageResource.dataOrNull?.count { it.unread }
val attendancePercentage = attendanceResource.dataOrNull?.calculatePercentage()
DashboardItem.HorizontalGroup( DashboardItem.HorizontalGroup(
isLoading = isLoading, isLoading = resList.any { it is Resource.Loading },
attendancePercentage = if (attendancePercentage == 0.0 && isLoading) -1.0 else attendancePercentage, error = resList.map { it.errorOrNull }.let { errors ->
unreadMessagesCount = if (messageCount == 0 && isLoading) -1 else messageCount, if (errors.all { it != null }) {
luckyNumber = if (luckyNumber == 0 && isLoading) -1 else luckyNumber errors.firstOrNull()
} else null
},
attendancePercentage = DashboardItem.HorizontalGroup.Cell(
data = attendanceResource.dataOrNull?.calculatePercentage(),
error = attendanceResource.errorOrNull != null,
isLoading = attendanceResource is Resource.Loading,
),
unreadMessagesCount = DashboardItem.HorizontalGroup.Cell(
data = messageResource.dataOrNull?.count { it.unread },
error = messageResource.errorOrNull != null,
isLoading = messageResource is Resource.Loading,
),
luckyNumber = DashboardItem.HorizontalGroup.Cell(
data = luckyNumberResource.dataOrNull?.luckyNumber,
error = luckyNumberResource.errorOrNull != null,
isLoading = luckyNumberResource is Resource.Loading,
)
) )
}) })
} }
@ -280,11 +301,8 @@ class DashboardPresenter @Inject constructor(
if (it.isLoading) { if (it.isLoading) {
Timber.i("Loading horizontal group data started") Timber.i("Loading horizontal group data started")
if (it.isFullDataLoaded) {
firstLoadedItemList += DashboardItem.Type.HORIZONTAL_GROUP
}
} else { } else {
firstLoadedItemList += DashboardItem.Type.HORIZONTAL_GROUP
Timber.i("Loading horizontal group result: Success") Timber.i("Loading horizontal group result: Success")
} }
} }

View File

@ -171,81 +171,105 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
position: Int position: Int
) { ) {
val item = items[position] as DashboardItem.HorizontalGroup val item = items[position] as DashboardItem.HorizontalGroup
val unreadMessagesCount = item.unreadMessagesCount
val attendancePercentage = item.attendancePercentage
val luckyNumber = item.luckyNumber
val error = item.error
val isLoading = item.isLoading
val binding = horizontalGroupViewHolder.binding
val context = binding.root.context
val isLoadingVisible = val isLoadingVisible =
(isLoading && !item.isDataLoaded) || (isLoading && !item.isFullDataLoaded) (item.isLoading && !item.isDataLoaded) || (item.isLoading && !item.isFullDataLoaded)
val isWideErrorShow = isLoadingVisible || item.error != null
with(horizontalGroupViewHolder.binding) {
dashboardHorizontalGroupItemInfoContainer.isVisible = isWideErrorShow
dashboardHorizontalGroupItemInfoProgress.isVisible = isLoadingVisible
dashboardHorizontalGroupItemInfoErrorText.isVisible = item.error != null
bindLuckyNumber(item, isWideErrorShow)
bindMessages(item, isWideErrorShow)
bindAttendance(item, isWideErrorShow)
}
}
private fun ItemDashboardHorizontalGroupBinding.bindLuckyNumber(
item: DashboardItem.HorizontalGroup,
isWideErrorShow: Boolean
) {
with(dashboardHorizontalGroupItemLuckyValue) {
isVisible = item.luckyNumber?.error != true
text = if (item.luckyNumber?.data == 0) {
context.getString(R.string.dashboard_horizontal_group_no_data)
} else item.luckyNumber?.data?.toString()
}
dashboardHorizontalGroupItemLuckyError.isVisible = item.luckyNumber?.error == true
with(dashboardHorizontalGroupItemLuckyContainer) {
isVisible = item.luckyNumber?.isHidden == false && !isWideErrorShow
setOnClickListener { onLuckyNumberTileClickListener() }
val isAttendanceHidden = item.attendancePercentage?.isHidden == true
val isMessagesHidden = item.unreadMessagesCount?.isHidden == true
val isLuckyNumberHidden = item.luckyNumber?.isHidden == true
updateLayoutParams<ViewGroup.MarginLayoutParams> {
updateMarginsRelative(
end = if (isAttendanceHidden && isMessagesHidden && !isLuckyNumberHidden) {
0
} else context.dpToPx(8f).toInt()
)
}
}
}
private fun ItemDashboardHorizontalGroupBinding.bindMessages(
item: DashboardItem.HorizontalGroup,
isWideErrorShow: Boolean
) {
dashboardHorizontalGroupItemMessageError.isVisible = item.unreadMessagesCount?.error == true
with(dashboardHorizontalGroupItemMessageValue) {
isVisible = item.unreadMessagesCount?.error != true
text = item.unreadMessagesCount?.data.toString()
}
with(dashboardHorizontalGroupItemMessageContainer) {
isVisible = item.unreadMessagesCount?.isHidden == false && !isWideErrorShow
setOnClickListener { onMessageTileClickListener() }
}
}
private fun ItemDashboardHorizontalGroupBinding.bindAttendance(
item: DashboardItem.HorizontalGroup,
isWideErrorShow: Boolean
) {
val attendancePercentage = item.attendancePercentage?.data
val attendanceColor = when { val attendanceColor = when {
attendancePercentage == null || attendancePercentage == .0 -> { attendancePercentage == null || attendancePercentage == .0 -> {
context.getThemeAttrColor(R.attr.colorOnSurface) root.context.getThemeAttrColor(R.attr.colorOnSurface)
} }
attendancePercentage <= ATTENDANCE_SECOND_WARNING_THRESHOLD -> { attendancePercentage <= ATTENDANCE_SECOND_WARNING_THRESHOLD -> {
context.getThemeAttrColor(R.attr.colorPrimary) root.context.getThemeAttrColor(R.attr.colorPrimary)
} }
attendancePercentage <= ATTENDANCE_FIRST_WARNING_THRESHOLD -> { attendancePercentage <= ATTENDANCE_FIRST_WARNING_THRESHOLD -> {
context.getThemeAttrColor(R.attr.colorTimetableChange) root.context.getThemeAttrColor(R.attr.colorTimetableChange)
} }
else -> context.getThemeAttrColor(R.attr.colorOnSurface) else -> root.context.getThemeAttrColor(R.attr.colorOnSurface)
} }
val attendanceString = if (attendancePercentage == null || attendancePercentage == .0) { val attendanceString = if (attendancePercentage == null || attendancePercentage == .0) {
context.getString(R.string.dashboard_horizontal_group_no_data) root.context.getString(R.string.dashboard_horizontal_group_no_data)
} else { } else {
"%.2f%%".format(attendancePercentage) "%.2f%%".format(attendancePercentage)
} }
with(binding.dashboardHorizontalGroupItemAttendanceValue) { dashboardHorizontalGroupItemAttendanceError.isVisible =
item.attendancePercentage?.error == true
with(dashboardHorizontalGroupItemAttendanceValue) {
isVisible = item.attendancePercentage?.error != true
text = attendanceString text = attendanceString
setTextColor(attendanceColor) setTextColor(attendanceColor)
} }
with(dashboardHorizontalGroupItemAttendanceContainer) {
with(binding) { isVisible = item.attendancePercentage?.isHidden == false && !isWideErrorShow
dashboardHorizontalGroupItemMessageValue.text = unreadMessagesCount.toString() setOnClickListener { onAttendanceTileClickListener() }
dashboardHorizontalGroupItemLuckyValue.text = if (luckyNumber == 0) { updateLayoutParams<ConstraintLayout.LayoutParams> {
context.getString(R.string.dashboard_horizontal_group_no_data) matchConstraintPercentWidth = when {
} else luckyNumber?.toString() item.luckyNumber?.isHidden == true && item.unreadMessagesCount?.isHidden == true -> 1.0f
item.luckyNumber?.isHidden == true || item.unreadMessagesCount?.isHidden == true -> 0.5f
dashboardHorizontalGroupItemInfoContainer.isVisible = error != null || isLoadingVisible else -> 0.4f
dashboardHorizontalGroupItemInfoProgress.isVisible = isLoadingVisible
dashboardHorizontalGroupItemInfoErrorText.isVisible = error != null
with(dashboardHorizontalGroupItemLuckyContainer) {
isVisible = luckyNumber != null && luckyNumber != -1 && !isLoadingVisible
setOnClickListener { onLuckyNumberTileClickListener() }
updateLayoutParams<ViewGroup.MarginLayoutParams> {
updateMarginsRelative(
end = if (attendancePercentage == null && unreadMessagesCount == null && luckyNumber != null) {
0
} else {
context.dpToPx(8f).toInt()
}
)
} }
} }
with(dashboardHorizontalGroupItemAttendanceContainer) {
isVisible =
attendancePercentage != null && attendancePercentage != -1.0 && !isLoadingVisible
updateLayoutParams<ConstraintLayout.LayoutParams> {
matchConstraintPercentWidth = when {
luckyNumber == null && unreadMessagesCount == null -> 1.0f
luckyNumber == null || unreadMessagesCount == null -> 0.5f
else -> 0.4f
}
}
setOnClickListener { onAttendanceTileClickListener() }
}
with(dashboardHorizontalGroupItemMessageContainer) {
isVisible =
unreadMessagesCount != null && unreadMessagesCount != -1 && !isLoadingVisible
setOnClickListener { onMessageTileClickListener() }
}
} }
} }

View File

@ -2,9 +2,11 @@ package io.github.wulkanowy.utils
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.res.ColorStateList
import android.graphics.* import android.graphics.*
import android.text.TextPaint import android.text.TextPaint
import android.util.DisplayMetrics.DENSITY_DEFAULT import android.util.DisplayMetrics.DENSITY_DEFAULT
import android.widget.ImageView
import androidx.annotation.* import androidx.annotation.*
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
@ -12,6 +14,7 @@ import androidx.core.graphics.applyCanvas
import androidx.core.graphics.drawable.RoundedBitmapDrawable import androidx.core.graphics.drawable.RoundedBitmapDrawable
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import androidx.core.widget.ImageViewCompat
@ColorInt @ColorInt
@ -85,3 +88,7 @@ fun Context.createNameInitialsDrawable(
return RoundedBitmapDrawableFactory.create(this.resources, bitmap) return RoundedBitmapDrawableFactory.create(this.resources, bitmap)
.apply { isCircular = true } .apply { isCircular = true }
} }
fun ImageView.setTint(@ColorInt color: Int) {
ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(color))
}

View File

@ -1,4 +1,4 @@
Wersja 1.8.0 Wersja 1.8.1
- naprawiliśmy liczenie średniej ucznia w ocenach klasy dla wykresu "Wszystkie" - naprawiliśmy liczenie średniej ucznia w ocenach klasy dla wykresu "Wszystkie"
- zmieniliśmy kolejność przycisków akcji w podglądzie wiadomości - zmieniliśmy kolejność przycisków akcji w podglądzie wiadomości

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"
android:fillColor="#000000"/>
</vector>

View File

@ -37,9 +37,25 @@
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginEnd="16dp"
app:tint="?colorOnSurface" app:tint="?colorOnSurface"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/dashboard_horizontal_group_item_lucky_error"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginEnd="-4dp"
android:layout_marginBottom="-4dp"
android:background="@drawable/ic_circle"
android:backgroundTint="?attr/colorSurface"
android:contentDescription="@string/error_unknown"
android:src="@drawable/ic_error_filled"
app:layout_constraintBottom_toBottomOf="@id/dashboard_horizontal_group_item_lucky_icon"
app:layout_constraintEnd_toEndOf="@id/dashboard_horizontal_group_item_lucky_icon"
app:tint="?colorError"
tools:visibility="visible" />
<TextView <TextView
android:id="@+id/dashboard_horizontal_group_item_lucky_value" android:id="@+id/dashboard_horizontal_group_item_lucky_value"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -89,9 +105,25 @@
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginEnd="16dp"
app:tint="?colorOnSurface" app:tint="?colorOnSurface"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/dashboard_horizontal_group_item_message_error"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginEnd="-4dp"
android:layout_marginBottom="-4dp"
android:background="@drawable/ic_circle"
android:backgroundTint="?attr/colorSurface"
android:contentDescription="@string/error_unknown"
android:src="@drawable/ic_error_filled"
app:layout_constraintBottom_toBottomOf="@id/dashboard_horizontal_group_item_message_icon"
app:layout_constraintEnd_toEndOf="@id/dashboard_horizontal_group_item_message_icon"
app:tint="?colorError"
tools:visibility="visible" />
<TextView <TextView
android:id="@+id/dashboard_horizontal_group_item_message_value" android:id="@+id/dashboard_horizontal_group_item_message_value"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -106,7 +138,8 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/dashboard_horizontal_group_item_message_icon" app:layout_constraintStart_toEndOf="@id/dashboard_horizontal_group_item_message_icon"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="16" /> tools:text="16"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
@ -145,9 +178,25 @@
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginEnd="16dp"
app:tint="?colorOnSurface" app:tint="?colorOnSurface"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/dashboard_horizontal_group_item_attendance_error"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginEnd="-4dp"
android:layout_marginBottom="-4dp"
android:background="@drawable/ic_circle"
android:backgroundTint="?attr/colorSurface"
android:contentDescription="@string/error_unknown"
android:src="@drawable/ic_error_filled"
app:layout_constraintBottom_toBottomOf="@id/dashboard_horizontal_group_item_attendance_icon"
app:layout_constraintEnd_toEndOf="@id/dashboard_horizontal_group_item_attendance_icon"
app:tint="?colorError"
tools:visibility="visible" />
<TextView <TextView
android:id="@+id/dashboard_horizontal_group_item_attendance_value" android:id="@+id/dashboard_horizontal_group_item_attendance_value"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -211,4 +260,4 @@
android:indeterminate="true" android:indeterminate="true"
app:indicatorSize="28dp" /> app:indicatorSize="28dp" />
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,143 @@
package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.sdk.pojo.Attendance
import io.github.wulkanowy.sdk.scrapper.attendance.SentExcuse
import org.junit.Test
import java.time.Instant
import java.time.LocalDate
import kotlin.test.assertEquals
class AttendanceMapperTest {
@Test
fun `map attendance when fallback is not necessary`() {
val attendance = listOf(
getSdkAttendance(1, LocalDate.of(2022, 11, 17), "Oryginalna 1"),
getSdkAttendance(2, LocalDate.of(2022, 11, 17), "Oryginalna 2"),
)
val lessons = listOf(
getEntityTimetable(1, LocalDate.of(2022, 11, 17), "Pierwsza"),
getEntityTimetable(2, LocalDate.of(2022, 11, 17), "Druga"),
)
val result = attendance.mapToEntities(getEntitySemester(), lessons)
assertEquals("Oryginalna 1", result[0].subject)
assertEquals("Oryginalna 2", result[1].subject)
}
@Test
fun `map attendance when fallback is not always necessary`() {
val attendance = listOf(
getSdkAttendance(1, LocalDate.of(2022, 11, 17), "Oryginalna 1"),
getSdkAttendance(2, LocalDate.of(2022, 11, 17), ""),
)
val lessons = listOf(
getEntityTimetable(1, LocalDate.of(2022, 11, 17), "Pierwsza"),
getEntityTimetable(2, LocalDate.of(2022, 11, 17), "Druga"),
)
val result = attendance.mapToEntities(getEntitySemester(), lessons)
assertEquals("Oryginalna 1", result[0].subject)
assertEquals("Druga", result[1].subject)
}
@Test
fun `map attendance when fallback is sometimes empty`() {
val attendance = listOf(
getSdkAttendance(1, LocalDate.of(2022, 11, 17), "Oryginalna 1"),
getSdkAttendance(2, LocalDate.of(2022, 11, 17), ""),
)
val lessons = listOf(
getEntityTimetable(1, LocalDate.of(2022, 11, 17), "Pierwsza"),
)
val result = attendance.mapToEntities(getEntitySemester(), lessons)
assertEquals("Oryginalna 1", result[0].subject)
assertEquals("", result[1].subject)
}
@Test
fun `map attendance when fallback is empty`() {
val attendance = listOf(
getSdkAttendance(1, LocalDate.of(2022, 11, 17), ""),
getSdkAttendance(2, LocalDate.of(2022, 11, 17), ""),
)
val lessons = listOf(
getEntityTimetable(1, LocalDate.of(2022, 11, 18), "Pierwsza"),
getEntityTimetable(2, LocalDate.of(2022, 10, 17), "Druga"),
)
val result = attendance.mapToEntities(getEntitySemester(), lessons)
assertEquals("", result[0].subject)
assertEquals("", result[1].subject)
}
@Test
fun `map attendance with all subject fallback`() {
val attendance = listOf(
getSdkAttendance(1, LocalDate.of(2022, 11, 17)),
getSdkAttendance(2, LocalDate.of(2022, 11, 17)),
)
val lessons = listOf(
getEntityTimetable(1, LocalDate.of(2022, 11, 17), "Pierwsza"),
getEntityTimetable(2, LocalDate.of(2022, 11, 17), "Druga"),
)
val result = attendance.mapToEntities(getEntitySemester(), lessons)
assertEquals("Pierwsza", result[0].subject)
assertEquals("Druga", result[1].subject)
}
private fun getSdkAttendance(number: Int, date: LocalDate, subject: String = "") = Attendance(
number = number,
name = "ABSENCE",
subject = subject,
date = date,
timeId = 1,
categoryId = 1,
deleted = false,
excuseStatus = SentExcuse.Status.WAITING,
excusable = false,
absence = false,
excused = false,
exemption = false,
lateness = false,
presence = false,
)
private fun getEntityTimetable(number: Int, date: LocalDate, subject: String = "") = Timetable(
number = number,
start = Instant.now(),
end = Instant.now(),
date = date,
subject = subject,
subjectOld = "",
group = "",
room = "",
roomOld = "",
teacher = "",
teacherOld = "",
info = "",
changes = false,
canceled = false,
studentId = 0,
diaryId = 0,
isStudentPlan = false,
)
private fun getEntitySemester() = Semester(
studentId = 0,
diaryId = 0,
kindergartenDiaryId = 0,
diaryName = "",
schoolYear = 0,
semesterId = 0,
semesterName = 0,
start = LocalDate.now(),
end = LocalDate.now(),
classId = 0,
unitId = 0
)
}

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.dao.AttendanceDao import io.github.wulkanowy.data.db.dao.AttendanceDao
import io.github.wulkanowy.data.db.dao.TimetableDao
import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.errorOrNull
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.data.toFirstResult
@ -29,6 +30,9 @@ class AttendanceRepositoryTest {
@MockK @MockK
private lateinit var attendanceDb: AttendanceDao private lateinit var attendanceDb: AttendanceDao
@MockK
private lateinit var timetableDb: TimetableDao
@MockK(relaxUnitFun = true) @MockK(relaxUnitFun = true)
private lateinit var refreshHelper: AutoRefreshHelper private lateinit var refreshHelper: AutoRefreshHelper
@ -51,8 +55,9 @@ class AttendanceRepositoryTest {
fun setUp() { fun setUp() {
MockKAnnotations.init(this) MockKAnnotations.init(this)
every { refreshHelper.shouldBeRefreshed(any()) } returns false every { refreshHelper.shouldBeRefreshed(any()) } returns false
coEvery { timetableDb.load(any(), any(), any(), any()) } returns emptyList()
attendanceRepository = AttendanceRepository(attendanceDb, sdk, refreshHelper) attendanceRepository = AttendanceRepository(attendanceDb, timetableDb, sdk, refreshHelper)
} }
@Test @Test
@ -60,8 +65,8 @@ class AttendanceRepositoryTest {
// prepare // prepare
coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList
coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
flowOf(remoteList.mapToEntities(semester)), flowOf(remoteList.mapToEntities(semester, emptyList())),
flowOf(remoteList.mapToEntities(semester)) flowOf(remoteList.mapToEntities(semester, emptyList()))
) )
coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3)
coEvery { attendanceDb.deleteAll(any()) } just Runs coEvery { attendanceDb.deleteAll(any()) } just Runs
@ -83,9 +88,9 @@ class AttendanceRepositoryTest {
// prepare // prepare
coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList
coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
flowOf(remoteList.dropLast(1).mapToEntities(semester)), flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())),
flowOf(remoteList.dropLast(1).mapToEntities(semester)), // after fetch end before save result flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())), // after fetch end before save result
flowOf(remoteList.mapToEntities(semester)) flowOf(remoteList.mapToEntities(semester, emptyList()))
) )
coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3)
coEvery { attendanceDb.deleteAll(any()) } just Runs coEvery { attendanceDb.deleteAll(any()) } just Runs
@ -100,7 +105,7 @@ class AttendanceRepositoryTest {
coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) }
coVerify { coVerify {
attendanceDb.insertAll(match { attendanceDb.insertAll(match {
it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] it.size == 1 && it[0] == remoteList.mapToEntities(semester, emptyList())[1]
}) })
} }
coVerify { attendanceDb.deleteAll(match { it.isEmpty() }) } coVerify { attendanceDb.deleteAll(match { it.isEmpty() }) }
@ -111,9 +116,9 @@ class AttendanceRepositoryTest {
// prepare // prepare
coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList.dropLast(1) coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList.dropLast(1)
coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
flowOf(remoteList.mapToEntities(semester)), flowOf(remoteList.mapToEntities(semester, emptyList())),
flowOf(remoteList.mapToEntities(semester)), // after fetch end before save result flowOf(remoteList.mapToEntities(semester, emptyList())), // after fetch end before save result
flowOf(remoteList.dropLast(1).mapToEntities(semester)) flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList()))
) )
coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3)
coEvery { attendanceDb.deleteAll(any()) } just Runs coEvery { attendanceDb.deleteAll(any()) } just Runs
@ -129,7 +134,7 @@ class AttendanceRepositoryTest {
coVerify { attendanceDb.insertAll(match { it.isEmpty() }) } coVerify { attendanceDb.insertAll(match { it.isEmpty() }) }
coVerify { coVerify {
attendanceDb.deleteAll(match { attendanceDb.deleteAll(match {
it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] it.size == 1 && it[0] == remoteList.mapToEntities(semester, emptyList())[1]
}) })
} }
} }

View File

@ -64,6 +64,18 @@ class GetMailboxByStudentUseCaseTest {
assertEquals(expectedMailbox, selectedMailbox) assertEquals(expectedMailbox, selectedMailbox)
} }
@Test
fun `get mailbox for user with reversed name`() = runTest {
val student = getStudentEntity(
userName = "Kowalski Jan",
studentName = "Jan Kowalski",
)
val expectedMailbox = getMailboxEntity("Kowalski Jan")
coEvery { mailboxDao.loadAll(any()) } returns listOf(expectedMailbox)
assertEquals(expectedMailbox, systemUnderTest(student))
}
@Test @Test
fun `get mailbox for unique non-authorized student`() = runTest { fun `get mailbox for unique non-authorized student`() = runTest {
val student = getStudentEntity( val student = getStudentEntity(