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
import android.util.Log
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.Canvas
@@ -24,12 +23,11 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
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.zIndex
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
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.api.Auditorium
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.ScheduleUnit
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 splitties.resources.appStr
import java.time.LocalDate
@@ -64,32 +77,83 @@ import java.time.ZoneId
import java.time.format.DateTimeFormatter
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
fun Schedule() {
var selectedDate by remember { mutableStateOf(LocalDate.now()) }
val viewModel: ScheduleViewModel = viewModel()
Column {
BoundedWeekPicker(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
selectedDate = selectedDate,
onDateSelected = { selectedDate = it }
selectedDate = viewModel.date.collectAsState().value,
onDateSelected = { viewModel.updateDate(it) }
)
ScheduleDay(date = selectedDate)
ScheduleDay(viewModel = viewModel)
}
}
@Composable
fun ScheduleDay(modifier: Modifier = Modifier, date: LocalDate) {
val scope = rememberCoroutineScope()
var loadedDate by remember { mutableStateOf(LocalDate.MIN) }
val lessons = remember { mutableListOf<ScheduleUnit>() }
fun ScheduleDay(modifier: Modifier = Modifier, viewModel: ScheduleViewModel) {
var expanded by remember { mutableIntStateOf(0) }
val state by viewModel.scheduleState.collectAsState()
if (loadedDate == date) {
Log.d("Loaded", "${date.format(DateTimeFormatter.ISO_DATE)} ${lessons.size}")
when (state) {
is ScheduleState.Success -> {
val lessons = (state as ScheduleState.Success).data
LazyColumn(modifier) {
items(lessons) {
ScheduleItem(unit = it, modifier = Modifier.clickable {
@@ -101,7 +165,8 @@ fun ScheduleDay(modifier: Modifier = Modifier, date: LocalDate) {
if (lessons.isEmpty())
item { Text(appStr(R.string.noData)) }
}
} else {
}
is ScheduleState.Loading ->
LinearProgressIndicator(
modifier = Modifier
.fillMaxWidth()
@@ -109,13 +174,8 @@ fun ScheduleDay(modifier: Modifier = Modifier, date: LocalDate) {
color = MaterialTheme.colorScheme.surfaceVariant,
trackColor = MaterialTheme.colorScheme.secondary,
)
LaunchedEffect(date != loadedDate) {
scope.launch(Dispatchers.IO) {
lessons.clear()
lessons.addAll(getScheduleDay(date = date))
loadedDate = date
}
}
is ScheduleState.Error ->
Text((state as ScheduleState.Error).message, color = MaterialTheme.colorScheme.error)
}
}