package com.radiola.ui.alarms import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import com.composables.icons.lucide.AlarmClock import com.composables.icons.lucide.ArrowLeft import com.composables.icons.lucide.Lucide import com.composables.icons.lucide.Plus import com.composables.icons.lucide.Trash2 import com.radiola.data.local.entity.AlarmEntity import com.radiola.domain.model.Station import com.radiola.ui.theme.RadiolaTheme /** Экран управления будильниками. */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun AlarmsScreen( onNavigateBack: () -> Unit, modifier: Modifier = Modifier, viewModel: AlarmsViewModel = hiltViewModel() ) { val colors = RadiolaTheme.colors val alarms by viewModel.alarms.collectAsState() val stations by viewModel.stations.collectAsState() // Состояние диалога добавления/редактирования var editingAlarm by remember { mutableStateOf(null) } var showEditor by remember { mutableStateOf(false) } Column( modifier = modifier .fillMaxSize() .background(colors.bgBase) ) { // Шапка с кнопкой «Назад» Row( modifier = Modifier .fillMaxWidth() .statusBarsPadding() .padding(horizontal = 8.dp, vertical = 4.dp), verticalAlignment = Alignment.CenterVertically ) { IconButton(onClick = onNavigateBack) { Icon(Lucide.ArrowLeft, contentDescription = "Назад", tint = colors.textPrimary) } Text( text = "Будильник", style = MaterialTheme.typography.headlineMedium, color = colors.textPrimary, modifier = Modifier.weight(1f).padding(start = 4.dp) ) IconButton(onClick = { editingAlarm = null showEditor = true }) { Icon(Lucide.Plus, contentDescription = "Добавить будильник", tint = colors.accent) } } if (alarms.isEmpty()) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp)) { Icon(Lucide.AlarmClock, contentDescription = null, tint = colors.textMuted, modifier = Modifier.size(48.dp)) Text("Нет будильников", color = colors.textMuted, style = MaterialTheme.typography.bodyLarge) Text( "Нажмите «+» чтобы добавить", color = colors.textMuted, style = MaterialTheme.typography.labelMedium ) } } } else { LazyColumn( modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues(horizontal = 20.dp, vertical = 12.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { items(alarms, key = { it.id }) { alarm -> AlarmCard( alarm = alarm, onToggle = { viewModel.toggle(alarm) }, onEdit = { editingAlarm = alarm showEditor = true }, onDelete = { viewModel.delete(alarm) } ) } } } } // Диалог добавления / редактирования if (showEditor) { AlarmEditorSheet( initial = editingAlarm, stations = stations, onSave = { alarm -> viewModel.addOrUpdate(alarm) showEditor = false }, onDismiss = { showEditor = false } ) } } // ───────────────────────────────────────────────────────────────────────────── @Composable private fun AlarmCard( alarm: AlarmEntity, onToggle: () -> Unit, onEdit: () -> Unit, onDelete: () -> Unit ) { val colors = RadiolaTheme.colors Row( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(16.dp)) .background(colors.surface) .border(1.dp, colors.border, RoundedCornerShape(16.dp)) .clickable(onClick = onEdit) .padding(horizontal = 16.dp, vertical = 14.dp), verticalAlignment = Alignment.CenterVertically ) { // Время Text( text = "%02d:%02d".format(alarm.hour, alarm.minute), style = MaterialTheme.typography.displaySmall, fontWeight = FontWeight.Bold, color = if (alarm.enabled) colors.textPrimary else colors.textMuted ) Spacer(Modifier.width(16.dp)) // Станция + дни Column(modifier = Modifier.weight(1f)) { Text( text = alarm.stationName, style = MaterialTheme.typography.titleMedium, color = if (alarm.enabled) colors.textPrimary else colors.textMuted, maxLines = 1 ) Text( text = daysSummary(alarm.daysMask), style = MaterialTheme.typography.labelMedium, color = colors.textSecondary ) } // Удалить IconButton(onClick = onDelete) { Icon(Lucide.Trash2, contentDescription = "Удалить", tint = colors.textMuted, modifier = Modifier.size(18.dp)) } // Вкл/выкл Switch( checked = alarm.enabled, onCheckedChange = { onToggle() }, colors = SwitchDefaults.colors( checkedThumbColor = colors.bgBase, checkedTrackColor = colors.accent, uncheckedThumbColor = colors.textMuted, uncheckedTrackColor = colors.surface2 ) ) } } // ───────────────────────────────────────────────────────────────────────────── private val DAY_LABELS = listOf("Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс") private fun daysSummary(mask: Int): String { if (mask == 0) return "Один раз" val all = (1 shl 7) - 1 if (mask == all) return "Каждый день" val weekdays = 0b0011111 // Пн-Пт if (mask == weekdays) return "По будням" val weekend = 0b1100000 // Сб-Вс if (mask == weekend) return "По выходным" return DAY_LABELS.filterIndexed { i, _ -> mask and (1 shl i) != 0 }.joinToString(" ") } // ───────────────────────────────────────────────────────────────────────────── /** * Нижний лист редактирования / создания будильника. * Использует Material3 TimePicker + выбор станции + чипы дней. */ @OptIn(ExperimentalMaterial3Api::class) @Composable private fun AlarmEditorSheet( initial: AlarmEntity?, stations: List, onSave: (AlarmEntity) -> Unit, onDismiss: () -> Unit ) { val colors = RadiolaTheme.colors // Начальные значения val initHour = initial?.hour ?: 7 val initMinute = initial?.minute ?: 0 var selectedHour by remember { mutableStateOf(initHour) } var selectedMinute by remember { mutableStateOf(initMinute) } var daysMask by remember { mutableStateOf(initial?.daysMask ?: 0) } var selectedStation by remember { mutableStateOf(stations.firstOrNull { it.id == initial?.stationId } ?: stations.firstOrNull()) } var fadeInSec by remember { mutableStateOf(initial?.fadeInSec ?: 60) } var stationDropdownExpanded by remember { mutableStateOf(false) } // Обновим станцию если список подгрузился после открытия LaunchedEffect(stations) { if (selectedStation == null) selectedStation = stations.firstOrNull() } val timePickerState = rememberTimePickerState( initialHour = initHour, initialMinute = initMinute, is24Hour = true ) ModalBottomSheet( onDismissRequest = onDismiss, containerColor = colors.elevated ) { Column( modifier = Modifier .fillMaxWidth() .padding(horizontal = 24.dp) .padding(bottom = 32.dp), verticalArrangement = Arrangement.spacedBy(20.dp) ) { Text( text = if (initial == null) "Новый будильник" else "Изменить будильник", style = MaterialTheme.typography.titleLarge, color = colors.textPrimary, fontWeight = FontWeight.SemiBold ) // Выбор времени TimePicker( state = timePickerState, colors = TimePickerDefaults.colors( clockDialColor = colors.surface2, selectorColor = colors.accent, timeSelectorSelectedContainerColor = colors.accent, timeSelectorUnselectedContainerColor = colors.surface, timeSelectorSelectedContentColor = colors.bgBase, timeSelectorUnselectedContentColor = colors.textPrimary, periodSelectorBorderColor = colors.border, clockDialSelectedContentColor = colors.bgBase, clockDialUnselectedContentColor = colors.textPrimary ), modifier = Modifier.align(Alignment.CenterHorizontally) ) // Выбор станции Column { Text("Станция", style = MaterialTheme.typography.labelSmall, color = colors.textMuted, letterSpacing = 1.sp) Spacer(Modifier.height(6.dp)) Box( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) .background(colors.surface) .border(1.dp, colors.border, RoundedCornerShape(12.dp)) .clickable { stationDropdownExpanded = true } .padding(horizontal = 16.dp, vertical = 14.dp) ) { Text( text = selectedStation?.name ?: "Выберите станцию", color = if (selectedStation != null) colors.textPrimary else colors.textMuted, style = MaterialTheme.typography.bodyLarge ) } DropdownMenu( expanded = stationDropdownExpanded, onDismissRequest = { stationDropdownExpanded = false }, modifier = Modifier.background(colors.elevated).heightIn(max = 300.dp) ) { stations.forEach { station -> DropdownMenuItem( text = { Text(station.name, color = colors.textPrimary) }, onClick = { selectedStation = station stationDropdownExpanded = false } ) } } } // Дни недели Column { Text("Повтор", style = MaterialTheme.typography.labelSmall, color = colors.textMuted, letterSpacing = 1.sp) Spacer(Modifier.height(8.dp)) Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { DAY_LABELS.forEachIndexed { i, label -> val selected = daysMask and (1 shl i) != 0 Box( modifier = Modifier .weight(1f) .clip(RoundedCornerShape(8.dp)) .background(if (selected) colors.accent else colors.surface) .border(1.dp, if (selected) colors.accent else colors.border, RoundedCornerShape(8.dp)) .clickable { daysMask = daysMask xor (1 shl i) } .padding(vertical = 8.dp), contentAlignment = Alignment.Center ) { Text( text = label, style = MaterialTheme.typography.labelMedium, color = if (selected) colors.bgBase else colors.textSecondary, fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Normal ) } } } Spacer(Modifier.height(4.dp)) Text( text = if (daysMask == 0) "Один раз (ближайшее совпадение)" else daysSummary(daysMask), style = MaterialTheme.typography.labelSmall, color = colors.textSecondary ) } // Кнопки Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp) ) { OutlinedButton( onClick = onDismiss, modifier = Modifier.weight(1f), colors = ButtonDefaults.outlinedButtonColors(contentColor = colors.textSecondary), border = androidx.compose.foundation.BorderStroke(1.dp, colors.border) ) { Text("Отмена") } Button( onClick = { val station = selectedStation ?: return@Button onSave( AlarmEntity( id = initial?.id ?: 0, hour = timePickerState.hour, minute = timePickerState.minute, daysMask = daysMask, stationId = station.id, stationName = station.name, enabled = initial?.enabled ?: true, fadeInSec = fadeInSec ) ) }, modifier = Modifier.weight(1f), colors = ButtonDefaults.buttonColors( containerColor = colors.accent, contentColor = colors.bgBase ), shape = RoundedCornerShape(10.dp) ) { Text("Сохранить", fontWeight = FontWeight.SemiBold) } } } } }