Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84ae97b906 | ||
|
|
2069ec811a |
@@ -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 }}
|
|
||||||
|
|||||||
@@ -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,9 +53,9 @@ 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 {
|
||||||
state.isSyncing && state.schedule == null -> {
|
state.isSyncing && state.schedule == null -> {
|
||||||
Row(
|
Row(
|
||||||
@@ -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,
|
||||||
@@ -289,4 +226,4 @@ private fun dayName(dayOfWeek: DayOfWeek): String = when (dayOfWeek) {
|
|||||||
|
|
||||||
private val FULL_DATE: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy")
|
private val FULL_DATE: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy")
|
||||||
private val CHIP_DATE: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM")
|
private val CHIP_DATE: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM")
|
||||||
private val TIME: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
|
private val TIME: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
|
||||||
@@ -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,103 +39,33 @@ 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)
|
_uiState.value = _uiState.value.copy(isSyncing = true)
|
||||||
|
scheduleRepository.sync(groupId)
|
||||||
|
_uiState.value = _uiState.value.copy(isSyncing = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
scheduleRepository.sync(groupId)
|
|
||||||
_uiState.value = _uiState.value.copy(isSyncing = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun switchGroup() {
|
fun switchGroup() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
currentGroupId = null
|
currentGroupId = null
|
||||||
@@ -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
|
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user