refactor: merge Test tab into Settings, 3 tabs total

- Settings tab now has credential fields + network test (Test Pi, Test Server)
- Removed standalone Test tab
- Bottom nav: Dashcam, Downloads, Settings
This commit is contained in:
Mathieu Moran
2026-08-12 04:04:21 -04:00
parent 19942dc114
commit 67dc9b897e
2 changed files with 57 additions and 14 deletions
@@ -83,19 +83,15 @@ fun YarisMainScreen(client: OkHttpClient) {
NavigationBarItem( NavigationBarItem(
selected = selectedTab == 1, selected = selectedTab == 1,
onClick = { selectedTab = 1 }, onClick = { selectedTab = 1 },
icon = { Text("�") }, icon = { Text(".") },
label = { Text("Downloads") } label = { Text("Downloads") }
) )
NavigationBarItem( NavigationBarItem(
selected = selectedTab == 2, selected = selectedTab == 2,
onClick = { selectedTab = 2 }, icon = { Text("⚙️") }, onClick = { selectedTab = 2 },
icon = { Text(".") },
label = { Text("Settings") } label = { Text("Settings") }
) )
NavigationBarItem(
selected = selectedTab == 3,
onClick = { selectedTab = 3 }, icon = { Text("�🔌") },
label = { Text("Test") }
)
} }
} }
) { padding -> ) { padding ->
@@ -124,11 +120,7 @@ fun YarisMainScreen(client: OkHttpClient) {
onCancelUpload = { dashcamVm.cancelUpload(it) }, onCancelUpload = { dashcamVm.cancelUpload(it) },
onRefresh = { dashcamVm.scanDownloads() }, onRefresh = { dashcamVm.scanDownloads() },
) )
2 -> SettingsScreen() 2 -> SettingsScreen(client = client)
3 -> DualNetworkTestScreen(
piClient = client,
serverClient = client,
)
} }
} }
} }
@@ -1,6 +1,7 @@
package com.yarisobd.app package com.yarisobd.app
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@@ -9,6 +10,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -18,21 +20,32 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.yarisobd.app.data.CredentialStore import com.yarisobd.app.data.CredentialStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
@Composable @Composable
fun SettingsScreen() { fun SettingsScreen(client: OkHttpClient) {
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope()
var serverUrl by remember { mutableStateOf(CredentialStore.getServerUrl(context)) } var serverUrl by remember { mutableStateOf(CredentialStore.getServerUrl(context)) }
var deviceName by remember { mutableStateOf(CredentialStore.getDeviceName(context)) } var deviceName by remember { mutableStateOf(CredentialStore.getDeviceName(context)) }
var apiKey by remember { mutableStateOf("") } var apiKey by remember { mutableStateOf("") }
var saved by remember { mutableStateOf(false) } var saved by remember { mutableStateOf(false) }
val configured = CredentialStore.isConfigured(context) val configured = CredentialStore.isConfigured(context)
// Network test state
var piResult by remember { mutableStateOf<String?>(null) }
var serverResult by remember { mutableStateOf<String?>(null) }
Column( Column(
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(16.dp) modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(16.dp)
) { ) {
@@ -82,8 +95,46 @@ fun SettingsScreen() {
saved = true saved = true
}, },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { Text(if (saved) "✓ Saved — restart app to apply" else "Save") } ) { Text(if (saved) "✓ Saved" else "Save") }
}
}
Spacer(Modifier.height(16.dp))
// Network test
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainerHigh)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text("Network Test", style = MaterialTheme.typography.titleMedium)
Spacer(Modifier.height(8.dp))
Row { Button(onClick = {
piResult = null
scope.launch { piResult = testPi(client) }
}, modifier = Modifier.weight(1f)) { Text("Test Pi") } }
piResult?.let { Text(it, style = MaterialTheme.typography.bodySmall, color = if (it.startsWith("✓")) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error, modifier = Modifier.padding(top = 4.dp)) }
Spacer(Modifier.height(8.dp))
Row { Button(onClick = {
serverResult = null
scope.launch { serverResult = testServer(client) }
}, modifier = Modifier.weight(1f), colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondary)) { Text("Test Server") } }
serverResult?.let { Text(it, style = MaterialTheme.typography.bodySmall, color = if (it.startsWith("✓")) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error, modifier = Modifier.padding(top = 4.dp)) }
} }
} }
} }
} }
private suspend fun testPi(client: OkHttpClient): String = withContext(Dispatchers.IO) {
try {
val res = client.newCall(Request.Builder().url("http://10.42.0.1/api/gps/status").get().build()).execute()
if (res.isSuccessful) "✓ Pi OK (${res.code})" else "✗ Pi HTTP ${res.code}"
} catch (e: Exception) { "✗ ${e.message}" }
}
private suspend fun testServer(client: OkHttpClient): String = withContext(Dispatchers.IO) {
try {
val res = client.newCall(Request.Builder().url("https://yaris.ceaf.dev/health").get().build()).execute()
if (res.isSuccessful) "✓ Server OK (${res.code})" else "✗ Server HTTP ${res.code}"
} catch (e: Exception) { "✗ ${e.message}" }
}