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(
selected = selectedTab == 1,
onClick = { selectedTab = 1 },
icon = { Text("�") },
icon = { Text(".") },
label = { Text("Downloads") }
)
NavigationBarItem(
selected = selectedTab == 2,
onClick = { selectedTab = 2 }, icon = { Text("⚙️") },
onClick = { selectedTab = 2 },
icon = { Text(".") },
label = { Text("Settings") }
)
NavigationBarItem(
selected = selectedTab == 3,
onClick = { selectedTab = 3 }, icon = { Text("�🔌") },
label = { Text("Test") }
)
}
}
) { padding ->
@@ -124,11 +120,7 @@ fun YarisMainScreen(client: OkHttpClient) {
onCancelUpload = { dashcamVm.cancelUpload(it) },
onRefresh = { dashcamVm.scanDownloads() },
)
2 -> SettingsScreen()
3 -> DualNetworkTestScreen(
piClient = client,
serverClient = client,
)
2 -> SettingsScreen(client = client)
}
}
}
@@ -1,6 +1,7 @@
package com.yarisobd.app
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
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.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
@@ -18,21 +20,32 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
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
fun SettingsScreen() {
fun SettingsScreen(client: OkHttpClient) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
var serverUrl by remember { mutableStateOf(CredentialStore.getServerUrl(context)) }
var deviceName by remember { mutableStateOf(CredentialStore.getDeviceName(context)) }
var apiKey by remember { mutableStateOf("") }
var saved by remember { mutableStateOf(false) }
val configured = CredentialStore.isConfigured(context)
// Network test state
var piResult by remember { mutableStateOf<String?>(null) }
var serverResult by remember { mutableStateOf<String?>(null) }
Column(
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(16.dp)
) {
@@ -82,8 +95,46 @@ fun SettingsScreen() {
saved = true
},
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}" }
}