| @@ -15,17 +15,21 @@ import ch.spherIC.ledlampcontrol.ui.viewmodel.LedViewModel | |||
| import okhttp3.OkHttpClient | |||
| import androidx.lifecycle.ViewModel | |||
| import androidx.lifecycle.ViewModelProvider | |||
| import androidx.compose.foundation.isSystemInDarkTheme | |||
| import androidx.compose.runtime.collectAsState | |||
| import androidx.compose.runtime.getValue | |||
| import androidx.activity.result.contract.ActivityResultContracts | |||
| import android.Manifest | |||
| import java.util.concurrent.TimeUnit | |||
| class MainActivity : ComponentActivity() { | |||
| private val client = OkHttpClient() | |||
| private val client = OkHttpClient.Builder() | |||
| .pingInterval(10, TimeUnit.SECONDS) | |||
| .connectTimeout(10, TimeUnit.SECONDS) | |||
| .readTimeout(0, TimeUnit.SECONDS) | |||
| .build() | |||
| private val webSocketManager = LedWebSocketManager(client) | |||
| private val repository = LedRepository(webSocketManager) | |||
| private lateinit var settingsRepository: SettingsRepository | |||
| @@ -51,9 +55,9 @@ class MainActivity : ComponentActivity() { | |||
| settingsRepository = SettingsRepository(applicationContext) | |||
| discoveryManager = LedDiscoveryManager(applicationContext) | |||
| // In a real app, use Hilt or Koin. Manual DI for simplicity. | |||
| val viewModelFactory = object : ViewModelProvider.Factory { | |||
| override fun <T : ViewModel> create(modelClass: Class<T>): T { | |||
| @Suppress("UNCHECKED_CAST") | |||
| return LedViewModel(repository, settingsRepository, discoveryManager) as T | |||
| } | |||
| } | |||
| @@ -27,15 +27,13 @@ class LedWebSocketManager(private val client: OkHttpClient) { | |||
| encodeDefaults = true | |||
| } | |||
| fun connect(url: String) { | |||
| if (currentUrl == url && (_connectionState.value is ConnectionState.Connecting || _connectionState.value is ConnectionState.Connected)) { | |||
| fun connect(url: String, force: Boolean = false) { | |||
| if (!force && currentUrl == url && (_connectionState.value is ConnectionState.Connecting || _connectionState.value is ConnectionState.Connected)) { | |||
| return | |||
| } | |||
| // If switching URLs, disconnect existing first | |||
| if (currentUrl != url) { | |||
| disconnect() | |||
| } | |||
| // Ensure a clean state before connecting | |||
| disconnect() | |||
| currentUrl = url | |||
| val request = Request.Builder().url(url).build() | |||
| @@ -14,8 +14,8 @@ class LedRepository(private val webSocketManager: LedWebSocketManager) { | |||
| val ledState: StateFlow<LedState> = webSocketManager.ledState | |||
| val ledConfig: StateFlow<LedConfig> = webSocketManager.ledConfig | |||
| fun connect(url: String) { | |||
| webSocketManager.connect(url) | |||
| fun connect(url: String, force: Boolean = false) { | |||
| webSocketManager.connect(url, force) | |||
| } | |||
| fun disconnect() { | |||
| @@ -82,9 +82,9 @@ fun DashboardScreen(viewModel: LedViewModel) { | |||
| Spacer(modifier = Modifier.weight(1f)) | |||
| NavigationDrawerItem( | |||
| label = { Text("Manage Devices") }, | |||
| selected = selectedTab == 2, | |||
| selected = selectedTab == 3, | |||
| onClick = { | |||
| selectedTab = 2 | |||
| selectedTab = 3 | |||
| scope.launch { drawerState.close() } | |||
| }, | |||
| icon = { Icon(Icons.Default.Settings, contentDescription = null) }, | |||
| @@ -129,8 +129,8 @@ fun DashboardScreen(viewModel: LedViewModel) { | |||
| NavigationBarItem( | |||
| selected = selectedTab == 1, | |||
| onClick = { selectedTab = 1 }, | |||
| icon = { Icon(Icons.Default.GridView, contentDescription = "Style") }, | |||
| label = { Text("Style") } | |||
| icon = { Icon(Icons.Default.GridView, contentDescription = "Modes") }, | |||
| label = { Text("Modes") } | |||
| ) | |||
| NavigationBarItem( | |||
| selected = selectedTab == 2, | |||
| @@ -147,8 +147,8 @@ fun DashboardScreen(viewModel: LedViewModel) { | |||
| NavigationBarItem( | |||
| selected = selectedTab == 4, | |||
| onClick = { selectedTab = 4 }, | |||
| icon = { Icon(Icons.Default.Palette, contentDescription = "Appearance") }, | |||
| label = { Text("Appearance") } | |||
| icon = { Icon(Icons.Default.Palette, contentDescription = "Style") }, | |||
| label = { Text("Style") } | |||
| ) | |||
| } | |||
| } | |||
| @@ -364,7 +364,7 @@ fun StylePane( | |||
| .verticalScroll(rememberScrollState()), | |||
| horizontalAlignment = Alignment.CenterHorizontally | |||
| ) { | |||
| Text("Animation Styles", style = MaterialTheme.typography.headlineMedium) | |||
| Text("Animated Modes", style = MaterialTheme.typography.headlineMedium) | |||
| Spacer(modifier = Modifier.height(24.dp)) | |||
| LedModeType.entries.forEach { mode -> | |||
| @@ -820,7 +820,7 @@ fun AppearancePane( | |||
| .verticalScroll(rememberScrollState()), | |||
| horizontalAlignment = Alignment.CenterHorizontally | |||
| ) { | |||
| Text("Appearance", style = MaterialTheme.typography.headlineMedium) | |||
| Text("Style", style = MaterialTheme.typography.headlineMedium) | |||
| Spacer(modifier = Modifier.height(24.dp)) | |||
| Text("Theme Mode", style = MaterialTheme.typography.titleMedium) | |||
| @@ -92,9 +92,10 @@ class LedViewModel( | |||
| fun selectDevice(deviceId: String?) { | |||
| viewModelScope.launch { | |||
| if (deviceId != null && deviceId == selectedDeviceId.value) { | |||
| if (deviceId != null) { | |||
| devices.value.find { it.id == deviceId }?.let { device -> | |||
| repository.connect("ws://${device.ip}:${device.port}") | |||
| // Always try to connect if it's a manual selection, even if currently "connected" | |||
| repository.connect("ws://${device.ip}:${device.port}", force = true) | |||
| } | |||
| } | |||
| settingsRepository.selectDevice(deviceId) | |||