package agzam.vyatsu.ui.info import agzam.vyatsu.data.static.CALLS import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues 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.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect 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.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.LocalLifecycleOwner import kotlinx.coroutines.delay import java.time.LocalTime import java.time.format.DateTimeFormatter import java.time.temporal.ChronoUnit @OptIn(ExperimentalMaterial3Api::class) @Composable fun CallsScreen() { var now by remember { mutableStateOf(LocalTime.now()) } val lifecycleOwner = LocalLifecycleOwner.current DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_RESUME) { now = LocalTime.now() } } lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } LaunchedEffect(Unit) { while (true) { now = LocalTime.now() delay(15_000L) } } val intervals = remember { val formatter = DateTimeFormatter.ofPattern("H:mm") CALLS.mapIndexed { index, call -> CallInterval( number = index + 1, start = LocalTime.parse(call.start, formatter), end = LocalTime.parse(call.end, formatter) ) } } val current = intervals.firstOrNull { !now.isBefore(it.start) && now.isBefore(it.end) } val next = intervals.firstOrNull { now.isBefore(it.start) } Scaffold( contentWindowInsets = WindowInsets(0.dp), topBar = { TopAppBar( windowInsets = WindowInsets(0.dp), title = { Text( text = "Звонки", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.SemiBold ) } ) } ) { innerPadding -> LazyColumn( modifier = Modifier .fillMaxSize() .padding(innerPadding), contentPadding = PaddingValues( horizontal = 16.dp, vertical = 12.dp ), verticalArrangement = Arrangement.spacedBy(12.dp) ) { item { CallsStatusCard( now = now, current = current, next = next, first = intervals.firstOrNull() ) } items( items = intervals, key = { it.number } ) { interval -> val isCurrent = current?.number == interval.number CallCard( interval = interval, now = now, isCurrent = isCurrent ) } } } } private data class CallInterval( val number: Int, val start: LocalTime, val end: LocalTime ) @Composable private fun CallCard( interval: CallInterval, now: LocalTime, isCurrent: Boolean ) { val isFinished = !now.isBefore(interval.end) val status = when { isCurrent -> "Идёт сейчас" isFinished -> "Завершена" else -> "Впереди" } val totalSeconds = ChronoUnit.SECONDS.between( interval.start, interval.end ).coerceAtLeast(1) val elapsedSeconds = ChronoUnit.SECONDS.between( interval.start, now ) val progress = ( elapsedSeconds.toFloat() / totalSeconds ).coerceIn(0f, 1f) Card( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(20.dp), colors = CardDefaults.cardColors( containerColor = if (isCurrent) { MaterialTheme.colorScheme.primaryContainer } else { MaterialTheme.colorScheme.surfaceContainerLow } ) ) { Column( modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp) ) { Surface( modifier = Modifier.size(56.dp), shape = RoundedCornerShape(16.dp), color = if (isCurrent) { MaterialTheme.colorScheme.primary } else { MaterialTheme.colorScheme.secondaryContainer }, contentColor = if (isCurrent) { MaterialTheme.colorScheme.onPrimary } else { MaterialTheme.colorScheme.onSecondaryContainer } ) { Box( contentAlignment = Alignment.Center ) { Text( text = interval.number.toString(), style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold ) } } Column( modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp) ) { Text( text = if (isCurrent) { "Текущая пара" } else { "Пара" }, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold ) Text( text = status, style = MaterialTheme.typography.bodySmall ) } Column( horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.spacedBy(4.dp) ) { Text( text = interval.start.toString(), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold ) Text( text = interval.end.toString(), style = MaterialTheme.typography.bodyMedium, ) } } } } } @Composable private fun CallsStatusCard( now: LocalTime, current: CallInterval?, next: CallInterval?, first: CallInterval? ) { val title: String val subtitle: String val progress: Float when { current != null -> { title = "Сейчас идёт ${current.number} пара" subtitle = "До конца ${minutesUntil(now, current.end)} мин" val total = ChronoUnit.SECONDS.between( current.start, current.end ).coerceAtLeast(1) val elapsed = ChronoUnit.SECONDS.between( current.start, now ) progress = ( elapsed.toFloat() / total ).coerceIn(0f, 1f) } next != null -> { title = if (first?.number == next.number) { "Занятия ещё не начались" } else { "Сейчас перемена" } subtitle = "До ${next.number} пары ${minutesUntil(now, next.start)} мин" progress = 0f } else -> { title = "Занятия закончились" subtitle = "На сегодня пар больше нет" progress = 1f } } Card( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(24.dp), colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.secondaryContainer ) ) { Column( modifier = Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { Text( text = title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold ) Text( text = subtitle, style = MaterialTheme.typography.headlineSmall ) if (current != null) { LinearProgressIndicator( progress = { progress }, modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.primary, trackColor = MaterialTheme.colorScheme.surfaceContainerHighest ) } } } } private fun minutesUntil( now: LocalTime, target: LocalTime ): Long { val seconds = ChronoUnit.SECONDS.between( now, target ).coerceAtLeast(0) return (seconds + 59) / 60 } /* // Агзам @Composable fun CallsScreen() { LazyColumn( Modifier .fillMaxWidth() .windowInsetsPadding(WindowInsets.statusBars) ) { itemsIndexed(CALLS) { index, call -> Row(Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 12.dp)) { Text( "${index + 1} пара", style = MaterialTheme.typography.bodyLarge, modifier = Modifier.weight(1f) ) Text( "${call.start} – ${call.end}", style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant ) } HorizontalDivider(Modifier.padding(horizontal = 16.dp)) } } } */