Compare commits
3
Commits
main
...
96161d6844
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96161d6844 | ||
|
|
84ae97b906 | ||
|
|
2069ec811a |
@@ -112,9 +112,3 @@ jobs:
|
||||
files: ${{ steps.apk.outputs.apk_path }}
|
||||
token: ${{ secrets.GITEA_TOKEN }}
|
||||
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
|
||||
}
|
||||
|
||||
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) }
|
||||
|
||||
@@ -23,6 +23,9 @@ class GroupRepositoryImpl(
|
||||
override suspend fun getGroups(): List<Group> =
|
||||
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 refreshIfStale(): Boolean {
|
||||
|
||||
@@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
interface GroupRepository {
|
||||
fun observeGroups(): Flow<List<Group>>
|
||||
suspend fun getGroups(): List<Group>
|
||||
suspend fun getGroup(id: String): Group?
|
||||
suspend fun getFaculties(): List<String>
|
||||
suspend fun refreshIfStale(): Boolean
|
||||
suspend fun forceRefresh(): Boolean
|
||||
|
||||
@@ -47,7 +47,7 @@ fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) {
|
||||
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
TopAppBar(
|
||||
title = { Text("Расписание") },
|
||||
title = { Text(state.groupName ?: "Расписание", style = MaterialTheme.typography.titleMedium) },
|
||||
actions = {
|
||||
TextButton(onClick = { onSwitchGroup() }) {
|
||||
Text("Сменить группу")
|
||||
@@ -56,7 +56,7 @@ fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) {
|
||||
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)) {
|
||||
when {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package agzam.vyatsu.ui.schedule
|
||||
|
||||
import agzam.vyatsu.domain.ScheduleDates
|
||||
import agzam.vyatsu.domain.model.Day
|
||||
import agzam.vyatsu.domain.model.Schedule
|
||||
import agzam.vyatsu.domain.repository.GroupRepository
|
||||
import agzam.vyatsu.domain.repository.PreferencesRepository
|
||||
import agzam.vyatsu.domain.repository.ScheduleRepository
|
||||
import androidx.lifecycle.ViewModel
|
||||
@@ -10,11 +12,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.LocalDate
|
||||
|
||||
class ScheduleViewModel(
|
||||
private val scheduleRepository: ScheduleRepository,
|
||||
private val preferencesRepository: PreferencesRepository
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
private val groupRepository: GroupRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val _uiState = MutableStateFlow(ScheduleUiState())
|
||||
@@ -37,102 +39,32 @@ class ScheduleViewModel(
|
||||
currentGroupId = groupId
|
||||
viewModelScope.launch {
|
||||
preferencesRepository.setSelectedGroup(groupId)
|
||||
|
||||
val savedWeekStart = runCatching {
|
||||
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
|
||||
)
|
||||
val groupName = groupRepository.getGroup(groupId)?.name
|
||||
_uiState.value = _uiState.value.copy(noGroup = false, groupName = groupName)
|
||||
|
||||
launch {
|
||||
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 {
|
||||
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)
|
||||
scheduleRepository.sync(groupId)
|
||||
_uiState.value = _uiState.value.copy(isSyncing = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun switchGroup() {
|
||||
viewModelScope.launch {
|
||||
@@ -147,10 +79,23 @@ class ScheduleViewModel(
|
||||
viewModelScope.launch {
|
||||
_uiState.value = _uiState.value.copy(isRefreshing = true)
|
||||
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() {
|
||||
val groupId = currentGroupId ?: return
|
||||
viewModelScope.launch {
|
||||
@@ -161,20 +106,29 @@ class ScheduleViewModel(
|
||||
fun clearSelectedPeriod() {
|
||||
val state = _uiState.value
|
||||
val schedule = state.schedule ?: return
|
||||
val weekStart = state.weekStart ?: return
|
||||
val period = schedule.periods.firstOrNull { it.weekStart == weekStart } ?: return
|
||||
val day = schedule.days.getOrNull(state.selectedIndex) ?: return
|
||||
val period = schedule.periodOf(day.date) ?: return
|
||||
viewModelScope.launch {
|
||||
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(
|
||||
val noGroup: Boolean = false,
|
||||
val isRefreshing: Boolean = false,
|
||||
val isSyncing: Boolean = false,
|
||||
val groupName: String? = null,
|
||||
val schedule: Schedule? = null,
|
||||
val weekStart: LocalDate? = null,
|
||||
val dayDate: LocalDate? = null,
|
||||
val todayDate: LocalDate? = null
|
||||
val todayIndex: Int = 0,
|
||||
val selectedIndex: Int = 0
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user