Skip to content

Commit 4ff9cf6

Browse files
Merge pull request #489 from VPlanPlus-Project/487-rebuild-timetable-updater
487 rebuild timetable updater
2 parents fe023cf + 56dca1f commit 4ff9cf6

38 files changed

Lines changed: 4820 additions & 353 deletions

File tree

composeApp/src/commonMain/kotlin/plus/vplan/app/domain/model/populated/PopulatedDay.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import plus.vplan.app.core.model.Holiday
1919
import plus.vplan.app.core.model.Homework
2020
import plus.vplan.app.core.model.Lesson
2121
import plus.vplan.app.core.model.Timetable
22-
import plus.vplan.app.utils.plus
22+
import plus.vplan.app.core.utils.date.plus
2323
import kotlin.time.Duration.Companion.days
2424

2525
data class PopulatedDay(
@@ -87,7 +87,11 @@ class DayPopulator(
8787
weekIndex = timetableWeek?.weekIndex ?: 0,
8888
dayOfWeek = day.date.dayOfWeek
8989
)
90-
}.map { it.toList() }
90+
}.map { timetableLessons ->
91+
timetableLessons
92+
.toList()
93+
.filter { timetableLesson -> timetableLesson.weekType == null || timetableLesson.weekType == day.week?.weekType }
94+
}
9195
}
9296

