feat: ViewModel in Schedule.kt

This commit is contained in:
2026-02-16 00:12:22 +03:00
parent 9b90b655ab
commit 568ca32668
@@ -2,7 +2,6 @@
package ru.sweetbread.unn.ui.composes package ru.sweetbread.unn.ui.composes
import android.util.Log
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
@@ -24,12 +23,11 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -43,9 +41,22 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
import ru.sweetbread.unn.R import ru.sweetbread.unn.R
import ru.sweetbread.unn.api.Auditorium import ru.sweetbread.unn.api.Auditorium
import ru.sweetbread.unn.api.Building import ru.sweetbread.unn.api.Building
@@ -55,6 +66,8 @@ import ru.sweetbread.unn.api.Lecturer
import ru.sweetbread.unn.api.LecturerRank import ru.sweetbread.unn.api.LecturerRank
import ru.sweetbread.unn.api.ScheduleUnit import ru.sweetbread.unn.api.ScheduleUnit
import ru.sweetbread.unn.api.getScheduleDay import ru.sweetbread.unn.api.getScheduleDay
import ru.sweetbread.unn.db.cacheDb
import ru.sweetbread.unn.db.toModel
import ru.sweetbread.unn.ui.theme.UNNTheme import ru.sweetbread.unn.ui.theme.UNNTheme
import splitties.resources.appStr import splitties.resources.appStr
import java.time.LocalDate import java.time.LocalDate
@@ -64,33 +77,84 @@ import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.Calendar import java.util.Calendar
class ScheduleRepository {
fun getFromDb(date: LocalDate): List<ScheduleUnit> =
cacheDb.scheduleDao()
.getSchedule(date.format(DateTimeFormatter.ISO_DATE))
.mapNotNull { it.toModel() }
suspend fun getFromApi(date: LocalDate): List<ScheduleUnit> = getScheduleDay(date)
}
sealed class ScheduleState {
object Loading : ScheduleState()
data class Success(val data: List<ScheduleUnit>) : ScheduleState()
data class Error(val message: String) : ScheduleState()
}
class ScheduleViewModel : ViewModel() {
private val repository = ScheduleRepository()
private val _date = MutableStateFlow(LocalDate.now())
val date: StateFlow<LocalDate> = _date.asStateFlow()
@OptIn(ExperimentalCoroutinesApi::class)
val scheduleState: StateFlow<ScheduleState> = _date
.flatMapLatest { date ->
flow {
emit(ScheduleState.Loading)
val cached = withContext(Dispatchers.IO) {
repository.getFromDb(date)
}
emit(ScheduleState.Success(cached))
try {
val fresh = repository.getFromApi(date)
if (fresh != cached) {
emit(ScheduleState.Success(fresh))
}
} catch (e: Exception) {
emit(ScheduleState.Error("Ошибка загрузки: ${e.message}"))
}
}.flowOn(Dispatchers.IO)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = ScheduleState.Loading
)
fun updateDate(newDate: LocalDate) {
_date.update { newDate }
}
}
@Composable @Composable
fun Schedule() { fun Schedule() {
var selectedDate by remember { mutableStateOf(LocalDate.now()) } val viewModel: ScheduleViewModel = viewModel()
Column { Column {
BoundedWeekPicker( BoundedWeekPicker(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 8.dp), .padding(vertical = 8.dp),
selectedDate = selectedDate, selectedDate = viewModel.date.collectAsState().value,
onDateSelected = { selectedDate = it } onDateSelected = { viewModel.updateDate(it) }
) )
ScheduleDay(date = selectedDate) ScheduleDay(viewModel = viewModel)
} }
} }
@Composable @Composable
fun ScheduleDay(modifier: Modifier = Modifier, date: LocalDate) { fun ScheduleDay(modifier: Modifier = Modifier, viewModel: ScheduleViewModel) {
val scope = rememberCoroutineScope()
var loadedDate by remember { mutableStateOf(LocalDate.MIN) }
val lessons = remember { mutableListOf<ScheduleUnit>() }
var expanded by remember { mutableIntStateOf(0) } var expanded by remember { mutableIntStateOf(0) }
val state by viewModel.scheduleState.collectAsState()
if (loadedDate == date) { when (state) {
Log.d("Loaded", "${date.format(DateTimeFormatter.ISO_DATE)} ${lessons.size}") is ScheduleState.Success -> {
LazyColumn (modifier) { val lessons = (state as ScheduleState.Success).data
LazyColumn(modifier) {
items(lessons) { items(lessons) {
ScheduleItem(unit = it, modifier = Modifier.clickable { ScheduleItem(unit = it, modifier = Modifier.clickable {
expanded = if (it.oid == expanded) 0 expanded = if (it.oid == expanded) 0
@@ -101,7 +165,8 @@ fun ScheduleDay(modifier: Modifier = Modifier, date: LocalDate) {
if (lessons.isEmpty()) if (lessons.isEmpty())
item { Text(appStr(R.string.noData)) } item { Text(appStr(R.string.noData)) }
} }
} else { }
is ScheduleState.Loading ->
LinearProgressIndicator( LinearProgressIndicator(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -109,13 +174,8 @@ fun ScheduleDay(modifier: Modifier = Modifier, date: LocalDate) {
color = MaterialTheme.colorScheme.surfaceVariant, color = MaterialTheme.colorScheme.surfaceVariant,
trackColor = MaterialTheme.colorScheme.secondary, trackColor = MaterialTheme.colorScheme.secondary,
) )
LaunchedEffect(date != loadedDate) { is ScheduleState.Error ->
scope.launch(Dispatchers.IO) { Text((state as ScheduleState.Error).message, color = MaterialTheme.colorScheme.error)
lessons.clear()
lessons.addAll(getScheduleDay(date = date))
loadedDate = date
}
}
} }
} }