feat: ViewModel in Schedule.kt
This commit is contained in:
@@ -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,58 +77,105 @@ 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
|
||||||
items(lessons) {
|
LazyColumn(modifier) {
|
||||||
ScheduleItem(unit = it, modifier = Modifier.clickable {
|
items(lessons) {
|
||||||
expanded = if (it.oid == expanded) 0
|
ScheduleItem(unit = it, modifier = Modifier.clickable {
|
||||||
else it.oid
|
expanded = if (it.oid == expanded) 0
|
||||||
}, expanded = expanded == it.oid)
|
else it.oid
|
||||||
}
|
}, expanded = expanded == it.oid)
|
||||||
|
}
|
||||||
|
|
||||||
if (lessons.isEmpty())
|
if (lessons.isEmpty())
|
||||||
item { Text(appStr(R.string.noData)) }
|
item { Text(appStr(R.string.noData)) }
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LinearProgressIndicator(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(8.dp),
|
|
||||||
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.Loading ->
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp),
|
||||||
|
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
trackColor = MaterialTheme.colorScheme.secondary,
|
||||||
|
)
|
||||||
|
is ScheduleState.Error ->
|
||||||
|
Text((state as ScheduleState.Error).message, color = MaterialTheme.colorScheme.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user