Compare commits

..
Author SHA1 Message Date
Agzamus 84ae97b906 feat: group display on schedule screen 2026-09-03 19:59:21 +03:00
Agzamus 2069ec811a ci: removed "Upload APK as artifact" action step 2026-09-03 19:47:06 +03:00
12 changed files with 79 additions and 210 deletions
-6
View File
@@ -112,9 +112,3 @@ jobs:
files: ${{ steps.apk.outputs.apk_path }} files: ${{ steps.apk.outputs.apk_path }}
token: ${{ secrets.GITEA_TOKEN }} token: ${{ secrets.GITEA_TOKEN }}
server_url: ${{ gitea.server_url }} server_url: ${{ gitea.server_url }}
- name: Upload APK as artifact
uses: actions/upload-artifact@v4
with:
name: vyatsu-schedule-${{ steps.version.outputs.version_name }}
path: ${{ steps.apk.outputs.apk_path }}
+1 -1
View File
@@ -41,5 +41,5 @@ private class SimpleFactory<T : ViewModel>(private val create: () -> T) : ViewMo
override fun <T : ViewModel> create(modelClass: Class<T>): T = create() as T override fun <T : ViewModel> create(modelClass: Class<T>): T = create() as T
} }
private fun scheduleFactory(c: AppContainer): ViewModelProvider.Factory = SimpleFactory { ScheduleViewModel(c.scheduleRepository, c.preferencesRepository) } private fun scheduleFactory(c: AppContainer): ViewModelProvider.Factory = SimpleFactory { ScheduleViewModel(c.scheduleRepository, c.preferencesRepository, c.groupRepository) }
private fun groupPickerFactory(c: AppContainer): ViewModelProvider.Factory = SimpleFactory { GroupPickerViewModel(c.groupRepository) } private fun groupPickerFactory(c: AppContainer): ViewModelProvider.Factory = SimpleFactory { GroupPickerViewModel(c.groupRepository) }
@@ -15,16 +15,4 @@ interface PreferencesDao {
@Query("DELETE FROM preferences WHERE key = 'selectedGroupId'") @Query("DELETE FROM preferences WHERE key = 'selectedGroupId'")
suspend fun clearSelectedGroup() suspend fun clearSelectedGroup()
@Query("SELECT value FROM preferences WHERE key = 'selectedWeekStart'")
suspend fun selectedWeekStart(): String?
@Query("INSERT OR REPLACE INTO preferences(key, value) VALUES('selectedWeekStart', :weekStart)")
suspend fun setSelectedWeekStart(weekStart: String)
@Query("SELECT value FROM preferences WHERE key = 'selectedDayDate'")
suspend fun selectedDayDate(): String?
@Query("INSERT OR REPLACE INTO preferences(key, value) VALUES('selectedDayDate', :date)")
suspend fun setSelectedDayDate(date: String)
} }
@@ -42,7 +42,7 @@ interface ScheduleDao {
suspend fun trim(groupId: String, keep: Int) suspend fun trim(groupId: String, keep: Int)
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertDays(days: List<DayEntity>): List<Long> suspend fun insertDays(days: List<DayEntity>)
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertLessons(lessons: List<LessonEntity>) suspend fun insertLessons(lessons: List<LessonEntity>)
@@ -23,6 +23,9 @@ class GroupRepositoryImpl(
override suspend fun getGroups(): List<Group> = override suspend fun getGroups(): List<Group> =
groupDao.observeAll().first().map { it.toDomain() } groupDao.observeAll().first().map { it.toDomain() }
override suspend fun getGroup(id: String): Group? =
groupDao.getById(id)?.toDomain()
override suspend fun getFaculties(): List<String> = groupDao.getFaculties() override suspend fun getFaculties(): List<String> = groupDao.getFaculties()
override suspend fun refreshIfStale(): Boolean { override suspend fun refreshIfStale(): Boolean {
@@ -9,8 +9,4 @@ class PreferencesRepositoryImpl(
override suspend fun selectedGroupId(): String? = dao.selectedGroupId() override suspend fun selectedGroupId(): String? = dao.selectedGroupId()
override suspend fun setSelectedGroup(groupId: String) = dao.setSelectedGroup(groupId) override suspend fun setSelectedGroup(groupId: String) = dao.setSelectedGroup(groupId)
override suspend fun clearSelectedGroup() = dao.clearSelectedGroup() override suspend fun clearSelectedGroup() = dao.clearSelectedGroup()
override suspend fun selectedWeekStart(): String? = dao.selectedWeekStart()
override suspend fun setSelectedWeekStart(weekStart: String) = dao.setSelectedWeekStart(weekStart)
override suspend fun selectedDayDate(): String? = dao.selectedDayDate()
override suspend fun setSelectedDayDate(date: String) = dao.setSelectedDayDate(date)
} }
@@ -112,8 +112,11 @@ class ScheduleRepositoryImpl(
private suspend fun replaceDays(scheduleId: Long, days: List<Day>) { private suspend fun replaceDays(scheduleId: Long, days: List<Day>) {
scheduleDao.deleteDaysForSchedule(scheduleId) scheduleDao.deleteDaysForSchedule(scheduleId)
val dayEntities = days.map { DayEntity(scheduleId = scheduleId, date = it.date.toEpochDay()) } val dayEntities = days.map { DayEntity(scheduleId = scheduleId, date = it.date.toEpochDay()) }
val dayIds = scheduleDao.insertDays(dayEntities) val dayIdByDate = mutableMapOf<LocalDate, Long>()
val dayIdByDate = dayEntities.zip(dayIds).associate { (day, id) -> LocalDate.ofEpochDay(day.date) to id } scheduleDao.insertDays(dayEntities)
for (day in dayEntities) {
dayIdByDate[LocalDate.ofEpochDay(day.date)] = day.id
}
val lessons = days.flatMap { day -> val lessons = days.flatMap { day ->
day.intervals.flatMapIndexed { slotIndex, slot -> day.intervals.flatMapIndexed { slotIndex, slot ->
slot.lessons.mapIndexed { lessonIndex, lesson -> slot.lessons.mapIndexed { lessonIndex, lesson ->
@@ -15,7 +15,4 @@ data class Schedule(
) { ) {
fun periodOf(date: LocalDate): SchedulePeriod? = fun periodOf(date: LocalDate): SchedulePeriod? =
periods.firstOrNull { !date.isBefore(it.weekStart) && !date.isAfter(it.weekEnd) } periods.firstOrNull { !date.isBefore(it.weekStart) && !date.isAfter(it.weekEnd) }
fun daysOf(period: SchedulePeriod): List<Day> =
days.filter { !it.date.isBefore(period.weekStart) && !it.date.isAfter(period.weekEnd) }
} }
@@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.Flow
interface GroupRepository { interface GroupRepository {
fun observeGroups(): Flow<List<Group>> fun observeGroups(): Flow<List<Group>>
suspend fun getGroups(): List<Group> suspend fun getGroups(): List<Group>
suspend fun getGroup(id: String): Group?
suspend fun getFaculties(): List<String> suspend fun getFaculties(): List<String>
suspend fun refreshIfStale(): Boolean suspend fun refreshIfStale(): Boolean
suspend fun forceRefresh(): Boolean suspend fun forceRefresh(): Boolean
@@ -4,8 +4,4 @@ interface PreferencesRepository {
suspend fun selectedGroupId(): String? suspend fun selectedGroupId(): String?
suspend fun setSelectedGroup(groupId: String) suspend fun setSelectedGroup(groupId: String)
suspend fun clearSelectedGroup() suspend fun clearSelectedGroup()
suspend fun selectedWeekStart(): String?
suspend fun setSelectedWeekStart(weekStart: String)
suspend fun selectedDayDate(): String?
suspend fun setSelectedDayDate(date: String)
} }
@@ -1,7 +1,6 @@
package agzam.vyatsu.ui.schedule package agzam.vyatsu.ui.schedule
import agzam.vyatsu.domain.model.Day import agzam.vyatsu.domain.model.Day
import agzam.vyatsu.domain.model.SchedulePeriod
import agzam.vyatsu.domain.model.TimeSlot import agzam.vyatsu.domain.model.TimeSlot
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -14,7 +13,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -37,7 +35,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import java.time.DayOfWeek import java.time.DayOfWeek
import java.time.LocalDate
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -47,7 +44,7 @@ fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) {
Column(Modifier.fillMaxSize()) { Column(Modifier.fillMaxSize()) {
TopAppBar( TopAppBar(
title = { Text("Расписание") }, title = { Text(state.groupName ?: "Расписание", style = MaterialTheme.typography.titleMedium) },
actions = { actions = {
TextButton(onClick = { onSwitchGroup() }) { TextButton(onClick = { onSwitchGroup() }) {
Text("Сменить группу") Text("Сменить группу")
@@ -56,7 +53,7 @@ fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) {
Icon(Icons.Default.Refresh, contentDescription = "Обновить") Icon(Icons.Default.Refresh, contentDescription = "Обновить")
} }
}, },
windowInsets = WindowInsets.systemBars.only(androidx.compose.foundation.layout.WindowInsetsSides.Top) windowInsets = WindowInsets(0.dp)
) )
Column(Modifier.fillMaxSize().padding(horizontal = 16.dp, vertical = 0.dp)) { Column(Modifier.fillMaxSize().padding(horizontal = 16.dp, vertical = 0.dp)) {
when { when {
@@ -86,17 +83,12 @@ fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) {
else -> { else -> {
val schedule = state.schedule!! val schedule = state.schedule!!
val weekDays = state.weekStart val day = schedule.days.getOrNull(state.selectedIndex)
?.let { ws -> schedule.periods.firstOrNull { it.weekStart == ws } }
?.let { schedule.daysOf(it) }
.orEmpty()
val day = schedule.days.firstOrNull { it.date == state.dayDate }
if (day == null) { if (day == null) {
Text("Занятий нет", style = MaterialTheme.typography.bodyLarge) Text("Занятий нет", style = MaterialTheme.typography.bodyLarge)
} else { } else {
val isTodayDay = day.date == state.todayDate val isToday = state.selectedIndex == state.todayIndex
val dayLabel = if (isTodayDay) "Сегодня " else "" val dayLabel = if (isToday) "Сегодня " else ""
Text( Text(
"$dayLabel ${dayName(day.date.dayOfWeek)} · ${day.date.format(FULL_DATE)}", "$dayLabel ${dayName(day.date.dayOfWeek)} · ${day.date.format(FULL_DATE)}",
@@ -107,7 +99,7 @@ fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) {
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
TextButton(onClick = { viewModel.clearSelectedPeriod() }) { TextButton(onClick = { viewModel.clearSelectedPeriod() }) {
Text("Очистить эту неделю") Text("Очистить неделю")
} }
TextButton(onClick = { viewModel.clearAll() }) { TextButton(onClick = { viewModel.clearAll() }) {
Text("Очистить всё") Text("Очистить всё")
@@ -115,19 +107,10 @@ fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) {
} }
HorizontalDivider(Modifier.padding(vertical = 8.dp)) HorizontalDivider(Modifier.padding(vertical = 8.dp))
WeekChips(
periods = schedule.periods,
selectedWeekStart = state.weekStart,
todayWeekStart = state.todayDate?.let { schedule.periodOf(it)?.weekStart },
onSelect = viewModel::selectWeek
)
HorizontalDivider(Modifier.padding(vertical = 8.dp))
DayChips( DayChips(
days = weekDays, days = schedule.days,
selectedDate = state.dayDate, selectedIndex = state.selectedIndex,
todayDate = state.todayDate, todayIndex = state.todayIndex,
onSelect = viewModel::selectDay onSelect = viewModel::selectDay
) )
@@ -152,57 +135,20 @@ fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) {
} }
} }
@Composable
private fun WeekChips(
periods: List<SchedulePeriod>,
selectedWeekStart: LocalDate?,
todayWeekStart: LocalDate?,
onSelect: (LocalDate) -> Unit
) {
LazyRow(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
items(periods, key = { it.weekStart.toEpochDay() }) { period ->
val selected = selectedWeekStart == period.weekStart
val isTodayWeek = todayWeekStart == period.weekStart
FilterChip(
selected = selected,
onClick = { onSelect(period.weekStart) },
label = {
Text(
weekLabel(period),
style = MaterialTheme.typography.labelLarge
)
},
modifier = Modifier.padding(vertical = 2.dp),
border = if (isTodayWeek && !selected) {
FilterChipDefaults.filterChipBorder(
borderColor = MaterialTheme.colorScheme.primary,
borderWidth = 1.dp,
enabled = true,
selected = false
)
} else {
FilterChipDefaults.filterChipBorder(enabled = true, selected = selected)
},
shape = RoundedCornerShape(8.dp)
)
}
}
}
@Composable @Composable
private fun DayChips( private fun DayChips(
days: List<Day>, days: List<Day>,
selectedDate: LocalDate?, selectedIndex: Int,
todayDate: LocalDate?, todayIndex: Int,
onSelect: (LocalDate) -> Unit onSelect: (Int) -> Unit
) { ) {
LazyRow(horizontalArrangement = Arrangement.spacedBy(6.dp)) { LazyRow(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
items(days, key = { it.date.toEpochDay() }) { day -> itemsIndexed(days) { index, day ->
val selected = selectedDate == day.date val selected = selectedIndex == index
val isTodayChip = todayDate == day.date val isTodayChip = todayIndex == index
FilterChip( FilterChip(
selected = selected, selected = selected,
onClick = { onSelect(day.date) }, onClick = { onSelect(index) },
label = { label = {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text( Text(
@@ -237,11 +183,6 @@ private fun DayChips(
} }
} }
private fun weekLabel(period: SchedulePeriod): String {
val first = period.weekStart
return "с ${first.format(CHIP_DATE)}"
}
@Composable @Composable
private fun LessonSlot(slotIndex: Int, slot: TimeSlot) { private fun LessonSlot(slotIndex: Int, slot: TimeSlot) {
Column(Modifier.fillMaxWidth()) { Column(Modifier.fillMaxWidth()) {
@@ -260,12 +201,8 @@ private fun LessonSlot(slotIndex: Int, slot: TimeSlot) {
style = MaterialTheme.typography.bodyLarge style = MaterialTheme.typography.bodyLarge
) )
Text( Text(
listOfNotNull( listOf(lesson.type.label, lesson.subgroup, lesson.teacher, lesson.room)
lesson.type.label, .filterNotNull()
lesson.subgroup,
lesson.teacher,
lesson.room
)
.filter { it.isNotBlank() } .filter { it.isNotBlank() }
.joinToString(" · "), .joinToString(" · "),
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
@@ -1,7 +1,9 @@
package agzam.vyatsu.ui.schedule package agzam.vyatsu.ui.schedule
import agzam.vyatsu.domain.ScheduleDates import agzam.vyatsu.domain.ScheduleDates
import agzam.vyatsu.domain.model.Day
import agzam.vyatsu.domain.model.Schedule import agzam.vyatsu.domain.model.Schedule
import agzam.vyatsu.domain.repository.GroupRepository
import agzam.vyatsu.domain.repository.PreferencesRepository import agzam.vyatsu.domain.repository.PreferencesRepository
import agzam.vyatsu.domain.repository.ScheduleRepository import agzam.vyatsu.domain.repository.ScheduleRepository
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@@ -10,11 +12,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.time.LocalDate
class ScheduleViewModel( class ScheduleViewModel(
private val scheduleRepository: ScheduleRepository, private val scheduleRepository: ScheduleRepository,
private val preferencesRepository: PreferencesRepository private val preferencesRepository: PreferencesRepository,
private val groupRepository: GroupRepository
) : ViewModel() { ) : ViewModel() {
private val _uiState = MutableStateFlow(ScheduleUiState()) private val _uiState = MutableStateFlow(ScheduleUiState())
@@ -37,102 +39,32 @@ class ScheduleViewModel(
currentGroupId = groupId currentGroupId = groupId
viewModelScope.launch { viewModelScope.launch {
preferencesRepository.setSelectedGroup(groupId) preferencesRepository.setSelectedGroup(groupId)
val groupName = groupRepository.getGroup(groupId)?.name
val savedWeekStart = runCatching { _uiState.value = _uiState.value.copy(noGroup = false, groupName = groupName)
preferencesRepository.selectedWeekStart()?.let(LocalDate::parse)
}.getOrNull()
val savedDayDate = runCatching {
preferencesRepository.selectedDayDate()?.let(LocalDate::parse)
}.getOrNull()
_uiState.value = _uiState.value.copy(
noGroup = false,
weekStart = savedWeekStart,
dayDate = savedDayDate
)
launch { launch {
scheduleRepository.observeSchedule(groupId).collect { schedule -> scheduleRepository.observeSchedule(groupId).collect { schedule ->
currentGroupId?.let { applySchedule(schedule) } currentGroupId?.let {
val days = schedule?.days.orEmpty()
val today = todayIndex(days)
val current = _uiState.value
val selected = current.selectedIndex.takeIf { i -> i in days.indices } ?: today
_uiState.value = current.copy(
schedule = schedule,
todayIndex = today,
selectedIndex = selected
)
}
} }
} }
launch { launch {
syncIfMissingToday(groupId)
}
}
}
private suspend fun applySchedule(schedule: Schedule?) {
val current = _uiState.value
if (schedule == null || schedule.periods.isEmpty()) {
_uiState.value = current.copy(schedule = schedule, todayDate = null)
return
}
val today = ScheduleDates.moscowToday()
val todayDate = if (schedule.periodOf(today) != null) today else null
val weekStart = when {
schedule.periods.any { it.weekStart == current.weekStart } -> current.weekStart!!
todayDate != null -> schedule.periodOf(todayDate)!!.weekStart
else -> schedule.periods.first().weekStart
}
val period = schedule.periods.first { it.weekStart == weekStart }
val weekDays = schedule.daysOf(period)
val dayDate = when {
weekDays.any { it.date == current.dayDate } -> current.dayDate
weekDays.any { it.date == todayDate } -> todayDate
else -> weekDays.firstOrNull()?.date
}
preferencesRepository.setSelectedWeekStart(weekStart.toString())
dayDate?.let { preferencesRepository.setSelectedDayDate(it.toString()) }
_uiState.value = current.copy(
schedule = schedule,
weekStart = weekStart,
dayDate = dayDate,
todayDate = todayDate
)
}
fun selectWeek(weekStart: LocalDate) {
val state = _uiState.value
val schedule = state.schedule ?: return
val period = schedule.periods.firstOrNull { it.weekStart == weekStart } ?: return
viewModelScope.launch {
val today = ScheduleDates.moscowToday()
val weekDays = schedule.daysOf(period)
val dayDate = when {
weekDays.any { it.date == today } -> today
else -> weekDays.firstOrNull()?.date
}
preferencesRepository.setSelectedWeekStart(weekStart.toString())
dayDate?.let { preferencesRepository.setSelectedDayDate(it.toString()) }
_uiState.value = _uiState.value.copy(weekStart = weekStart, dayDate = dayDate)
}
}
fun selectDay(date: LocalDate) {
val state = _uiState.value
val schedule = state.schedule ?: return
val period = schedule.periodOf(date) ?: return
viewModelScope.launch {
preferencesRepository.setSelectedWeekStart(period.weekStart.toString())
preferencesRepository.setSelectedDayDate(date.toString())
_uiState.value = _uiState.value.copy(weekStart = period.weekStart, dayDate = date)
}
}
private suspend fun syncIfMissingToday(groupId: String) {
val schedule = scheduleRepository.getSchedule(groupId)
val today = ScheduleDates.moscowToday()
val hasTodayPeriod = schedule?.periodOf(today) != null
if (hasTodayPeriod) return
_uiState.value = _uiState.value.copy(isSyncing = true) _uiState.value = _uiState.value.copy(isSyncing = true)
scheduleRepository.sync(groupId) scheduleRepository.sync(groupId)
_uiState.value = _uiState.value.copy(isSyncing = false) _uiState.value = _uiState.value.copy(isSyncing = false)
} }
}
}
fun switchGroup() { fun switchGroup() {
viewModelScope.launch { viewModelScope.launch {
@@ -147,10 +79,23 @@ class ScheduleViewModel(
viewModelScope.launch { viewModelScope.launch {
_uiState.value = _uiState.value.copy(isRefreshing = true) _uiState.value = _uiState.value.copy(isRefreshing = true)
scheduleRepository.sync(groupId) scheduleRepository.sync(groupId)
_uiState.value = _uiState.value.copy(isRefreshing = false) val current = _uiState.value
val days = current.schedule?.days.orEmpty()
val today = todayIndex(days)
_uiState.value = current.copy(
isRefreshing = false,
todayIndex = today,
selectedIndex = current.selectedIndex.takeIf { it in days.indices } ?: today
)
} }
} }
fun selectDay(index: Int) {
val days = _uiState.value.schedule?.days.orEmpty()
if (index !in days.indices) return
_uiState.value = _uiState.value.copy(selectedIndex = index)
}
fun clearAll() { fun clearAll() {
val groupId = currentGroupId ?: return val groupId = currentGroupId ?: return
viewModelScope.launch { viewModelScope.launch {
@@ -161,20 +106,29 @@ class ScheduleViewModel(
fun clearSelectedPeriod() { fun clearSelectedPeriod() {
val state = _uiState.value val state = _uiState.value
val schedule = state.schedule ?: return val schedule = state.schedule ?: return
val weekStart = state.weekStart ?: return val day = schedule.days.getOrNull(state.selectedIndex) ?: return
val period = schedule.periods.firstOrNull { it.weekStart == weekStart } ?: return val period = schedule.periodOf(day.date) ?: return
viewModelScope.launch { viewModelScope.launch {
scheduleRepository.clearPeriod(schedule.groupId, period) scheduleRepository.clearPeriod(schedule.groupId, period)
} }
} }
private fun todayIndex(days: List<Day>): Int {
if (days.isEmpty()) return 0
val today = ScheduleDates.moscowToday()
val exact = days.indexOfFirst { it.date == today }
if (exact >= 0) return exact
val next = days.indexOfFirst { !it.date.isBefore(today) }
return if (next >= 0) next else days.lastIndex
}
} }
data class ScheduleUiState( data class ScheduleUiState(
val noGroup: Boolean = false, val noGroup: Boolean = false,
val isRefreshing: Boolean = false, val isRefreshing: Boolean = false,
val isSyncing: Boolean = false, val isSyncing: Boolean = false,
val groupName: String? = null,
val schedule: Schedule? = null, val schedule: Schedule? = null,
val weekStart: LocalDate? = null, val todayIndex: Int = 0,
val dayDate: LocalDate? = null, val selectedIndex: Int = 0
val todayDate: LocalDate? = null
) )