| @@ -6,7 +6,6 @@ import ch.spherIC.ledlampcontrol.data.model.LedState | |||
| import kotlinx.coroutines.flow.* | |||
| import kotlinx.serialization.encodeToString | |||
| import kotlinx.serialization.json.Json | |||
| import kotlinx.serialization.json.encodeToJsonElement | |||
| import okhttp3.* | |||
| class LedWebSocketManager(private val client: OkHttpClient) { | |||
| @@ -104,27 +103,36 @@ class LedWebSocketManager(private val client: OkHttpClient) { | |||
| } | |||
| 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 | |||
| // Use a small delay to allow the strip to process and save the previous command | |||
| 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) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -549,6 +549,7 @@ fun TimerPane( | |||
| } | |||
| } | |||
| @OptIn(ExperimentalMaterial3Api::class) | |||
| @Composable | |||
| fun TimerIntervalItem(label: String, key: String, interval: TimeInterval, onUpdate: (String, Long, Long) -> Unit) { | |||
| var showDialog by remember { mutableStateOf(false) } | |||
| @@ -566,30 +567,82 @@ fun TimerIntervalItem(label: String, key: String, interval: TimeInterval, onUpda | |||
| } | |||
| 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 { | |||
| val h = seconds / 3600 | |||
| val m = (seconds % 3600) / 60 | |||
| @@ -174,6 +174,11 @@ class LedViewModel( | |||
| fun updateTimerInterval(key: String, startTime: Long, endTime: Long) { | |||
| val range = "${formatSeconds(startTime)} - ${formatSeconds(endTime)}" | |||
| 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 { | |||