9397
val substitution = when (context) {

composeApp/src/commonMain/kotlin/plus/vplan/app/domain/usecase/GetDayUseCase.kt

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,14 @@ package plus.vplan.app.domain.usecase
44

55
import kotlinx.coroutines.ExperimentalCoroutinesApi
66
import kotlinx.coroutines.flow.Flow
7-
import kotlinx.coroutines.flow.distinctUntilChangedBy
87
import kotlinx.coroutines.flow.flatMapLatest
9-
import kotlinx.coroutines.flow.map
108
import kotlinx.datetime.LocalDate
11-
import kotlinx.datetime.isoDayNumber
129
import plus.vplan.app.core.data.day.DayRepository
13-
import plus.vplan.app.core.model.Day
1410
import plus.vplan.app.core.model.Profile
1511
import plus.vplan.app.core.model.School
1612
import plus.vplan.app.domain.model.populated.DayPopulator
1713
import plus.vplan.app.domain.model.populated.PopulatedDay
1814
import plus.vplan.app.domain.model.populated.PopulationContext
19-
import plus.vplan.app.utils.plus
20-
import kotlin.time.Duration.Companion.days
2115

2216
class GetDayUseCase(
2317
private val dayRepository: DayRepository,
@@ -26,16 +20,6 @@ class GetDayUseCase(
2620
operator fun invoke(profile: Profile, date: LocalDate): Flow<PopulatedDay> {
2721
val school: School.AppSchool = profile.school
2822
return dayRepository.getBySchool(school, date)
29-
.map { it ?: Day(
30-
id = Day.buildId(school.id, date),
31-
date = date,
32-
school = school,
33-
week = null,
34-
info = null,
35-
dayType = if (date.dayOfWeek.isoDayNumber > school.daysPerWeek) Day.DayType.WEEKEND else Day.DayType.REGULAR,
36-
nextSchoolDay = date + 1.days
37-
) }
38-
.distinctUntilChangedBy { "${it.id}|${it.week?.id}|${it.info}|${it.dayType}" }
3923
.flatMapLatest { day ->
4024
dayPopulator.populateSingle(day, PopulationContext.Profile(profile))
4125
}

composeApp/src/commonMain/kotlin/plus/vplan/app/feature/calendar/di/calendarModule.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package plus.vplan.app.feature.calendar.di
33
import org.koin.core.module.dsl.singleOf
44
import org.koin.core.module.dsl.viewModelOf
55
import org.koin.dsl.module
6+
import plus.vplan.app.feature.calendar.domain.usecase.DownloadDayIfNecessaryUseCase
67
import plus.vplan.app.feature.calendar.domain.usecase.GetFirstLessonStartUseCase
78
import plus.vplan.app.feature.calendar.domain.usecase.GetHolidaysUseCase
89
import plus.vplan.app.feature.calendar.domain.usecase.GetLastDisplayTypeUseCase
@@ -13,6 +14,7 @@ val calendarModule = module {
1314
singleOf(::GetLastDisplayTypeUseCase)
1415
singleOf(::SetLastDisplayTypeUseCase)
1516
singleOf(::GetFirstLessonStartUseCase)
17+
singleOf(::DownloadDayIfNecessaryUseCase)
1618
singleOf(::GetHolidaysUseCase)
1719

1820
viewModelOf(::CalendarViewModel)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
@file:OptIn(FlowPreview::class)
2+
3+
package plus.vplan.app.feature.calendar.domain.usecase
4+
5+
import kotlinx.coroutines.Dispatchers
6+
import kotlinx.coroutines.FlowPreview
7+
import kotlinx.coroutines.flow.MutableStateFlow
8+
import kotlinx.coroutines.flow.StateFlow
9+
import kotlinx.coroutines.flow.first
10+
import kotlinx.coroutines.sync.Mutex
11+
import kotlinx.coroutines.sync.withLock
12+
import kotlinx.coroutines.withContext
13+
import kotlinx.datetime.LocalDate
14+
import plus.vplan.app.core.data.timetable.TimetableRepository
15+
import plus.vplan.app.core.model.School
16+
import plus.vplan.app.core.model.Timetable
17+
import plus.vplan.app.core.sync.domain.usecase.sp24.UpdateTimetableUseCase
18+
import plus.vplan.app.core.utils.date.now
19+
20+
class DownloadDayIfNecessaryUseCase(
21+
private val updateTimetableUseCase: UpdateTimetableUseCase,
22+
private val timetableRepository: TimetableRepository,
23+
) {
24+
25+
private val mutex = Mutex()
26+
val isRunning: StateFlow<Boolean>
27+
field = MutableStateFlow(false)
28+
suspend operator fun invoke(date: LocalDate, school: School.AppSchool) = withContext(Dispatchers.Default) {
29+
if (date > LocalDate.now()) return@withContext
30+
mutex.withLock {
31+
val timetables = timetableRepository.getTimetables(school).first()
32+
.filter { it.week.start <= date }
33+
if (timetables.isEmpty() || timetables.maxBy { it.week.start }.dataState == Timetable.HasData.Unknown) {
34+
try {
35+
isRunning.value = true
36+
updateTimetableUseCase.updateTimetableRelatedToDate(date, school)
37+
} finally {
38+
isRunning.value = false
39+
}
40+
}
41+
}
42+
}
43+
}

composeApp/src/commonMain/kotlin/plus/vplan/app/feature/calendar/ui/CalendarScreen.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import androidx.compose.material3.FilledTonalIconButton
4444
import androidx.compose.material3.FloatingActionButton
4545
import androidx.compose.material3.HorizontalDivider
4646
import androidx.compose.material3.Icon
47+
import androidx.compose.material3.LinearProgressIndicator
4748
import androidx.compose.material3.LocalTextStyle
4849
import androidx.compose.material3.MaterialTheme
4950
import androidx.compose.material3.Text
@@ -356,6 +357,13 @@ private fun CalendarScreenContent(
356357
if (cause == DateSelectionCause.DayClick && scrollProgress == 2f) scrollProgress = 0f
357358
} }
358359
)
360+
AnimatedVisibility(
361+
visible = state.isTimetableUpdating,
362+
enter = expandVertically(),
363+
exit = shrinkVertically()
364+
) {
365+
LinearProgressIndicator(Modifier.fillMaxWidth())
366+
}
359367
HorizontalDivider()
360368
val isUserDraggingPager = pagerState.interactionSource.collectIsDraggedAsState().value
361369
val isUserDraggingList = lazyListState.interactionSource.collectIsDraggedAsState().value

composeApp/src/commonMain/kotlin/plus/vplan/app/feature/calendar/ui/CalendarViewModel.kt

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,17 @@ import plus.vplan.app.core.model.Profile
3232
import plus.vplan.app.core.model.Week
3333
import plus.vplan.app.core.utils.date.atStartOfWeek
3434
import plus.vplan.app.core.utils.date.now
35+
import plus.vplan.app.core.utils.date.plus
3536
import plus.vplan.app.domain.usecase.GetCurrentDateTimeUseCase
3637
import plus.vplan.app.domain.usecase.GetDayUseCase
38+
import plus.vplan.app.feature.calendar.domain.usecase.DownloadDayIfNecessaryUseCase
3739
import plus.vplan.app.feature.calendar.domain.usecase.GetFirstLessonStartUseCase
3840
import plus.vplan.app.feature.calendar.domain.usecase.GetHolidaysUseCase
3941
import plus.vplan.app.feature.calendar.domain.usecase.GetLastDisplayTypeUseCase
4042
import plus.vplan.app.feature.calendar.domain.usecase.SetLastDisplayTypeUseCase
4143
import plus.vplan.app.utils.atStartOfMonth
4244
import plus.vplan.app.utils.inWholeMinutes
43-
import plus.vplan.app.utils.plus
45+
import kotlin.comparisons.nullsLast
4446
import kotlin.time.Duration.Companion.days
4547

4648
class CalendarViewModel(
@@ -52,6 +54,7 @@ class CalendarViewModel(
5254
private val getFirstLessonStartUseCase: GetFirstLessonStartUseCase,
5355
private val getHolidaysUseCase: GetHolidaysUseCase,
5456
private val keyValueRepository: KeyValueRepository,
57+
private val downloadDayIfNecessaryUseCase: DownloadDayIfNecessaryUseCase,
5558
) : ViewModel() {
5659
val state: StateFlow<CalendarState>
5760
field = MutableStateFlow(CalendarState())
@@ -67,6 +70,12 @@ class CalendarViewModel(
6770
}
6871
}
6972

73+
viewModelScope.launch {
74+
downloadDayIfNecessaryUseCase.isRunning.collect { isRunning ->
75+
state.update { it.copy(isTimetableUpdating = isRunning) }
76+
}
77+
}
78+
7079
viewModelScope.launch {
7180
getLastDisplayTypeUseCase().collect { displayType ->
7281
state.update { it.copy(displayType = displayType) }
@@ -110,6 +119,9 @@ class CalendarViewModel(
110119

111120
private fun launchDay(profile: Profile, date: LocalDate): Job {
112121
return viewModelScope.launch {
122+
launch {
123+
downloadDayIfNecessaryUseCase(date, profile.school)
124+
}
113125
keyValueRepository.get(Keys.forceReducedCalendarView.key)
114126
.map { it.toBoolean() }
115127
.collectLatest { forceReduced ->
@@ -199,7 +211,8 @@ data class CalendarState(
199211
val start: LocalTime = LocalTime(0, 0),
200212
val holidays: Set<LocalDate> = emptySet(),
201213
val selectorDays: Map<LocalDate, DateSelectorDay> = emptyMap(),
202-
val calendarDays: Map<LocalDate, CalendarDay> = emptyMap()
214+
val calendarDays: Map<LocalDate, CalendarDay> = emptyMap(),
215+
val isTimetableUpdating: Boolean = false,
203216
)
204217

205218
sealed class CalendarEvent {
@@ -228,7 +241,7 @@ data class CalendarDay(
228241
val week: Week? = null,
229242
val assessments: List<Assessment> = emptyList(),
230243
val homework: List<Homework> = emptyList(),
231-
val lessons: LessonRendering? = null
244+
val lessons: LessonRendering? = null,
232245
)
233246

234247
suspend fun Collection<Lesson>.calculateLayouting(): List<LessonLayoutingInfo> {
@@ -238,7 +251,12 @@ suspend fun Collection<Lesson>.calculateLayouting(): List<LessonLayoutingInfo> {
238251
return withContext(Dispatchers.Default) {
239252

240253
val lessons = this@calculateLayouting
241-
.filter { it.lessonTime != null }
254+
.filter { lesson -> lesson.lessonTime != null }
255+
.sortedWith(
256+
compareBy<Lesson> { lesson -> lesson.lessonTime!!.start }
257+
.thenBy(nullsLast()) { lesson -> lesson.subject }
258+
.thenBy(nullsLast()) { lesson -> lesson.teachers.map { it.name }.sorted().joinToString() }
259+
)
242260
.sortedBy { it.lessonTime!!.start.inWholeMinutes() }
243261

244262
data class Event(val time: Int, val isStart: Boolean, val lesson: Lesson)

composeApp/src/commonMain/kotlin/plus/vplan/app/feature/calendar/ui/components/date_selector/Month.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import androidx.compose.ui.draw.alpha
1212
import androidx.compose.ui.unit.Dp
1313
import androidx.compose.ui.unit.dp
1414
import kotlinx.datetime.LocalDate
15+
import plus.vplan.app.core.utils.date.plus
1516
import plus.vplan.app.feature.calendar.ui.DateSelectorDay
1617
import plus.vplan.app.utils.minus
17-
import plus.vplan.app.utils.plus
1818
import kotlin.time.Duration.Companion.days
1919

2020
@Composable
@@ -27,7 +27,7 @@ fun Month(
2727
scrollProgress: Float,
2828
onDateSelected: (DateSelectionCause, LocalDate) -> Unit = { _, _ -> },
2929
) {
30-
var weekRowHeight = remember(scrollProgress, containerMaxHeight) { if (scrollProgress <= 1f) weekHeightDefault * scrollProgress
30+
val weekRowHeight = remember(scrollProgress, containerMaxHeight) { if (scrollProgress <= 1f) weekHeightDefault * scrollProgress
3131
else (containerMaxHeight / 5) * (scrollProgress/2).coerceIn(0f, 1f) }
3232

3333
Column(Modifier.fillMaxWidth()) {

composeApp/src/commonMain/kotlin/plus/vplan/app/feature/calendar/ui/components/date_selector/Week.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import androidx.compose.runtime.remember
88
import androidx.compose.ui.Modifier
99
import androidx.compose.ui.unit.Dp
1010
import kotlinx.datetime.LocalDate
11+
import plus.vplan.app.core.utils.date.plus
1112
import plus.vplan.app.feature.calendar.ui.DateSelectorDay
12-
import plus.vplan.app.utils.plus
1313
import kotlin.time.Duration.Companion.days
1414

1515
@Composable

composeApp/src/commonMain/kotlin/plus/vplan/app/feature/calendar/ui/components/date_selector/WeekScroller.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import kotlinx.datetime.plus
1414
import kotlinx.datetime.until
1515
import plus.vplan.app.core.utils.date.atStartOfWeek
1616
import plus.vplan.app.core.utils.date.now
17+
import plus.vplan.app.core.utils.date.plus
1718
import plus.vplan.app.feature.calendar.ui.DateSelectorDay
1819
import plus.vplan.app.utils.minus
19-
import plus.vplan.app.utils.plus
2020
import kotlin.time.Duration.Companion.days
2121

2222
const val WEEK_PAGER_SIZE = Int.MAX_VALUE

composeApp/src/commonMain/kotlin/plus/vplan/app/feature/home/ui/HomeScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -986,7 +986,7 @@ private fun RefreshIndicator(
986986
@Composable
987987
private fun RefreshIndicatorPreview() {
988988
AppTheme(dynamicColor = false) {
989-
RefreshIndicator(HomeState.CurrentUpdateStage.Timetable)
989+
RefreshIndicator(HomeState.CurrentUpdateStage.Timetable(HomeState.CurrentUpdateStage.Timetable.ForWeek.Next))
990990
}
991991
}
992992

0 commit comments

Comments
 (0)