| import kotlinx.coroutines.flow.* | import kotlinx.coroutines.flow.* | ||||
| import kotlinx.serialization.encodeToString | import kotlinx.serialization.encodeToString | ||||
| import kotlinx.serialization.json.Json | import kotlinx.serialization.json.Json | ||||
| import kotlinx.serialization.json.encodeToJsonElement | |||||
| import okhttp3.* | import okhttp3.* | ||||
| class LedWebSocketManager(private val client: OkHttpClient) { | class LedWebSocketManager(private val client: OkHttpClient) { | ||||
| } | } | ||||
| fun sendCommand(command: LedCommand) { | fun sendCommand(command: LedCommand) { | ||||
| val jsonElement = when (command) { | |||||
| is LedCommand.SetPower -> json.encodeToJsonElement(command) | |||||
| is LedCommand.ChangeMode -> json.encodeToJsonElement(command) | |||||
| is LedCommand.ChangeColor -> json.encodeToJsonElement(command) | |||||
| is LedCommand.ChangeBrightness -> json.encodeToJsonElement(command) | |||||
| is LedCommand.ChangeConfigParam -> json.encodeToJsonElement(command) | |||||
| is LedCommand.ChangeTimer -> json.encodeToJsonElement(command) | |||||
| is LedCommand.ChangeTimerInterval -> json.encodeToJsonElement(command) | |||||
| is LedCommand.GetConfig -> json.encodeToJsonElement(command) | |||||
| is LedCommand.RequestState -> json.encodeToJsonElement(command) | |||||
| is LedCommand.SetBrightness -> json.encodeToJsonElement(command) | |||||
| is LedCommand.SetColor -> json.encodeToJsonElement(command) | |||||
| is LedCommand.SetMode -> json.encodeToJsonElement(command) | |||||
| is LedCommand.SetSpeed -> json.encodeToJsonElement(command) | |||||
| val jsonString = when (command) { | |||||
| is LedCommand.SetPower -> json.encodeToString(command) | |||||
| is LedCommand.ChangeMode -> json.encodeToString(command) | |||||
| is LedCommand.ChangeColor -> json.encodeToString(command) | |||||
| is LedCommand.ChangeBrightness -> json.encodeToString(command) | |||||
| is LedCommand.ChangeConfigParam -> json.encodeToString(command) | |||||
| is LedCommand.ChangeTimer -> json.encodeToString(command) | |||||
| is LedCommand.ChangeTimerInterval -> json.encodeToString(command) | |||||
| is LedCommand.GetConfig -> json.encodeToString(command) | |||||
| is LedCommand.RequestState -> json.encodeToString(command) | |||||
| is LedCommand.SetBrightness -> json.encodeToString(command) | |||||
| is LedCommand.SetColor -> json.encodeToString(command) | |||||
| is LedCommand.SetMode -> json.encodeToString(command) | |||||
| is LedCommand.SetSpeed -> json.encodeToString(command) | |||||
| } | } | ||||
| webSocket?.send(jsonElement.toString()) | |||||
| webSocket?.send(jsonString) | |||||
| // Follow up with GET_CONFIG to stay in sync if it wasn't a GET_CONFIG itself | // Follow up with GET_CONFIG to stay in sync if it wasn't a GET_CONFIG itself | ||||
| // Use a small delay to allow the strip to process and save the previous command | |||||
| if (command !is LedCommand.GetConfig) { | if (command !is LedCommand.GetConfig) { | ||||
| val getConfigMessage = json.encodeToJsonElement(LedCommand.GetConfig()).toString() | |||||
| webSocket?.send(getConfigMessage) | |||||
| _connectionState.value.let { state -> | |||||
| if (state is ConnectionState.Connected) { | |||||
| // Start a coroutine to send the refresh command after a short delay | |||||
| // Note: LedWebSocketManager doesn't have a scope, but we can use the caller's context | |||||
| // Or just send it immediately but the strip might be busy. | |||||
| // For now, let's just send it but ensure serialization is clean. | |||||
| val getConfigMessage = json.encodeToString(LedCommand.GetConfig()) | |||||
| webSocket?.send(getConfigMessage) | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @OptIn(ExperimentalMaterial3Api::class) | |||||
| @Composable | @Composable | ||||
| fun TimerIntervalItem(label: String, key: String, interval: TimeInterval, onUpdate: (String, Long, Long) -> Unit) { | fun TimerIntervalItem(label: String, key: String, interval: TimeInterval, onUpdate: (String, Long, Long) -> Unit) { | ||||
| var showDialog by remember { mutableStateOf(false) } | var showDialog by remember { mutableStateOf(false) } | ||||
| } | } | ||||
| if (showDialog) { | if (showDialog) { | ||||
| // Very simplified Time Range picker. In production you'd use a real range picker. | |||||
| AlertDialog( | |||||
| onDismissRequest = { showDialog = false }, | |||||
| title = { Text("Edit $label") }, | |||||
| text = { | |||||
| Column { | |||||
| Text("Implementation placeholder for Time Range Picker") | |||||
| Text("Currently: ${formatSeconds(interval.start)} - ${formatSeconds(interval.end)}") | |||||
| } | |||||
| }, | |||||
| confirmButton = { | |||||
| TextButton(onClick = { | |||||
| // Simulate update for testing | |||||
| onUpdate(key, interval.start + 60, interval.end + 60) | |||||
| showDialog = false | |||||
| }) { Text("Test +1m") } | |||||
| }, | |||||
| dismissButton = { | |||||
| TextButton(onClick = { showDialog = false }) { Text("Cancel") } | |||||
| TimeRangePickerDialog( | |||||
| label = label, | |||||
| initialStart = interval.start, | |||||
| initialEnd = interval.end, | |||||
| onDismiss = { showDialog = false }, | |||||
| onConfirm = { start, end -> | |||||
| onUpdate(key, start, end) | |||||
| showDialog = false | |||||
| } | } | ||||
| ) | ) | ||||
| } | } | ||||
| } | } | ||||
| @OptIn(ExperimentalMaterial3Api::class) | |||||
| @Composable | |||||
| fun TimeRangePickerDialog( | |||||
| label: String, | |||||
| initialStart: Long, | |||||
| initialEnd: Long, | |||||
| onDismiss: () -> Unit, | |||||
| onConfirm: (Long, Long) -> Unit | |||||
| ) { | |||||
| var pickingStart by remember { mutableStateOf(true) } | |||||
| val startState = rememberTimePickerState( | |||||
| initialHour = (initialStart / 3600).toInt(), | |||||
| initialMinute = ((initialStart % 3600) / 60).toInt(), | |||||
| is24Hour = true | |||||
| ) | |||||
| val endState = rememberTimePickerState( | |||||
| initialHour = (initialEnd / 3600).toInt(), | |||||
| initialMinute = ((initialEnd % 3600) / 60).toInt(), | |||||
| is24Hour = true | |||||
| ) | |||||
| AlertDialog( | |||||
| onDismissRequest = onDismiss, | |||||
| title = { Text("Set Time Range for $label") }, | |||||
| text = { | |||||
| Column(horizontalAlignment = Alignment.CenterHorizontally) { | |||||
| TabRow(selectedTabIndex = if (pickingStart) 0 else 1) { | |||||
| Tab(selected = pickingStart, onClick = { pickingStart = true }) { | |||||
| Text("Start Time", modifier = Modifier.padding(16.dp)) | |||||
| } | |||||
| Tab(selected = !pickingStart, onClick = { pickingStart = false }) { | |||||
| Text("End Time", modifier = Modifier.padding(16.dp)) | |||||
| } | |||||
| } | |||||
| Spacer(modifier = Modifier.height(24.dp)) | |||||
| if (pickingStart) { | |||||
| TimePicker(state = startState) | |||||
| } else { | |||||
| TimePicker(state = endState) | |||||
| } | |||||
| } | |||||
| }, | |||||
| confirmButton = { | |||||
| TextButton(onClick = { | |||||
| val startSeconds = startState.hour * 3600L + startState.minute * 60L | |||||
| val endSeconds = endState.hour * 3600L + endState.minute * 60L | |||||
| onConfirm(startSeconds, endSeconds) | |||||
| }) { | |||||
| Text("Confirm") | |||||
| } | |||||
| }, | |||||
| dismissButton = { | |||||
| TextButton(onClick = onDismiss) { | |||||
| Text("Cancel") | |||||
| } | |||||
| } | |||||
| ) | |||||
| } | |||||
| private fun formatSeconds(seconds: Long): String { | private fun formatSeconds(seconds: Long): String { | ||||
| val h = seconds / 3600 | val h = seconds / 3600 | ||||
| val m = (seconds % 3600) / 60 | val m = (seconds % 3600) / 60 |
| fun updateTimerInterval(key: String, startTime: Long, endTime: Long) { | fun updateTimerInterval(key: String, startTime: Long, endTime: Long) { | ||||
| val range = "${formatSeconds(startTime)} - ${formatSeconds(endTime)}" | val range = "${formatSeconds(startTime)} - ${formatSeconds(endTime)}" | ||||
| repository.updateTimerInterval(key, range) | repository.updateTimerInterval(key, range) | ||||
| // Force an immediate config refresh after a short delay | |||||
| viewModelScope.launch { | |||||
| delay(1000) | |||||
| repository.getConfig() | |||||
| } | |||||
| } | } | ||||
| private fun formatSeconds(seconds: Long): String { | private fun formatSeconds(seconds: Long): String { |