diff --git a/app/src/main/java/agzam/vyatsu/data/local/PreferencesDao.kt b/app/src/main/java/agzam/vyatsu/data/local/PreferencesDao.kt index b7ac864..a5ac5f0 100644 --- a/app/src/main/java/agzam/vyatsu/data/local/PreferencesDao.kt +++ b/app/src/main/java/agzam/vyatsu/data/local/PreferencesDao.kt @@ -15,4 +15,16 @@ interface PreferencesDao { @Query("DELETE FROM preferences WHERE key = 'selectedGroupId'") 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) } diff --git a/app/src/main/java/agzam/vyatsu/data/local/ScheduleDao.kt b/app/src/main/java/agzam/vyatsu/data/local/ScheduleDao.kt index eb055a0..afeb5ad 100644 --- a/app/src/main/java/agzam/vyatsu/data/local/ScheduleDao.kt +++ b/app/src/main/java/agzam/vyatsu/data/local/ScheduleDao.kt @@ -42,7 +42,7 @@ interface ScheduleDao { suspend fun trim(groupId: String, keep: Int) @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertDays(days: List) + suspend fun insertDays(days: List): List @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertLessons(lessons: List) diff --git a/app/src/main/java/agzam/vyatsu/data/repository/PreferencesRepositoryImpl.kt b/app/src/main/java/agzam/vyatsu/data/repository/PreferencesRepositoryImpl.kt index 93452cf..5ad238e 100644 --- a/app/src/main/java/agzam/vyatsu/data/repository/PreferencesRepositoryImpl.kt +++ b/app/src/main/java/agzam/vyatsu/data/repository/PreferencesRepositoryImpl.kt @@ -9,4 +9,8 @@ class PreferencesRepositoryImpl( override suspend fun selectedGroupId(): String? = dao.selectedGroupId() override suspend fun setSelectedGroup(groupId: String) = dao.setSelectedGroup(groupId) 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) } diff --git a/app/src/main/java/agzam/vyatsu/data/repository/ScheduleRepositoryImpl.kt b/app/src/main/java/agzam/vyatsu/data/repository/ScheduleRepositoryImpl.kt index 9c5df46..be28ecf 100644 --- a/app/src/main/java/agzam/vyatsu/data/repository/ScheduleRepositoryImpl.kt +++ b/app/src/main/java/agzam/vyatsu/data/repository/ScheduleRepositoryImpl.kt @@ -112,11 +112,8 @@ class ScheduleRepositoryImpl( private suspend fun replaceDays(scheduleId: Long, days: List) { scheduleDao.deleteDaysForSchedule(scheduleId) val dayEntities = days.map { DayEntity(scheduleId = scheduleId, date = it.date.toEpochDay()) } - val dayIdByDate = mutableMapOf() - scheduleDao.insertDays(dayEntities) - for (day in dayEntities) { - dayIdByDate[LocalDate.ofEpochDay(day.date)] = day.id - } + val dayIds = scheduleDao.insertDays(dayEntities) + val dayIdByDate = dayEntities.zip(dayIds).associate { (day, id) -> LocalDate.ofEpochDay(day.date) to id } val lessons = days.flatMap { day -> day.intervals.flatMapIndexed { slotIndex, slot -> slot.lessons.mapIndexed { lessonIndex, lesson -> diff --git a/app/src/main/java/agzam/vyatsu/domain/model/Schedule.kt b/app/src/main/java/agzam/vyatsu/domain/model/Schedule.kt index 4d24b76..06ef506 100644 --- a/app/src/main/java/agzam/vyatsu/domain/model/Schedule.kt +++ b/app/src/main/java/agzam/vyatsu/domain/model/Schedule.kt @@ -15,4 +15,7 @@ data class Schedule( ) { fun periodOf(date: LocalDate): SchedulePeriod? = periods.firstOrNull { !date.isBefore(it.weekStart) && !date.isAfter(it.weekEnd) } + + fun daysOf(period: SchedulePeriod): List = + days.filter { !it.date.isBefore(period.weekStart) && !it.date.isAfter(period.weekEnd) } } diff --git a/app/src/main/java/agzam/vyatsu/domain/repository/PreferencesRepository.kt b/app/src/main/java/agzam/vyatsu/domain/repository/PreferencesRepository.kt index bcadd2f..c4a6307 100644 --- a/app/src/main/java/agzam/vyatsu/domain/repository/PreferencesRepository.kt +++ b/app/src/main/java/agzam/vyatsu/domain/repository/PreferencesRepository.kt @@ -4,4 +4,8 @@ interface PreferencesRepository { suspend fun selectedGroupId(): String? suspend fun setSelectedGroup(groupId: String) suspend fun clearSelectedGroup() + suspend fun selectedWeekStart(): String? + suspend fun setSelectedWeekStart(weekStart: String) + suspend fun selectedDayDate(): String? + suspend fun setSelectedDayDate(date: String) } diff --git a/app/src/main/java/agzam/vyatsu/ui/schedule/ScheduleScreen.kt b/app/src/main/java/agzam/vyatsu/ui/schedule/ScheduleScreen.kt index 64defc0..34067d4 100644 --- a/app/src/main/java/agzam/vyatsu/ui/schedule/ScheduleScreen.kt +++ b/app/src/main/java/agzam/vyatsu/ui/schedule/ScheduleScreen.kt @@ -1,6 +1,7 @@ package agzam.vyatsu.ui.schedule import agzam.vyatsu.domain.model.Day +import agzam.vyatsu.domain.model.SchedulePeriod import agzam.vyatsu.domain.model.TimeSlot import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -13,6 +14,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons @@ -35,6 +37,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import java.time.DayOfWeek +import java.time.LocalDate import java.time.format.DateTimeFormatter @OptIn(ExperimentalMaterial3Api::class) @@ -55,7 +58,7 @@ fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) { }, 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 { state.isSyncing && state.schedule == null -> { Row( @@ -83,12 +86,17 @@ fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) { else -> { val schedule = state.schedule!! - val day = schedule.days.getOrNull(state.selectedIndex) + val weekDays = state.weekStart + ?.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) { Text("Занятий нет", style = MaterialTheme.typography.bodyLarge) } else { - val isToday = state.selectedIndex == state.todayIndex - val dayLabel = if (isToday) "Сегодня " else "" + val isTodayDay = day.date == state.todayDate + val dayLabel = if (isTodayDay) "Сегодня " else "" Text( "$dayLabel ${dayName(day.date.dayOfWeek)} · ${day.date.format(FULL_DATE)}", @@ -99,7 +107,7 @@ fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) { horizontalArrangement = Arrangement.spacedBy(8.dp) ) { TextButton(onClick = { viewModel.clearSelectedPeriod() }) { - Text("Очистить неделю") + Text("Очистить эту неделю") } TextButton(onClick = { viewModel.clearAll() }) { Text("Очистить всё") @@ -107,10 +115,19 @@ fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) { } 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( - days = schedule.days, - selectedIndex = state.selectedIndex, - todayIndex = state.todayIndex, + days = weekDays, + selectedDate = state.dayDate, + todayDate = state.todayDate, onSelect = viewModel::selectDay ) @@ -136,19 +153,56 @@ fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) { } @Composable -private fun DayChips( - days: List, - selectedIndex: Int, - todayIndex: Int, - onSelect: (Int) -> Unit +private fun WeekChips( + periods: List, + selectedWeekStart: LocalDate?, + todayWeekStart: LocalDate?, + onSelect: (LocalDate) -> Unit ) { LazyRow(horizontalArrangement = Arrangement.spacedBy(6.dp)) { - itemsIndexed(days) { index, day -> - val selected = selectedIndex == index - val isTodayChip = todayIndex == index + items(periods, key = { it.weekStart.toEpochDay() }) { period -> + val selected = selectedWeekStart == period.weekStart + val isTodayWeek = todayWeekStart == period.weekStart FilterChip( selected = selected, - onClick = { onSelect(index) }, + 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 +private fun DayChips( + days: List, + selectedDate: LocalDate?, + todayDate: LocalDate?, + onSelect: (LocalDate) -> Unit +) { + LazyRow(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + items(days, key = { it.date.toEpochDay() }) { day -> + val selected = selectedDate == day.date + val isTodayChip = todayDate == day.date + FilterChip( + selected = selected, + onClick = { onSelect(day.date) }, label = { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text( @@ -183,6 +237,11 @@ private fun DayChips( } } +private fun weekLabel(period: SchedulePeriod): String { + val first = period.weekStart + return "с ${first.format(CHIP_DATE)}" +} + @Composable private fun LessonSlot(slotIndex: Int, slot: TimeSlot) { Column(Modifier.fillMaxWidth()) { @@ -201,8 +260,12 @@ private fun LessonSlot(slotIndex: Int, slot: TimeSlot) { style = MaterialTheme.typography.bodyLarge ) Text( - listOf(lesson.type.label, lesson.subgroup, lesson.teacher, lesson.room) - .filterNotNull() + listOfNotNull( + lesson.type.label, + lesson.subgroup, + lesson.teacher, + lesson.room + ) .filter { it.isNotBlank() } .joinToString(" · "), style = MaterialTheme.typography.bodySmall, @@ -226,4 +289,4 @@ private fun dayName(dayOfWeek: DayOfWeek): String = when (dayOfWeek) { private val FULL_DATE: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy") private val CHIP_DATE: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM") -private val TIME: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm") \ No newline at end of file +private val TIME: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")