Dashboard fixes (#1463)

This commit is contained in:
Rafał Borcz 2021-09-05 23:24:03 +02:00 committed by GitHub
parent 2b55ec02ff
commit 77c5330f91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 433 additions and 420 deletions

View File

@ -33,10 +33,16 @@ class GradeRepository @Inject constructor(
private val cacheKey = "grade" private val cacheKey = "grade"
fun getGrades(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource( fun getGrades(
student: Student,
semester: Semester,
forceRefresh: Boolean,
notify: Boolean = false
) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
shouldFetch = { (details, summaries) -> shouldFetch = { (details, summaries) ->
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isShouldBeRefreshed =
refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
details.isEmpty() || summaries.isEmpty() || forceRefresh || isShouldBeRefreshed details.isEmpty() || summaries.isEmpty() || forceRefresh || isShouldBeRefreshed
}, },
query = { query = {
@ -59,8 +65,14 @@ class GradeRepository @Inject constructor(
} }
) )
private suspend fun refreshGradeDetails(student: Student, oldGrades: List<Grade>, newDetails: List<Grade>, notify: Boolean) { private suspend fun refreshGradeDetails(
val notifyBreakDate = oldGrades.maxByOrNull { it.date }?.date ?: student.registrationDate.toLocalDate() student: Student,
oldGrades: List<Grade>,
newDetails: List<Grade>,
notify: Boolean
) {
val notifyBreakDate =
oldGrades.maxByOrNull { it.date }?.date ?: student.registrationDate.toLocalDate()
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails) gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach { gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach {
if (it.date >= notifyBreakDate) it.apply { if (it.date >= notifyBreakDate) it.apply {
@ -70,10 +82,15 @@ class GradeRepository @Inject constructor(
}) })
} }
private suspend fun refreshGradeSummaries(oldSummaries: List<GradeSummary>, newSummary: List<GradeSummary>, notify: Boolean) { private suspend fun refreshGradeSummaries(
oldSummaries: List<GradeSummary>,
newSummary: List<GradeSummary>,
notify: Boolean
) {
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary) gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary)
gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary -> gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary ->
val oldSummary = oldSummaries.find { oldSummary -> oldSummary.subject == summary.subject } val oldSummary =
oldSummaries.find { oldSummary -> oldSummary.subject == summary.subject }
summary.isPredictedGradeNotified = when { summary.isPredictedGradeNotified = when {
summary.predictedGrade.isEmpty() -> true summary.predictedGrade.isEmpty() -> true
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false notify && oldSummary?.predictedGrade != summary.predictedGrade -> false

View File

@ -22,7 +22,11 @@ class SemesterRepository @Inject constructor(
private val dispatchers: DispatchersProvider private val dispatchers: DispatchersProvider
) { ) {
suspend fun getSemesters(student: Student, forceRefresh: Boolean = false, refreshOnNoCurrent: Boolean = false) = withContext(dispatchers.backgroundThread) { suspend fun getSemesters(
student: Student,
forceRefresh: Boolean = false,
refreshOnNoCurrent: Boolean = false
) = withContext(dispatchers.backgroundThread) {
val semesters = semesterDb.loadAll(student.studentId, student.classId) val semesters = semesterDb.loadAll(student.studentId, student.classId)
if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) { if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) {
@ -31,14 +35,21 @@ class SemesterRepository @Inject constructor(
} else semesters } else semesters
} }
private fun isShouldFetch(student: Student, semesters: List<Semester>, forceRefresh: Boolean, refreshOnNoCurrent: Boolean): Boolean { private fun isShouldFetch(
student: Student,
semesters: List<Semester>,
forceRefresh: Boolean,
refreshOnNoCurrent: Boolean
): Boolean {
val isNoSemesters = semesters.isEmpty() val isNoSemesters = semesters.isEmpty()
val isRefreshOnModeChangeRequired = if (Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) { val isRefreshOnModeChangeRequired =
semesters.firstOrNull { it.isCurrent }?.diaryId == 0 if (Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
} else false semesters.firstOrNull { it.isCurrent }?.diaryId == 0
} else false
val isRefreshOnNoCurrentAppropriate = refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent } val isRefreshOnNoCurrentAppropriate =
refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent }
return forceRefresh || isNoSemesters || isRefreshOnModeChangeRequired || isRefreshOnNoCurrentAppropriate return forceRefresh || isNoSemesters || isRefreshOnModeChangeRequired || isRefreshOnNoCurrentAppropriate
} }
@ -52,7 +63,8 @@ class SemesterRepository @Inject constructor(
semesterDb.insertSemesters(new.uniqueSubtract(old)) semesterDb.insertSemesters(new.uniqueSubtract(old))
} }
suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) = withContext(dispatchers.backgroundThread) { suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) =
getSemesters(student, forceRefresh).getCurrentOrLast() withContext(dispatchers.backgroundThread) {
} getSemesters(student, forceRefresh).getCurrentOrLast()
}
} }

View File

@ -18,7 +18,7 @@ open class BasePresenter<T : BaseView>(
protected val studentRepository: StudentRepository protected val studentRepository: StudentRepository
) : CoroutineScope { ) : CoroutineScope {
private var job: Job = Job() private var job = Job()
private val jobs = mutableMapOf<String, Job>() private val jobs = mutableMapOf<String, Job>()

View File

@ -8,7 +8,7 @@ import androidx.core.view.get
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.databinding.FragmentAccountBinding import io.github.wulkanowy.databinding.FragmentAccountBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
@ -75,9 +75,7 @@ class AccountFragment : BaseFragment<FragmentAccountBinding>(R.layout.fragment_a
} }
} }
override fun openAccountDetailsView(studentWithSemesters: StudentWithSemesters) { override fun openAccountDetailsView(student: Student) {
(activity as? MainActivity)?.pushView( (activity as? MainActivity)?.pushView(AccountDetailsFragment.newInstance(student))
AccountDetailsFragment.newInstance(studentWithSemesters)
)
} }
} }

View File

