package agzam.vyatsu.ui.schedule import agzam.vyatsu.domain.model.Day import agzam.vyatsu.domain.model.SchedulePeriod import agzam.vyatsu.domain.model.TimeSlot import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.filled.Refresh import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChipDefaults import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp import java.time.DayOfWeek import java.time.LocalDate import java.time.format.DateTimeFormatter @OptIn(ExperimentalMaterial3Api::class) @Composable fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) { val state by viewModel.uiState.collectAsState() Column(Modifier.fillMaxSize()) { TopAppBar( title = { Text(state.groupName ?: "Расписание", style = MaterialTheme.typography.titleMedium) }, actions = { TextButton(onClick = { onSwitchGroup() }) { Text("Сменить группу") } IconButton(onClick = { viewModel.refresh() }) { Icon(Icons.Default.Refresh, contentDescription = "Обновить") } }, windowInsets = WindowInsets(0.dp) ) Column(Modifier.fillMaxSize().padding(horizontal = 16.dp, vertical = 0.dp)) { when { state.isSyncing && state.schedule == null -> { Row( Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { CircularProgressIndicator() } } state.schedule == null -> { Column( Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { Text( "Не удалось загрузить расписание", style = MaterialTheme.typography.bodyLarge ) TextButton(onClick = { viewModel.refresh() }) { Text("Повторить") } } } else -> { val schedule = state.schedule!! val weekDays = state.weekStart ?.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) { Text("Занятий нет", style = MaterialTheme.typography.bodyLarge) } else { val isTodayDay = day.date == state.todayDate val dayLabel = if (isTodayDay) "Сегодня " else "" Row( Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically ) { Text( "$dayLabel${dayName(day.date.dayOfWeek)} · ${day.date.format(FULL_DATE)}", style = MaterialTheme.typography.titleMedium, modifier = Modifier.weight(1f) ) WeekSelector( periods = schedule.periods, selectedWeekStart = state.weekStart, todayWeekStart = state.todayDate?.let { schedule.periodOf(it)?.weekStart }, onSelect = viewModel::selectWeek ) } HorizontalDivider(Modifier.padding(top = 8.dp, bottom = 0.dp)) DayChips( days = weekDays, selectedDate = state.dayDate, todayDate = state.todayDate, onSelect = viewModel::selectDay ) HorizontalDivider(Modifier.padding(top=0.dp, bottom = 8.dp)) if (day.intervals.all { it.lessons.isEmpty() }) { Text("Занятий нет", style = MaterialTheme.typography.bodyLarge) } else { LazyColumn( modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(8.dp) ) { itemsIndexed(day.intervals) { slotIndex, slot -> LessonSlot(slotIndex = slotIndex, slot = slot) } } } } } } } } } @OptIn(ExperimentalMaterial3Api::class) @Composable private fun WeekSelector( periods: List, selectedWeekStart: LocalDate?, todayWeekStart: LocalDate?, onSelect: (LocalDate) -> Unit ) { val selected = periods.firstOrNull { it.weekStart == selectedWeekStart } var expanded by remember { mutableStateOf(false) } Box { Row( modifier = Modifier .clip(MaterialTheme.shapes.small) .clickable(enabled = true, onClick = { expanded = true }) .padding(horizontal = 10.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { Text( selected?.let(::weekLabel) ?: "Период", style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.primary ) Icon( imageVector = Icons.Default.ArrowDropDown, contentDescription = null, tint = MaterialTheme.colorScheme.primary ) } DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false }, modifier = Modifier.widthIn(min = 220.dp) ) { periods.forEach { period -> val isTodayWeek = todayWeekStart == period.weekStart DropdownMenuItem( text = { Text( weekLabel(period), style = MaterialTheme.typography.bodyLarge ) }, trailingIcon = if (isTodayWeek) { { Text( "сегодня", style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.primary ) } } else { null }, onClick = { expanded = false onSelect(period.weekStart) } ) } } } } @Composable private fun DayChips( days: List, selectedDate: LocalDate?, todayDate: LocalDate?, onSelect: (LocalDate) -> Unit ) { LazyRow(horizontalArrangement = Arrangement.spacedBy(6.dp)) { items(days, key = { it.date.toEpochDay() }) { day -> val selected = selectedDate == day.date val isTodayChip = todayDate == day.date FilterChip( selected = selected, onClick = { onSelect(day.date) }, label = { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text( dayName(day.date.dayOfWeek), style = MaterialTheme.typography.labelMedium ) Text( day.date.format(CHIP_DATE), style = MaterialTheme.typography.labelSmall, color = if (selected) { MaterialTheme.colorScheme.onSecondaryContainer } else { MaterialTheme.colorScheme.onSurfaceVariant } ) } }, modifier = Modifier.padding(vertical = 2.dp), border = if (isTodayChip && !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) ) } } } private fun weekLabel(period: SchedulePeriod): String { val first = period.weekStart return "с ${first.format(CHIP_DATE)}" } @Composable private fun LessonSlot(slotIndex: Int, slot: TimeSlot) { Column(Modifier.fillMaxWidth()) { Text( "${slotIndex + 1} пара · ${slot.start.format(TIME)}–${slot.end.format(TIME)}", style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.primary ) if (slot.lessons.isEmpty()) { Text("—", style = MaterialTheme.typography.bodyMedium) } else { slot.lessons.forEach { lesson -> Column(Modifier.fillMaxWidth().padding(vertical = 2.dp)) { Text( lesson.type.label, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) Text( lesson.subject?.takeIf { it.isNotBlank() } ?: lesson.raw, style = MaterialTheme.typography.bodyLarge ) Text( listOfNotNull( lesson.subgroup, lesson.teacher, lesson.room ) .filter { it.isNotBlank() } .joinToString(" · "), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) } } } } } private fun dayName(dayOfWeek: DayOfWeek): String = when (dayOfWeek) { DayOfWeek.MONDAY -> "Пн" DayOfWeek.TUESDAY -> "Вт" DayOfWeek.WEDNESDAY -> "Ср" DayOfWeek.THURSDAY -> "Чт" DayOfWeek.FRIDAY -> "Пт" DayOfWeek.SATURDAY -> "Сб" DayOfWeek.SUNDAY -> "Вс" } private val FULL_DATE: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy") private val CHIP_DATE: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM") private val TIME: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")