@ -28,7 +28,7 @@ class AccountPresenter @Inject constructor(
} }
fun onItemSelected(studentWithSemesters: StudentWithSemesters) { fun onItemSelected(studentWithSemesters: StudentWithSemesters) {
view?.openAccountDetailsView(studentWithSemesters) view?.openAccountDetailsView(studentWithSemesters.student)
} }
private fun loadData() { private fun loadData() {

View File

@ -1,6 +1,6 @@
package io.github.wulkanowy.ui.modules.account package io.github.wulkanowy.ui.modules.account
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface AccountView : BaseView { interface AccountView : BaseView {
@ -11,5 +11,5 @@ interface AccountView : BaseView {
fun openLoginView() fun openLoginView()
fun openAccountDetailsView(studentWithSemesters: StudentWithSemesters) fun openAccountDetailsView(student: Student)
} }

View File

@ -37,9 +37,9 @@ class AccountDetailsFragment :
private const val ARGUMENT_KEY = "Data" private const val ARGUMENT_KEY = "Data"
fun newInstance(studentWithSemesters: StudentWithSemesters) = fun newInstance(student: Student) =
AccountDetailsFragment().apply { AccountDetailsFragment().apply {
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, studentWithSemesters) } arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, student) }
} }
} }
@ -51,7 +51,7 @@ class AccountDetailsFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding = FragmentAccountDetailsBinding.bind(view) binding = FragmentAccountDetailsBinding.bind(view)
presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as StudentWithSemesters) presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student)
} }
override fun initView() { override fun initView() {

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.account.accountdetails
import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.services.sync.SyncManager
@ -27,9 +28,9 @@ class AccountDetailsPresenter @Inject constructor(
private var studentId: Long? = null private var studentId: Long? = null
fun onAttachView(view: AccountDetailsView, studentWithSemesters: StudentWithSemesters) { fun onAttachView(view: AccountDetailsView, student: Student) {
super.onAttachView(view) super.onAttachView(view)
studentId = studentWithSemesters.student.id studentId = student.id
view.initView() view.initView()
errorHandler.showErrorMessage = ::showErrorViewOnError errorHandler.showErrorMessage = ::showErrorViewOnError

View File

@ -14,6 +14,7 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.entities.TimetableHeader import io.github.wulkanowy.data.db.entities.TimetableHeader
import io.github.wulkanowy.databinding.ItemDashboardAccountBinding import io.github.wulkanowy.databinding.ItemDashboardAccountBinding
@ -41,7 +42,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
private var lessonsTimer: Timer? = null private var lessonsTimer: Timer? = null
var onAccountTileClickListener: () -> Unit = {} var onAccountTileClickListener: (Student) -> Unit = {}
var onLuckyNumberTileClickListener: () -> Unit = {} var onLuckyNumberTileClickListener: () -> Unit = {}
@ -152,7 +153,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
dashboardAccountItemName.text = student?.nickOrName.orEmpty() dashboardAccountItemName.text = student?.nickOrName.orEmpty()
dashboardAccountItemSchoolName.text = student?.schoolName.orEmpty() dashboardAccountItemSchoolName.text = student?.schoolName.orEmpty()
root.setOnClickListener { onAccountTileClickListener() } root.setOnClickListener { student?.let(onAccountTileClickListener) }
} }
} }
@ -170,38 +171,41 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
val binding = horizontalGroupViewHolder.binding val binding = horizontalGroupViewHolder.binding
val context = binding.root.context val context = binding.root.context
val attendanceColor = when { val attendanceColor = when {
attendancePercentage ?: 0.0 <= ATTENDANCE_SECOND_WARNING_THRESHOLD -> { attendancePercentage == null || attendancePercentage == .0 -> {
context.getThemeAttrColor(R.attr.colorOnSurface)
}
attendancePercentage <= ATTENDANCE_SECOND_WARNING_THRESHOLD -> {
context.getThemeAttrColor(R.attr.colorPrimary) context.getThemeAttrColor(R.attr.colorPrimary)
} }
attendancePercentage ?: 0.0 <= ATTENDANCE_FIRST_WARNING_THRESHOLD -> { attendancePercentage <= ATTENDANCE_FIRST_WARNING_THRESHOLD -> {
context.getThemeAttrColor(R.attr.colorTimetableChange) context.getThemeAttrColor(R.attr.colorTimetableChange)
} }
else -> context.getThemeAttrColor(R.attr.colorOnSurface) else -> context.getThemeAttrColor(R.attr.colorOnSurface)
} }
val attendanceString = if (attendancePercentage == null || attendancePercentage == .0) {
context.getString(R.string.dashboard_horizontal_group_no_data)
} else {
"%.2f%%".format(attendancePercentage)
}
with(binding.dashboardHorizontalGroupItemAttendanceValue) { with(binding.dashboardHorizontalGroupItemAttendanceValue) {
text = "%.2f%%".format(attendancePercentage) text = attendanceString
setTextColor(attendanceColor) setTextColor(attendanceColor)
} }
with(binding) { with(binding) {
dashboardHorizontalGroupItemMessageValue.text = unreadMessagesCount.toString() dashboardHorizontalGroupItemMessageValue.text = unreadMessagesCount.toString()
dashboardHorizontalGroupItemLuckyValue.text = if (luckyNumber == -1) { dashboardHorizontalGroupItemLuckyValue.text = if (luckyNumber == 0) {
context.getString(R.string.dashboard_horizontal_group_no_lukcy_number) context.getString(R.string.dashboard_horizontal_group_no_data)
} else luckyNumber?.toString() } else luckyNumber?.toString()
if (dashboardHorizontalGroupItemInfoContainer.isVisible != (error != null || isLoading)) { dashboardHorizontalGroupItemInfoContainer.isVisible = error != null || isLoading
dashboardHorizontalGroupItemInfoContainer.isVisible = error != null || isLoading dashboardHorizontalGroupItemInfoProgress.isVisible =
} (isLoading && !item.isDataLoaded) || (isLoading && !item.isFullDataLoaded)
if (dashboardHorizontalGroupItemInfoProgress.isVisible != isLoading) {
dashboardHorizontalGroupItemInfoProgress.isVisible = isLoading
}
dashboardHorizontalGroupItemInfoErrorText.isVisible = error != null dashboardHorizontalGroupItemInfoErrorText.isVisible = error != null
with(dashboardHorizontalGroupItemLuckyContainer) { with(dashboardHorizontalGroupItemLuckyContainer) {
isVisible = error == null && !isLoading && luckyNumber != null isVisible = luckyNumber != null && luckyNumber != -1
setOnClickListener { onLuckyNumberTileClickListener() } setOnClickListener { onLuckyNumberTileClickListener() }
updateLayoutParams<ViewGroup.MarginLayoutParams> { updateLayoutParams<ViewGroup.MarginLayoutParams> {
@ -216,7 +220,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
} }
with(dashboardHorizontalGroupItemAttendanceContainer) { with(dashboardHorizontalGroupItemAttendanceContainer) {
isVisible = error == null && !isLoading && attendancePercentage != null isVisible = attendancePercentage != null && attendancePercentage != -1.0
updateLayoutParams<ConstraintLayout.LayoutParams> { updateLayoutParams<ConstraintLayout.LayoutParams> {
matchConstraintPercentWidth = when { matchConstraintPercentWidth = when {
luckyNumber == null && unreadMessagesCount == null -> 1.0f luckyNumber == null && unreadMessagesCount == null -> 1.0f
@ -228,7 +232,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
} }
with(dashboardHorizontalGroupItemMessageContainer) { with(dashboardHorizontalGroupItemMessageContainer) {
isVisible = error == null && !isLoading && unreadMessagesCount != null isVisible = unreadMessagesCount != null && unreadMessagesCount != -1
setOnClickListener { onMessageTileClickListener() } setOnClickListener { onMessageTileClickListener() }
} }
} }
@ -291,14 +295,14 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
updateLessonView(item, currentTimetable, binding) updateLessonView(item, currentTimetable, binding)
binding.dashboardLessonsItemTitleTomorrow.isVisible = false binding.dashboardLessonsItemTitleTomorrow.isVisible = false
} }
currentDayHeader != null && currentDayHeader.content.isNotBlank() -> {
updateLessonView(item, emptyList(), binding, currentDayHeader)
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
}
tomorrowTimetable.isNotEmpty() -> { tomorrowTimetable.isNotEmpty() -> {
updateLessonView(item, tomorrowTimetable, binding) updateLessonView(item, tomorrowTimetable, binding)
binding.dashboardLessonsItemTitleTomorrow.isVisible = true binding.dashboardLessonsItemTitleTomorrow.isVisible = true
} }
currentDayHeader != null && currentDayHeader.content.isNotBlank() -> {
updateLessonView(item, emptyList(), binding, currentDayHeader)
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
}
tomorrowDayHeader != null && tomorrowDayHeader.content.isNotBlank() -> { tomorrowDayHeader != null && tomorrowDayHeader.content.isNotBlank() -> {
updateLessonView(item, emptyList(), binding, tomorrowDayHeader) updateLessonView(item, emptyList(), binding, tomorrowDayHeader)
binding.dashboardLessonsItemTitleTomorrow.isVisible = true binding.dashboardLessonsItemTitleTomorrow.isVisible = true
@ -348,6 +352,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
} }
} }
@SuppressLint("SetTextI18n")
private fun updateFirstLessonView( private fun updateFirstLessonView(
binding: ItemDashboardLessonsBinding, binding: ItemDashboardLessonsBinding,
firstLesson: Timetable?, firstLesson: Timetable?,
@ -367,7 +372,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
firstLesson ?: return firstLesson ?: return
val minutesToStartLesson = val minutesToStartLesson =
Duration.between(currentDateTime, firstLesson.start).toMinutes() Duration.between(currentDateTime, firstLesson.start).toMinutes() + 1
val isFirstTimeVisible: Boolean val isFirstTimeVisible: Boolean
val isFirstTimeRangeVisible: Boolean val isFirstTimeRangeVisible: Boolean
val firstTimeText: String val firstTimeText: String
@ -376,12 +381,12 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
val firstTitleAndValueTextColor: Int val firstTitleAndValueTextColor: Int
val firstTitleAndValueTextFont: Typeface val firstTitleAndValueTextFont: Typeface
if (currentDateTime.isBefore(firstLesson.start)) { if (currentDateTime < firstLesson.start) {
if (minutesToStartLesson > 60) { if (minutesToStartLesson > 60) {
val formattedStartTime = firstLesson.start.toFormattedString("HH:mm") val formattedStartTime = firstLesson.start.toFormattedString("HH:mm")
val formattedEndTime = firstLesson.end.toFormattedString("HH:mm") val formattedEndTime = firstLesson.end.toFormattedString("HH:mm")
firstTimeRangeText = "${formattedStartTime}-${formattedEndTime}" firstTimeRangeText = "$formattedStartTime - $formattedEndTime"
firstTimeText = "" firstTimeText = ""
isFirstTimeRangeVisible = true isFirstTimeRangeVisible = true
@ -421,7 +426,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
} }
} }
} else { } else {
val minutesToEndLesson = firstLesson.left!!.toMinutes() val minutesToEndLesson = firstLesson.left!!.toMinutes() + 1
firstTimeText = context.resources.getQuantityString( firstTimeText = context.resources.getQuantityString(
R.plurals.dashboard_timetable_first_lesson_time_more_minutes, R.plurals.dashboard_timetable_first_lesson_time_more_minutes,
@ -454,11 +459,8 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
with(binding.dashboardLessonsItemFirstValue) { with(binding.dashboardLessonsItemFirstValue) {
setTextColor(firstTitleAndValueTextColor) setTextColor(firstTitleAndValueTextColor)
typeface = firstTitleAndValueTextFont typeface = firstTitleAndValueTextFont
text = context.getString( text =
R.string.dashboard_timetable_lesson_value, "${firstLesson.subject} ${if (firstLesson.room.isNotBlank()) "(${firstLesson.room})" else ""}"
firstLesson.subject,
firstLesson.room
)
} }
} }
@ -472,13 +474,11 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
val formattedStartTime = secondLesson?.start?.toFormattedString("HH:mm") val formattedStartTime = secondLesson?.start?.toFormattedString("HH:mm")
val formattedEndTime = secondLesson?.end?.toFormattedString("HH:mm") val formattedEndTime = secondLesson?.end?.toFormattedString("HH:mm")
val secondTimeText = "${formattedStartTime}-${formattedEndTime}" val secondTimeText = "$formattedStartTime - $formattedEndTime"
val secondValueText = if (secondLesson != null) { val secondValueText = if (secondLesson != null) {
context.getString( val roomString = if (secondLesson.room.isNotBlank()) "(${secondLesson.room})" else ""
R.string.dashboard_timetable_lesson_value,
secondLesson.subject, "${secondLesson.subject} $roomString"
secondLesson.room
)
} else { } else {
context.getString(R.string.dashboard_timetable_second_lesson_value_end) context.getString(R.string.dashboard_timetable_second_lesson_value_end)
} }

View File

@ -14,7 +14,7 @@ import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.databinding.FragmentDashboardBinding import io.github.wulkanowy.databinding.FragmentDashboardBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.account.AccountFragment import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
import io.github.wulkanowy.ui.modules.exam.ExamFragment import io.github.wulkanowy.ui.modules.exam.ExamFragment
@ -77,7 +77,9 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
) )
dashboardAdapter.apply { dashboardAdapter.apply {
onAccountTileClickListener = { mainActivity.pushView(AccountFragment.newInstance()) } onAccountTileClickListener = {
mainActivity.pushView(AccountDetailsFragment.newInstance(it))
}
onLuckyNumberTileClickListener = { onLuckyNumberTileClickListener = {
mainActivity.pushView(LuckyNumberFragment.newInstance()) mainActivity.pushView(LuckyNumberFragment.newInstance())
} }

View File

@ -35,6 +35,9 @@ sealed class DashboardItem(val type: Type) {
override val isDataLoaded override val isDataLoaded
get() = unreadMessagesCount != null || attendancePercentage != null || luckyNumber != null get() = unreadMessagesCount != null || attendancePercentage != null || luckyNumber != null
val isFullDataLoaded
get() = luckyNumber != -1 && attendancePercentage != -1.0 && unreadMessagesCount != -1
} }
data class Grades( data class Grades(

View File

@ -2,6 +2,8 @@ package io.github.wulkanowy.ui.modules.dashboard
import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository
import io.github.wulkanowy.data.repositories.ConferenceRepository import io.github.wulkanowy.data.repositories.ConferenceRepository
@ -18,11 +20,18 @@ import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.calculatePercentage import io.github.wulkanowy.utils.calculatePercentage
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.flowWithResourceIn import io.github.wulkanowy.utils.flowWithResourceIn
import io.github.wulkanowy.utils.nextOrSameSchoolDay import io.github.wulkanowy.utils.nextOrSameSchoolDay
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
@ -48,9 +57,11 @@ class DashboardPresenter @Inject constructor(
private val dashboardItemRefreshLoadedList = mutableListOf<DashboardItem>() private val dashboardItemRefreshLoadedList = mutableListOf<DashboardItem>()
private lateinit var dashboardItemsToLoad: Set<DashboardItem.Type> private var dashboardItemsToLoad = emptySet<DashboardItem.Type>()
private var dashboardTilesToLoad: Set<DashboardItem.Tile> = emptySet() private var dashboardTileLoadedList = emptySet<DashboardItem.Tile>()
private val firstLoadedItemList = mutableListOf<DashboardItem.Type>()
private lateinit var lastError: Throwable private lateinit var lastError: Throwable
@ -69,8 +80,10 @@ class DashboardPresenter @Inject constructor(
} }
fun onDragAndDropEnd(list: List<DashboardItem>) { fun onDragAndDropEnd(list: List<DashboardItem>) {
dashboardItemLoadedList.clear() with(dashboardItemLoadedList) {
dashboardItemLoadedList.addAll(list) clear()
addAll(list)
}
val positionList = val positionList =
list.mapIndexed { index, dashboardItem -> Pair(dashboardItem.type, index) }.toMap() list.mapIndexed { index, dashboardItem -> Pair(dashboardItem.type, index) }.toMap()
@ -78,87 +91,102 @@ class DashboardPresenter @Inject constructor(
preferencesRepository.dashboardItemsPosition = positionList preferencesRepository.dashboardItemsPosition = positionList
} }
fun loadData(forceRefresh: Boolean = false, tilesToLoad: Set<DashboardItem.Tile>) { fun loadData(
val oldDashboardDataToLoad = dashboardTilesToLoad tilesToLoad: Set<DashboardItem.Tile>,
forceRefresh: Boolean = false,
) {
val oldDashboardTileLoadedList = dashboardTileLoadedList
dashboardItemsToLoad = tilesToLoad.map { it.toDashboardItemType() }.toSet()
dashboardTileLoadedList = tilesToLoad
dashboardTilesToLoad = tilesToLoad val itemsToLoad = generateDashboardTileListToLoad(
dashboardItemsToLoad = dashboardTilesToLoad.map { it.toDashboardItemType() }.toSet() dashboardTilesToLoad = tilesToLoad,
dashboardLoadedTiles = oldDashboardTileLoadedList,
forceRefresh = forceRefresh
).map { it.toDashboardItemType() }
removeUnselectedTiles() removeUnselectedTiles(tilesToLoad.toList())
loadTiles(tileList = itemsToLoad, forceRefresh = forceRefresh)
val newTileList = generateTileListToLoad(oldDashboardDataToLoad, forceRefresh)
loadTiles(forceRefresh, newTileList)
} }
private fun removeUnselectedTiles() { private fun generateDashboardTileListToLoad(
val isLuckyNumberToLoad = dashboardTilesToLoad: Set<DashboardItem.Tile>,
dashboardTilesToLoad.any { it == DashboardItem.Tile.LUCKY_NUMBER } dashboardLoadedTiles: Set<DashboardItem.Tile>,
val isMessagesToLoad = forceRefresh: Boolean
dashboardTilesToLoad.any { it == DashboardItem.Tile.MESSAGES } ) = dashboardTilesToLoad.filter { newItemToLoad ->
val isAttendanceToLoad = dashboardLoadedTiles.none { it == newItemToLoad } || forceRefresh
dashboardTilesToLoad.any { it == DashboardItem.Tile.ATTENDANCE } }
private fun removeUnselectedTiles(tilesToLoad: List<DashboardItem.Tile>) {
dashboardItemLoadedList.removeAll { loadedTile -> dashboardItemsToLoad.none { it == loadedTile.type } } dashboardItemLoadedList.removeAll { loadedTile -> dashboardItemsToLoad.none { it == loadedTile.type } }
val horizontalGroup = val horizontalGroup =
dashboardItemLoadedList.find { it is DashboardItem.HorizontalGroup } as DashboardItem.HorizontalGroup? dashboardItemLoadedList.find { it is DashboardItem.HorizontalGroup } as DashboardItem.HorizontalGroup?
if (horizontalGroup != null) { if (horizontalGroup != null) {
val horizontalIndex = dashboardItemLoadedList.indexOf(horizontalGroup) val isLuckyNumberToLoad = DashboardItem.Tile.LUCKY_NUMBER in tilesToLoad
dashboardItemLoadedList.remove(horizontalGroup) val isMessagesToLoad = DashboardItem.Tile.MESSAGES in tilesToLoad
val isAttendanceToLoad = DashboardItem.Tile.ATTENDANCE in tilesToLoad
var updatedHorizontalGroup = horizontalGroup val horizontalGroupIndex = dashboardItemLoadedList.indexOf(horizontalGroup)
if (horizontalGroup.luckyNumber != null && !isLuckyNumberToLoad) { val newHorizontalGroup = horizontalGroup.copy(
updatedHorizontalGroup = updatedHorizontalGroup.copy(luckyNumber = null) attendancePercentage = horizontalGroup.attendancePercentage.takeIf { isAttendanceToLoad },
unreadMessagesCount = horizontalGroup.unreadMessagesCount.takeIf { isMessagesToLoad },
luckyNumber = horizontalGroup.luckyNumber.takeIf { isLuckyNumberToLoad }
)
with(dashboardItemLoadedList) {
removeAt(horizontalGroupIndex)
add(horizontalGroupIndex, newHorizontalGroup)
} }
if (horizontalGroup.attendancePercentage != null && !isAttendanceToLoad) {
updatedHorizontalGroup = updatedHorizontalGroup.copy(attendancePercentage = null)
}
if (horizontalGroup.unreadMessagesCount != null && !isMessagesToLoad) {
updatedHorizontalGroup = updatedHorizontalGroup.copy(unreadMessagesCount = null)
}
if (horizontalGroup.error != null) {
updatedHorizontalGroup = updatedHorizontalGroup.copy(error = null, isLoading = true)
}
dashboardItemLoadedList.add(horizontalIndex, updatedHorizontalGroup)
} }
view?.updateData(dashboardItemLoadedList) view?.updateData(dashboardItemLoadedList)
} }
private fun loadTiles(forceRefresh: Boolean, tileList: List<DashboardItem.Tile>) { private fun loadTiles(
tileList.forEach { tileList: List<DashboardItem.Type>,
when (it) { forceRefresh: Boolean
DashboardItem.Tile.ACCOUNT -> loadCurrentAccount(forceRefresh) ) {
DashboardItem.Tile.LUCKY_NUMBER -> loadLuckyNumber(forceRefresh) launch {
DashboardItem.Tile.MESSAGES -> loadMessages(forceRefresh) Timber.i("Loading dashboard account data started")
DashboardItem.Tile.ATTENDANCE -> loadAttendance(forceRefresh) val student = runCatching { studentRepository.getCurrentStudent(true) }
DashboardItem.Tile.LESSONS -> loadLessons(forceRefresh) .onFailure {
DashboardItem.Tile.GRADES -> loadGrades(forceRefresh) Timber.i("Loading dashboard account result: An exception occurred")
DashboardItem.Tile.HOMEWORK -> loadHomework(forceRefresh) errorHandler.dispatch(it)
DashboardItem.Tile.ANNOUNCEMENTS -> loadSchoolAnnouncements(forceRefresh) updateData(DashboardItem.Account(error = it), forceRefresh)
DashboardItem.Tile.EXAMS -> loadExams(forceRefresh) }
DashboardItem.Tile.CONFERENCES -> loadConferences(forceRefresh) .onSuccess { Timber.i("Loading dashboard account result: Success") }
DashboardItem.Tile.ADS -> TODO() .getOrNull() ?: return@launch
tileList.forEach {
when (it) {
DashboardItem.Type.ACCOUNT -> {
updateData(DashboardItem.Account(student), forceRefresh)
}
DashboardItem.Type.HORIZONTAL_GROUP -> {
loadHorizontalGroup(student, forceRefresh)
}
DashboardItem.Type.LESSONS -> loadLessons(student, forceRefresh)
DashboardItem.Type.GRADES -> loadGrades(student, forceRefresh)
DashboardItem.Type.HOMEWORK -> loadHomework(student, forceRefresh)
DashboardItem.Type.ANNOUNCEMENTS -> {
loadSchoolAnnouncements(student, forceRefresh)
}
DashboardItem.Type.EXAMS -> loadExams(student, forceRefresh)
DashboardItem.Type.CONFERENCES -> {
loadConferences(student, forceRefresh)
}
DashboardItem.Type.ADS -> TODO()
}
} }
} }
} }
private fun generateTileListToLoad(
oldDashboardTileToLoad: Set<DashboardItem.Tile>,
forceRefresh: Boolean
) = dashboardTilesToLoad.filter { newTileToLoad ->
oldDashboardTileToLoad.none { it == newTileToLoad } || forceRefresh
}
fun onSwipeRefresh() { fun onSwipeRefresh() {
Timber.i("Force refreshing the dashboard") Timber.i("Force refreshing the dashboard")
loadData(true, preferencesRepository.selectedDashboardTiles) loadData(preferencesRepository.selectedDashboardTiles, forceRefresh = true)
} }
fun onRetry() { fun onRetry() {
@ -166,7 +194,7 @@ class DashboardPresenter @Inject constructor(
showErrorView(false) showErrorView(false)
showProgress(true) showProgress(true)
} }
loadData(true, preferencesRepository.selectedDashboardTiles) loadData(preferencesRepository.selectedDashboardTiles, forceRefresh = true)
} }
fun onViewReselected() { fun onViewReselected() {
@ -192,139 +220,86 @@ class DashboardPresenter @Inject constructor(
}.toSet() }.toSet()
} }
private fun loadCurrentAccount(forceRefresh: Boolean) { private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
flowWithResource { studentRepository.getCurrentStudent(false) } flow {
val semester = semesterRepository.getCurrentSemester(student)
val selectedTiles = preferencesRepository.selectedDashboardTiles
val luckyNumberFlow = luckyNumberRepository.getLuckyNumber(student, forceRefresh)
.map {
if (it.data == null) {
it.copy(data = LuckyNumber(0, LocalDate.now(), 0))
} else it
}
.takeIf { DashboardItem.Tile.LUCKY_NUMBER in selectedTiles } ?: flowOf(null)
val messageFLow = messageRepository.getMessages(
student = student,
semester = semester,
folder = MessageFolder.RECEIVED,
forceRefresh = forceRefresh
).takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowOf(null)
val attendanceFlow = attendanceSummaryRepository.getAttendanceSummary(
student = student,
semester = semester,
subjectId = -1,
forceRefresh = forceRefresh
).takeIf { DashboardItem.Tile.ATTENDANCE in selectedTiles } ?: flowOf(null)
emitAll(
combine(
luckyNumberFlow,
messageFLow,
attendanceFlow
) { luckyNumberResource, messageResource, attendanceResource ->
val error =
luckyNumberResource?.error ?: messageResource?.error ?: attendanceResource?.error
error?.let { throw it }
val luckyNumber = luckyNumberResource?.data?.luckyNumber
val messageCount = messageResource?.data?.count { it.unread }
val attendancePercentage = attendanceResource?.data?.calculatePercentage()
val isLoading =
luckyNumberResource?.status == Status.LOADING || messageResource?.status == Status.LOADING || attendanceResource?.status == Status.LOADING
DashboardItem.HorizontalGroup(
isLoading = isLoading,
attendancePercentage = if (attendancePercentage == 0.0 && isLoading) -1.0 else attendancePercentage,
unreadMessagesCount = if (messageCount == 0 && isLoading) -1 else messageCount,
luckyNumber = if (luckyNumber == 0 && isLoading) -1 else luckyNumber
)
})
}
.filterNot { it.isLoading && forceRefresh }
.distinctUntilChanged()
.onEach { .onEach {
when (it.status) { updateData(it, forceRefresh)
Status.LOADING -> {
Timber.i("Loading dashboard account data started") if (it.isLoading) {
if (forceRefresh) return@onEach Timber.i("Loading horizontal group data started")
updateData(DashboardItem.Account(it.data, isLoading = true), forceRefresh)
} if (it.isFullDataLoaded) {
Status.SUCCESS -> { firstLoadedItemList += DashboardItem.Type.HORIZONTAL_GROUP
Timber.i("Loading dashboard account result: Success")
updateData(DashboardItem.Account(it.data), forceRefresh)
}
Status.ERROR -> {
Timber.i("Loading dashboard account result: An exception occurred")
errorHandler.dispatch(it.error!!)
updateData(DashboardItem.Account(error = it.error), forceRefresh)
} }
} else {
Timber.i("Loading horizontal group result: Success")
} }
} }
.launch("dashboard_account") .catch {
} Timber.i("Loading horizontal group result: An exception occurred")
updateData(
private fun loadLuckyNumber(forceRefresh: Boolean) { DashboardItem.HorizontalGroup(error = it),
flowWithResourceIn { forceRefresh,
val student = studentRepository.getCurrentStudent(true) )
errorHandler.dispatch(it)
luckyNumberRepository.getLuckyNumber(student, forceRefresh)
}.onEach {
when (it.status) {
Status.LOADING -> {
Timber.i("Loading dashboard lucky number data started")
if (forceRefresh) return@onEach
processHorizontalGroupData(
luckyNumber = it.data?.luckyNumber,
isLoading = true,
forceRefresh = forceRefresh
)
}
Status.SUCCESS -> {
Timber.i("Loading dashboard lucky number result: Success")
processHorizontalGroupData(
luckyNumber = it.data?.luckyNumber ?: -1,
forceRefresh = forceRefresh
)
}
Status.ERROR -> {
Timber.i("Loading dashboard lucky number result: An exception occurred")
errorHandler.dispatch(it.error!!)
processHorizontalGroupData(error = it.error, forceRefresh = forceRefresh)
}
} }
}.launch("dashboard_lucky_number") .launch("horizontal_group")
} }
private fun loadMessages(forceRefresh: Boolean) { private fun loadGrades(student: Student, forceRefresh: Boolean) {
flowWithResourceIn { flowWithResourceIn {
val student = studentRepository.getCurrentStudent(true)
val semester = semesterRepository.getCurrentSemester(student)
messageRepository.getMessages(student, semester, MessageFolder.RECEIVED, forceRefresh)
}.onEach {
when (it.status) {
Status.LOADING -> {
Timber.i("Loading dashboard messages data started")
if (forceRefresh) return@onEach
val unreadMessagesCount = it.data?.count { message -> message.unread }
processHorizontalGroupData(
unreadMessagesCount = unreadMessagesCount,
isLoading = true,
forceRefresh = forceRefresh
)
}
Status.SUCCESS -> {
Timber.i("Loading dashboard messages result: Success")
val unreadMessagesCount = it.data?.count { message -> message.unread }
processHorizontalGroupData(
unreadMessagesCount = unreadMessagesCount,
forceRefresh = forceRefresh
)
}
Status.ERROR -> {
Timber.i("Loading dashboard messages result: An exception occurred")
errorHandler.dispatch(it.error!!)
processHorizontalGroupData(error = it.error, forceRefresh = forceRefresh)
}
}
}.launch("dashboard_messages")
}
private fun loadAttendance(forceRefresh: Boolean) {
flowWithResourceIn {
val student = studentRepository.getCurrentStudent(true)
val semester = semesterRepository.getCurrentSemester(student)
attendanceSummaryRepository.getAttendanceSummary(student, semester, -1, forceRefresh)
}.onEach {
when (it.status) {
Status.LOADING -> {
Timber.i("Loading dashboard attendance data started")
if (forceRefresh) return@onEach
val attendancePercentage = it.data?.calculatePercentage()
processHorizontalGroupData(
attendancePercentage = attendancePercentage,
isLoading = true,
forceRefresh = forceRefresh
)
}
Status.SUCCESS -> {
Timber.i("Loading dashboard attendance result: Success")
val attendancePercentage = it.data?.calculatePercentage()
processHorizontalGroupData(
attendancePercentage = attendancePercentage,
forceRefresh = forceRefresh
)
}
Status.ERROR -> {
Timber.i("Loading dashboard attendance result: An exception occurred")
errorHandler.dispatch(it.error!!)
processHorizontalGroupData(error = it.error, forceRefresh = forceRefresh)
}
}
}.launch("dashboard_attendance")
}
private fun loadGrades(forceRefresh: Boolean) {
flowWithResourceIn {
val student = studentRepository.getCurrentStudent(true)
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
gradeRepository.getGrades(student, semester, forceRefresh) gradeRepository.getGrades(student, semester, forceRefresh)
@ -353,6 +328,7 @@ class DashboardPresenter @Inject constructor(
Status.LOADING -> { Status.LOADING -> {
Timber.i("Loading dashboard grades data started") Timber.i("Loading dashboard grades data started")
if (forceRefresh) return@onEach if (forceRefresh) return@onEach
updateData( updateData(
DashboardItem.Grades( DashboardItem.Grades(
subjectWithGrades = it.data, subjectWithGrades = it.data,
@ -360,6 +336,10 @@ class DashboardPresenter @Inject constructor(
isLoading = true isLoading = true
), forceRefresh ), forceRefresh
) )
if (!it.data.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.GRADES
}
} }
Status.SUCCESS -> { Status.SUCCESS -> {
Timber.i("Loading dashboard grades result: Success") Timber.i("Loading dashboard grades result: Success")
@ -367,7 +347,8 @@ class DashboardPresenter @Inject constructor(
DashboardItem.Grades( DashboardItem.Grades(
subjectWithGrades = it.data, subjectWithGrades = it.data,
gradeTheme = preferencesRepository.gradeColorTheme gradeTheme = preferencesRepository.gradeColorTheme
), forceRefresh ),
forceRefresh
) )
} }
Status.ERROR -> { Status.ERROR -> {
@ -379,9 +360,8 @@ class DashboardPresenter @Inject constructor(
}.launch("dashboard_grades") }.launch("dashboard_grades")
} }
private fun loadLessons(forceRefresh: Boolean) { private fun loadLessons(student: Student, forceRefresh: Boolean) {
flowWithResourceIn { flowWithResourceIn {
val student = studentRepository.getCurrentStudent(true)
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
val date = LocalDate.now().nextOrSameSchoolDay val date = LocalDate.now().nextOrSameSchoolDay
@ -398,24 +378,34 @@ class DashboardPresenter @Inject constructor(
Status.LOADING -> { Status.LOADING -> {
Timber.i("Loading dashboard lessons data started") Timber.i("Loading dashboard lessons data started")
if (forceRefresh) return@onEach if (forceRefresh) return@onEach
updateData(DashboardItem.Lessons(it.data, isLoading = true), forceRefresh) updateData(
DashboardItem.Lessons(it.data, isLoading = true),
forceRefresh
)
if (!it.data?.lessons.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.LESSONS
}
} }
Status.SUCCESS -> { Status.SUCCESS -> {
Timber.i("Loading dashboard lessons result: Success") Timber.i("Loading dashboard lessons result: Success")
updateData(DashboardItem.Lessons(it.data), forceRefresh) updateData(
DashboardItem.Lessons(it.data), forceRefresh
)
} }
Status.ERROR -> { Status.ERROR -> {
Timber.i("Loading dashboard lessons result: An exception occurred") Timber.i("Loading dashboard lessons result: An exception occurred")
errorHandler.dispatch(it.error!!) errorHandler.dispatch(it.error!!)
updateData(DashboardItem.Lessons(error = it.error), forceRefresh) updateData(
DashboardItem.Lessons(error = it.error), forceRefresh
)
} }
} }
}.launch("dashboard_lessons") }.launch("dashboard_lessons")
} }
private fun loadHomework(forceRefresh: Boolean) { private fun loadHomework(student: Student, forceRefresh: Boolean) {
flowWithResourceIn { flowWithResourceIn {
val student = studentRepository.getCurrentStudent(true)
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
val date = LocalDate.now().nextOrSameSchoolDay val date = LocalDate.now().nextOrSameSchoolDay
@ -443,6 +433,10 @@ class DashboardPresenter @Inject constructor(
DashboardItem.Homework(it.data ?: emptyList(), isLoading = true), DashboardItem.Homework(it.data ?: emptyList(), isLoading = true),
forceRefresh forceRefresh
) )
if (!it.data.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.HOMEWORK
}
} }
Status.SUCCESS -> { Status.SUCCESS -> {
Timber.i("Loading dashboard homework result: Success") Timber.i("Loading dashboard homework result: Success")
@ -457,10 +451,8 @@ class DashboardPresenter @Inject constructor(
}.launch("dashboard_homework") }.launch("dashboard_homework")
} }
private fun loadSchoolAnnouncements(forceRefresh: Boolean) { private fun loadSchoolAnnouncements(student: Student, forceRefresh: Boolean) {
flowWithResourceIn { flowWithResourceIn {
val student = studentRepository.getCurrentStudent(true)
schoolAnnouncementRepository.getSchoolAnnouncements(student, forceRefresh) schoolAnnouncementRepository.getSchoolAnnouncements(student, forceRefresh)
}.onEach { }.onEach {
when (it.status) { when (it.status) {
@ -468,11 +460,13 @@ class DashboardPresenter @Inject constructor(
Timber.i("Loading dashboard announcements data started") Timber.i("Loading dashboard announcements data started")
if (forceRefresh) return@onEach if (forceRefresh) return@onEach
updateData( updateData(
DashboardItem.Announcements( DashboardItem.Announcements(it.data ?: emptyList(), isLoading = true),
it.data ?: emptyList(), forceRefresh
isLoading = true
), forceRefresh
) )
if (!it.data.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.ANNOUNCEMENTS
}
} }
Status.SUCCESS -> { Status.SUCCESS -> {
Timber.i("Loading dashboard announcements result: Success") Timber.i("Loading dashboard announcements result: Success")
@ -487,9 +481,8 @@ class DashboardPresenter @Inject constructor(
}.launch("dashboard_announcements") }.launch("dashboard_announcements")
} }
private fun loadExams(forceRefresh: Boolean) { private fun loadExams(student: Student, forceRefresh: Boolean) {
flowWithResourceIn { flowWithResourceIn {
val student = studentRepository.getCurrentStudent(true)
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
examRepository.getExams( examRepository.getExams(
@ -508,6 +501,10 @@ class DashboardPresenter @Inject constructor(
DashboardItem.Exams(it.data.orEmpty(), isLoading = true), DashboardItem.Exams(it.data.orEmpty(), isLoading = true),
forceRefresh forceRefresh
) )
if (!it.data.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.EXAMS
}
} }
Status.SUCCESS -> { Status.SUCCESS -> {
Timber.i("Loading dashboard exams result: Success") Timber.i("Loading dashboard exams result: Success")
@ -522,9 +519,8 @@ class DashboardPresenter @Inject constructor(
}.launch("dashboard_exams") }.launch("dashboard_exams")
} }
private fun loadConferences(forceRefresh: Boolean) { private fun loadConferences(student: Student, forceRefresh: Boolean) {
flowWithResourceIn { flowWithResourceIn {
val student = studentRepository.getCurrentStudent(true)
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
conferenceRepository.getConferences( conferenceRepository.getConferences(
@ -542,6 +538,10 @@ class DashboardPresenter @Inject constructor(
DashboardItem.Conferences(it.data ?: emptyList(), isLoading = true), DashboardItem.Conferences(it.data ?: emptyList(), isLoading = true),
forceRefresh forceRefresh
) )
if (!it.data.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.CONFERENCES
}
} }
Status.SUCCESS -> { Status.SUCCESS -> {
Timber.i("Loading dashboard conferences result: Success") Timber.i("Loading dashboard conferences result: Success")
@ -556,145 +556,119 @@ class DashboardPresenter @Inject constructor(
}.launch("dashboard_conferences") }.launch("dashboard_conferences")
} }
private fun processHorizontalGroupData( private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) {
luckyNumber: Int? = null, val isForceRefreshError = forceRefresh && dashboardItem.error != null
unreadMessagesCount: Int? = null, val isFirstRunDataLoadedError =
attendancePercentage: Double? = null, dashboardItem.type in firstLoadedItemList && dashboardItem.error != null
error: Throwable? = null,
isLoading: Boolean = false,
forceRefresh: Boolean
) {
val isLuckyNumberToLoad =
dashboardTilesToLoad.any { it == DashboardItem.Tile.LUCKY_NUMBER }
val isMessagesToLoad =
dashboardTilesToLoad.any { it == DashboardItem.Tile.MESSAGES }
val isAttendanceToLoad =
dashboardTilesToLoad.any { it == DashboardItem.Tile.ATTENDANCE }
val isPushedToList =
dashboardItemLoadedList.any { it.type == DashboardItem.Type.HORIZONTAL_GROUP }
if (error != null) { with(dashboardItemLoadedList) {
updateData(DashboardItem.HorizontalGroup(error = error), forceRefresh) removeAll { it.type == dashboardItem.type && !isForceRefreshError && !isFirstRunDataLoadedError }
return if (!isForceRefreshError && !isFirstRunDataLoadedError) add(dashboardItem)
} }
if (isLoading) { sortDashboardItems()
val horizontalGroup =
dashboardItemLoadedList.find { it is DashboardItem.HorizontalGroup } as DashboardItem.HorizontalGroup?
val updatedHorizontalGroup =
horizontalGroup?.copy(isLoading = true) ?: DashboardItem.HorizontalGroup(isLoading = true)
updateData(updatedHorizontalGroup, forceRefresh) if (forceRefresh) {
} updateForceRefreshData(dashboardItem)
} else {
if (forceRefresh && !isPushedToList) { updateNormalData()
updateData(DashboardItem.HorizontalGroup(), forceRefresh)
}
val horizontalGroup =
dashboardItemLoadedList.single { it is DashboardItem.HorizontalGroup } as DashboardItem.HorizontalGroup
when {
luckyNumber != null -> {
updateData(horizontalGroup.copy(luckyNumber = luckyNumber), forceRefresh)
}
unreadMessagesCount != null -> {
updateData(
horizontalGroup.copy(unreadMessagesCount = unreadMessagesCount),
forceRefresh
)
}
attendancePercentage != null -> {
updateData(
horizontalGroup.copy(attendancePercentage = attendancePercentage),
forceRefresh
)
}
}
val isHorizontalGroupLoaded = dashboardItemLoadedList.any {
if (it !is DashboardItem.HorizontalGroup) return@any false
val isLuckyNumberStateCorrect = (it.luckyNumber != null) == isLuckyNumberToLoad
val isMessagesStateCorrect = (it.unreadMessagesCount != null) == isMessagesToLoad
val isAttendanceStateCorrect = (it.attendancePercentage != null) == isAttendanceToLoad
isLuckyNumberStateCorrect && isAttendanceStateCorrect && isMessagesStateCorrect
}
if (isHorizontalGroupLoaded) {
val updatedHorizontalGroup =
dashboardItemLoadedList.single { it is DashboardItem.HorizontalGroup } as DashboardItem.HorizontalGroup
updateData(updatedHorizontalGroup.copy(isLoading = false, error = null), forceRefresh)
} }
} }
private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) { private fun updateNormalData() {
val isForceRefreshError = forceRefresh && dashboardItem.error != null val isItemsLoaded =
val dashboardItemsPosition = preferencesRepository.dashboardItemsPosition dashboardItemsToLoad.all { type -> dashboardItemLoadedList.any { it.type == type } }
val isItemsDataLoaded = isItemsLoaded && dashboardItemLoadedList.all {
with(dashboardItemLoadedList) { it.isDataLoaded || it.error != null
removeAll { it.type == dashboardItem.type && !isForceRefreshError }
if (!isForceRefreshError) add(dashboardItem)
sortBy { tile -> dashboardItemsToLoad.single { it == tile.type }.ordinal }
} }
if (forceRefresh) { if (isItemsDataLoaded) {
with(dashboardItemRefreshLoadedList) { view?.run {
removeAll { it.type == dashboardItem.type } showProgress(false)
add(dashboardItem) showErrorView(false)
showContent(true)
updateData(dashboardItemLoadedList.toList())
} }
} }
showErrorIfExists(
isItemsLoaded = isItemsLoaded,
itemsLoadedList = dashboardItemLoadedList,
forceRefresh = false
)
}
private fun updateForceRefreshData(dashboardItem: DashboardItem) {
with(dashboardItemRefreshLoadedList) {
removeAll { it.type == dashboardItem.type }
add(dashboardItem)
}
val isRefreshItemLoaded =
dashboardItemsToLoad.all { type -> dashboardItemRefreshLoadedList.any { it.type == type } }
val isRefreshItemsDataLoaded = isRefreshItemLoaded && dashboardItemRefreshLoadedList.all {
it.isDataLoaded || it.error != null
}
if (isRefreshItemsDataLoaded) {
view?.run {
showRefresh(false)
showErrorView(false)
showContent(true)
updateData(dashboardItemLoadedList.toList())
}
}
showErrorIfExists(
isItemsLoaded = isRefreshItemLoaded,
itemsLoadedList = dashboardItemRefreshLoadedList,
forceRefresh = true
)
if (isRefreshItemsDataLoaded) dashboardItemRefreshLoadedList.clear()
}
private fun showErrorIfExists(
isItemsLoaded: Boolean,
itemsLoadedList: List<DashboardItem>,
forceRefresh: Boolean
) {
val filteredItems = itemsLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT }
val isAccountItemError =
itemsLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null
val isGeneralError =
filteredItems.none { it.error == null } && filteredItems.isNotEmpty() || isAccountItemError
val errorMessage = itemsLoadedList.map { it.error?.stackTraceToString() }.toString()
val filteredOriginalLoadedList =
dashboardItemLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT }
val wasAccountItemError =
dashboardItemLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null
val wasGeneralError =
filteredOriginalLoadedList.none { it.error == null } && filteredOriginalLoadedList.isNotEmpty() || wasAccountItemError
if (isGeneralError && isItemsLoaded) {
lastError = Exception(errorMessage)
view?.run {
showProgress(false)
showRefresh(false)
if ((forceRefresh && wasGeneralError) || !forceRefresh) {
showContent(false)
showErrorView(true)
}
}
}
}
private fun sortDashboardItems() {
val dashboardItemsPosition = preferencesRepository.dashboardItemsPosition
dashboardItemLoadedList.sortBy { tile -> dashboardItemLoadedList.sortBy { tile ->
dashboardItemsPosition?.getOrDefault( dashboardItemsPosition?.getOrDefault(
tile.type, tile.type,
tile.type.ordinal + 100 tile.type.ordinal + 100
) ?: tile.type.ordinal ) ?: tile.type.ordinal
} }
val isItemsLoaded =
dashboardItemsToLoad.all { type -> dashboardItemLoadedList.any { it.type == type } }
val isRefreshItemLoaded =
dashboardItemsToLoad.all { type -> dashboardItemRefreshLoadedList.any { it.type == type } }
val isItemsDataLoaded = isItemsLoaded && dashboardItemLoadedList.all {
it.isDataLoaded || it.error != null
}
val isRefreshItemsDataLoaded = isRefreshItemLoaded && dashboardItemRefreshLoadedList.all {
it.isDataLoaded || it.error != null
}
if (isRefreshItemsDataLoaded) {
view?.showRefresh(false)
dashboardItemRefreshLoadedList.clear()
}
view?.run {
if (!forceRefresh) {
showProgress(!isItemsDataLoaded)
showContent(isItemsDataLoaded)
}
updateData(dashboardItemLoadedList.toList())
}
if (isItemsLoaded) {
val filteredItems =
dashboardItemLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT }
val isAccountItemError =
dashboardItemLoadedList.single { it.type == DashboardItem.Type.ACCOUNT }.error != null
val isGeneralError =
filteredItems.all { it.error != null } && filteredItems.isNotEmpty() || isAccountItemError
val errorMessage = filteredItems.map { it.error?.stackTraceToString() }.toString()
lastError = Exception(errorMessage)
view?.run {
showProgress(false)
showContent(!isGeneralError)
showErrorView(isGeneralError)
}
}
} }
} }

View File

@ -18,7 +18,7 @@ inline val Timetable.left: Duration?
get() = when { get() = when {
canceled -> null canceled -> null
!isStudentPlan -> null !isStudentPlan -> null
end.isAfter(now()) && start.isBefore(now()) -> between(now(), end) end >= now() && start <= now() -> between(now(), end)
else -> null else -> null
} }

View File

@ -4,14 +4,14 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingHorizontal="12dp"
android:layout_marginVertical="2dp" android:layout_marginVertical="2dp"
android:clipToPadding="false"> android:clipToPadding="false"
android:paddingHorizontal="12dp">
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/dashboard_horizontal_group_item_lucky_container" android:id="@+id/dashboard_horizontal_group_item_lucky_container"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="44dp"
android:layout_marginVertical="4dp" android:layout_marginVertical="4dp"
app:cardElevation="4dp" app:cardElevation="4dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
@ -62,7 +62,7 @@
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/dashboard_horizontal_group_item_message_container" android:id="@+id/dashboard_horizontal_group_item_message_container"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="44dp"
android:layout_marginVertical="4dp" android:layout_marginVertical="4dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
app:cardElevation="4dp" app:cardElevation="4dp"
@ -119,7 +119,7 @@
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/dashboard_horizontal_group_item_attendance_container" android:id="@+id/dashboard_horizontal_group_item_attendance_container"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="44dp"
android:layout_marginVertical="4dp" android:layout_marginVertical="4dp"
app:cardElevation="4dp" app:cardElevation="4dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
@ -169,10 +169,8 @@
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/dashboard_horizontal_group_item_info_container" android:id="@+id/dashboard_horizontal_group_item_info_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="44dp"
android:layout_marginVertical="4dp" android:layout_marginVertical="4dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:visibility="gone" android:visibility="gone"
app:cardElevation="4dp" app:cardElevation="4dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"

View File

@ -496,7 +496,6 @@
<!--Dashboard--> <!--Dashboard-->
<string name="dashboard_timetable_title">Lekce</string> <string name="dashboard_timetable_title">Lekce</string>
<string name="dashboard_timetable_title_tomorrow">(Zítra)</string> <string name="dashboard_timetable_title_tomorrow">(Zítra)</string>
<string name="dashboard_timetable_lesson_value">%1$s (%2$s)</string>
<string name="dashboard_timetable_first_lesson_title_moment">Za chvíli:</string> <string name="dashboard_timetable_first_lesson_title_moment">Za chvíli:</string>
<string name="dashboard_timetable_first_lesson_title_soon">Brzy:</string> <string name="dashboard_timetable_first_lesson_title_soon">Brzy:</string>
<string name="dashboard_timetable_first_lesson_title_first">První:</string> <string name="dashboard_timetable_first_lesson_title_first">První:</string>
@ -566,7 +565,7 @@
<item quantity="other">Ještě %1$d dalších setkání</item> <item quantity="other">Ještě %1$d dalších setkání</item>
</plurals> </plurals>
<string name="dashboard_horizontal_group_error">Při načítání dat došlo k chybě</string> <string name="dashboard_horizontal_group_error">Při načítání dat došlo k chybě</string>
<string name="dashboard_horizontal_group_no_lukcy_number">Žádné</string> <string name="dashboard_horizontal_group_no_data">Žádné</string>
<!--Error dialog--> <!--Error dialog-->
<string name="dialog_error_check_update">Zkontrolovat aktualizace</string> <string name="dialog_error_check_update">Zkontrolovat aktualizace</string>
<string name="dialog_error_check_update_message">Před hlášením chyby zkontrolujte, zda je k dispozici aktualizace s opravou chyb</string> <string name="dialog_error_check_update_message">Před hlášením chyby zkontrolujte, zda je k dispozici aktualizace s opravou chyb</string>

View File

@ -432,7 +432,6 @@
<!--Dashboard--> <!--Dashboard-->
<string name="dashboard_timetable_title">Lektionen</string> <string name="dashboard_timetable_title">Lektionen</string>
<string name="dashboard_timetable_title_tomorrow">(Morgen)</string> <string name="dashboard_timetable_title_tomorrow">(Morgen)</string>
<string name="dashboard_timetable_lesson_value">%1$s (%2$s)</string>
<string name="dashboard_timetable_first_lesson_title_moment">Gleich:</string> <string name="dashboard_timetable_first_lesson_title_moment">Gleich:</string>
<string name="dashboard_timetable_first_lesson_title_soon">Bald:</string> <string name="dashboard_timetable_first_lesson_title_soon">Bald:</string>
<string name="dashboard_timetable_first_lesson_title_first">Erstens:</string> <string name="dashboard_timetable_first_lesson_title_first">Erstens:</string>
@ -488,7 +487,7 @@
<item quantity="other">%1$d weitere Konferenzen</item> <item quantity="other">%1$d weitere Konferenzen</item>
</plurals> </plurals>
<string name="dashboard_horizontal_group_error">Fehler beim Laden der Daten</string> <string name="dashboard_horizontal_group_error">Fehler beim Laden der Daten</string>
<string name="dashboard_horizontal_group_no_lukcy_number">Keine</string> <string name="dashboard_horizontal_group_no_data">Keine</string>
<!--Error dialog--> <!--Error dialog-->
<string name="dialog_error_check_update">Auf Updates prüfen</string> <string name="dialog_error_check_update">Auf Updates prüfen</string>
<string name="dialog_error_check_update_message">Bevor Sie einen Fehler melden, prüfen Sie zuerst, ob ein Update mit der Fehlerbehebung verfügbar ist</string> <string name="dialog_error_check_update_message">Bevor Sie einen Fehler melden, prüfen Sie zuerst, ob ein Update mit der Fehlerbehebung verfügbar ist</string>

View File

@ -496,7 +496,6 @@
<!--Dashboard--> <!--Dashboard-->
<string name="dashboard_timetable_title">Lekcje</string> <string name="dashboard_timetable_title">Lekcje</string>
<string name="dashboard_timetable_title_tomorrow">(Jutro)</string> <string name="dashboard_timetable_title_tomorrow">(Jutro)</string>
<string name="dashboard_timetable_lesson_value">%1$s (%2$s)</string>
<string name="dashboard_timetable_first_lesson_title_moment">Za chwilę:</string> <string name="dashboard_timetable_first_lesson_title_moment">Za chwilę:</string>
<string name="dashboard_timetable_first_lesson_title_soon">Wkrótce:</string> <string name="dashboard_timetable_first_lesson_title_soon">Wkrótce:</string>
<string name="dashboard_timetable_first_lesson_title_first">Pierwsza:</string> <string name="dashboard_timetable_first_lesson_title_first">Pierwsza:</string>
@ -566,7 +565,7 @@
<item quantity="other">Jeszcze %1$d dodatkowych zebrań</item> <item quantity="other">Jeszcze %1$d dodatkowych zebrań</item>
</plurals> </plurals>
<string name="dashboard_horizontal_group_error">Wystąpił błąd podczas ładowania danych</string> <string name="dashboard_horizontal_group_error">Wystąpił błąd podczas ładowania danych</string>
<string name="dashboard_horizontal_group_no_lukcy_number">Brak</string> <string name="dashboard_horizontal_group_no_data">Brak</string>
<!--Error dialog--> <!--Error dialog-->
<string name="dialog_error_check_update">Sprawdź dostępność aktualizacji</string> <string name="dialog_error_check_update">Sprawdź dostępność aktualizacji</string>
<string name="dialog_error_check_update_message">Przed zgłoszeniem błędu sprawdź wcześniej, czy dostępna jest już aktualizacja z poprawką błędu</string> <string name="dialog_error_check_update_message">Przed zgłoszeniem błędu sprawdź wcześniej, czy dostępna jest już aktualizacja z poprawką błędu</string>

View File

@ -496,7 +496,6 @@
<!--Dashboard--> <!--Dashboard-->
<string name="dashboard_timetable_title">Уроки</string> <string name="dashboard_timetable_title">Уроки</string>
<string name="dashboard_timetable_title_tomorrow">(Завтра)</string> <string name="dashboard_timetable_title_tomorrow">(Завтра)</string>
<string name="dashboard_timetable_lesson_value">%1$s (%2$s)</string>
<string name="dashboard_timetable_first_lesson_title_moment">Сейчас:</string> <string name="dashboard_timetable_first_lesson_title_moment">Сейчас:</string>
<string name="dashboard_timetable_first_lesson_title_soon">Скоро:</string> <string name="dashboard_timetable_first_lesson_title_soon">Скоро:</string>
<string name="dashboard_timetable_first_lesson_title_first">Первый:</string> <string name="dashboard_timetable_first_lesson_title_first">Первый:</string>
@ -566,7 +565,7 @@
<item quantity="other">Еще %1$d конференций</item> <item quantity="other">Еще %1$d конференций</item>
</plurals> </plurals>
<string name="dashboard_horizontal_group_error">Произошла ошибка при загрузке данных</string> <string name="dashboard_horizontal_group_error">Произошла ошибка при загрузке данных</string>
<string name="dashboard_horizontal_group_no_lukcy_number">Отсутствует</string> <string name="dashboard_horizontal_group_no_data">Отсутствует</string>
<!--Error dialog--> <!--Error dialog-->
<string name="dialog_error_check_update">Проверить наличие обновлений</string> <string name="dialog_error_check_update">Проверить наличие обновлений</string>
<string name="dialog_error_check_update_message">Прежде чем сообщать об ошибке, проверьте наличие обновлений</string> <string name="dialog_error_check_update_message">Прежде чем сообщать об ошибке, проверьте наличие обновлений</string>

View File

@ -496,7 +496,6 @@
<!--Dashboard--> <!--Dashboard-->
<string name="dashboard_timetable_title">Lekcie</string> <string name="dashboard_timetable_title">Lekcie</string>
<string name="dashboard_timetable_title_tomorrow">(Zajtra)</string> <string name="dashboard_timetable_title_tomorrow">(Zajtra)</string>
<string name="dashboard_timetable_lesson_value">%1$s (%2$s)</string>
<string name="dashboard_timetable_first_lesson_title_moment">Za chvíľu:</string> <string name="dashboard_timetable_first_lesson_title_moment">Za chvíľu:</string>
<string name="dashboard_timetable_first_lesson_title_soon">Čoskoro:</string> <string name="dashboard_timetable_first_lesson_title_soon">Čoskoro:</string>
<string name="dashboard_timetable_first_lesson_title_first">Prvá:</string> <string name="dashboard_timetable_first_lesson_title_first">Prvá:</string>
@ -566,7 +565,7 @@
<item quantity="other">Ešte %1$d ďalších stretnutí</item> <item quantity="other">Ešte %1$d ďalších stretnutí</item>
</plurals> </plurals>
<string name="dashboard_horizontal_group_error">Pri načítaní dát došlo k chybe</string> <string name="dashboard_horizontal_group_error">Pri načítaní dát došlo k chybe</string>
<string name="dashboard_horizontal_group_no_lukcy_number">Žiadne</string> <string name="dashboard_horizontal_group_no_data">Žiadne</string>
<!--Error dialog--> <!--Error dialog-->
<string name="dialog_error_check_update">Skontrolovať aktualizácie</string> <string name="dialog_error_check_update">Skontrolovať aktualizácie</string>
<string name="dialog_error_check_update_message">Pred hlásením chyby skontrolujte, či je k dispozícii aktualizácia s opravou chýb</string> <string name="dialog_error_check_update_message">Pred hlásením chyby skontrolujte, či je k dispozícii aktualizácia s opravou chýb</string>

View File

@ -496,7 +496,6 @@
<!--Dashboard--> <!--Dashboard-->
<string name="dashboard_timetable_title">Уроки</string> <string name="dashboard_timetable_title">Уроки</string>
<string name="dashboard_timetable_title_tomorrow">(Завтра)</string> <string name="dashboard_timetable_title_tomorrow">(Завтра)</string>
<string name="dashboard_timetable_lesson_value">%1$s (%2$s)</string>
<string name="dashboard_timetable_first_lesson_title_moment">Через мить:</string> <string name="dashboard_timetable_first_lesson_title_moment">Через мить:</string>
<string name="dashboard_timetable_first_lesson_title_soon">Незабаром:</string> <string name="dashboard_timetable_first_lesson_title_soon">Незабаром:</string>
<string name="dashboard_timetable_first_lesson_title_first">Перше:</string> <string name="dashboard_timetable_first_lesson_title_first">Перше:</string>
@ -566,7 +565,7 @@
<item quantity="other">%1$d більше конференцій</item> <item quantity="other">%1$d більше конференцій</item>
</plurals> </plurals>
<string name="dashboard_horizontal_group_error">Помилка при завантаженні даних</string> <string name="dashboard_horizontal_group_error">Помилка при завантаженні даних</string>
<string name="dashboard_horizontal_group_no_lukcy_number">Нічого</string> <string name="dashboard_horizontal_group_no_data">Нічого</string>
<!--Error dialog--> <!--Error dialog-->
<string name="dialog_error_check_update">Провірити наявність оновлень</string> <string name="dialog_error_check_update">Провірити наявність оновлень</string>
<string name="dialog_error_check_update_message">Перед тим, як повідомлювати о помілці, перевірте наявність оновлень</string> <string name="dialog_error_check_update_message">Перед тим, як повідомлювати о помілці, перевірте наявність оновлень</string>

View File

@ -495,7 +495,6 @@
<!--Dashboard--> <!--Dashboard-->
<string name="dashboard_timetable_title">Lessons</string> <string name="dashboard_timetable_title">Lessons</string>
<string name="dashboard_timetable_title_tomorrow">(Tomorrow)</string> <string name="dashboard_timetable_title_tomorrow">(Tomorrow)</string>
<string name="dashboard_timetable_lesson_value">%1$s (%2$s)</string>
<string name="dashboard_timetable_first_lesson_title_moment">In a moment:</string> <string name="dashboard_timetable_first_lesson_title_moment">In a moment:</string>
<string name="dashboard_timetable_first_lesson_title_soon">Soon:</string> <string name="dashboard_timetable_first_lesson_title_soon">Soon:</string>
<string name="dashboard_timetable_first_lesson_title_first">First:</string> <string name="dashboard_timetable_first_lesson_title_first">First:</string>
@ -557,7 +556,7 @@
</plurals> </plurals>
<string name="dashboard_horizontal_group_error">An error occurred while loading data</string> <string name="dashboard_horizontal_group_error">An error occurred while loading data</string>
<string name="dashboard_horizontal_group_no_lukcy_number">None</string> <string name="dashboard_horizontal_group_no_data">None</string>
<!--Error dialog--> <!--Error dialog-->

View File

@ -32,7 +32,22 @@ class TimetableExtensionTest {
assertEquals(null, getTimetableEntity(canceled = true).left) assertEquals(null, getTimetableEntity(canceled = true).left)
assertEquals(null, getTimetableEntity(start = now().plusMinutes(5), end = now().plusMinutes(50)).left) assertEquals(null, getTimetableEntity(start = now().plusMinutes(5), end = now().plusMinutes(50)).left)
assertEquals(null, getTimetableEntity(start = now().minusMinutes(1), end = now().plusMinutes(44), isStudentPlan = false).left) assertEquals(null, getTimetableEntity(start = now().minusMinutes(1), end = now().plusMinutes(44), isStudentPlan = false).left)
assertNotEquals(null, getTimetableEntity(start = now().minusMinutes(1), end = now().plusMinutes(44), isStudentPlan = true).left) assertNotEquals(
null,
getTimetableEntity(
start = now().minusMinutes(1),
end = now().plusMinutes(44),
isStudentPlan = true
).left
)
assertNotEquals(
null,
getTimetableEntity(
start = now(),
end = now().plusMinutes(45),
isStudentPlan = true
).left
)
} }
@Test @Test