Improve Android connection and preview UI testing
This commit is contained in:
+35
-15
@@ -7,6 +7,13 @@ import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.testTagsAsResourceId
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
@@ -21,6 +28,7 @@ private fun JsonObject.text(key: String, fallback: String = "—") = get(key)?.j
|
||||
|
||||
data class AppInfo(val version: String, val build: String, val date: String)
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun QimiaoApp(controller: Controller, onScan: () -> Unit, decode: (String) -> ImageBitmap?,
|
||||
appInfo: AppInfo = AppInfo("—", "—", "—")) {
|
||||
@@ -34,12 +42,16 @@ fun QimiaoApp(controller: Controller, onScan: () -> Unit, decode: (String) -> Im
|
||||
icon = { Text(listOf("◉", "▣", "▦", "⚙")[index]) }, label = { Text(name) })
|
||||
}
|
||||
} }) { padding ->
|
||||
Column(Modifier.fillMaxSize().padding(padding).padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Column(Modifier.fillMaxSize().semantics { testTagsAsResourceId = true }.padding(padding).padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
val identity = state.identity
|
||||
Text(if (identity == null) "当前连接设备:未连接${if (state.phase == "正在连接") " · 连接中" else ""}"
|
||||
else "当前连接设备:${identity.text("device_name")}(${identity.text("short_id")}) · ${wifiSummary(state)}",
|
||||
Modifier.weight(1f), style = MaterialTheme.typography.bodySmall, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
val summary = if (identity == null) "当前连接设备:未连接${if (state.phase == "正在连接") " · 连接中" else ""}"
|
||||
else "当前连接设备:${identity.text("device_name")}(${identity.text("short_id")}) · ${wifiSummary(state)}"
|
||||
key(summary) { Text(summary,
|
||||
Modifier.weight(1f).testTag("connection_summary").clipToBounds()
|
||||
.basicMarquee(iterations = Int.MAX_VALUE, repeatDelayMillis = 1_000,
|
||||
initialDelayMillis = 1_000, velocity = 30.dp),
|
||||
style = MaterialTheme.typography.bodySmall, maxLines = 1, softWrap = false, overflow = TextOverflow.Clip) }
|
||||
Box {
|
||||
TextButton({ menu = true }) { Text("⋮") }
|
||||
DropdownMenu(menu, { menu = false }) {
|
||||
@@ -50,7 +62,7 @@ fun QimiaoApp(controller: Controller, onScan: () -> Unit, decode: (String) -> Im
|
||||
}
|
||||
if (state.busy) LinearProgressIndicator(Modifier.fillMaxWidth())
|
||||
state.error?.let { error -> Card { Row(Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(error, Modifier.weight(1f)); TextButton(controller::dismissError) { Text("知道了") }
|
||||
Text(error, Modifier.weight(1f).testTag("controller_error")); TextButton(controller::dismissError) { Text("知道了") }
|
||||
} } }
|
||||
when (state.page) {
|
||||
0 -> ConnectionPage(state, controller, onScan)
|
||||
@@ -79,7 +91,7 @@ private fun ConnectionPage(state: ControllerState, c: Controller, scan: () -> Un
|
||||
var selected by remember { mutableStateOf<RememberedDevice?>(null) }
|
||||
var renaming by remember { mutableStateOf(false) }
|
||||
var name by remember { mutableStateOf("") }
|
||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
LazyColumn(Modifier.fillMaxWidth().testTag("connection_list"), verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
item { Text("已保存的设备", style = MaterialTheme.typography.titleMedium) }
|
||||
if (state.remembered.isEmpty()) item { Text("暂无已保存设备,扫描后选择即可连接") }
|
||||
items(state.remembered, key = { it.id }) { device ->
|
||||
@@ -96,12 +108,16 @@ private fun ConnectionPage(state: ControllerState, c: Controller, scan: () -> Un
|
||||
} }
|
||||
item { Text("扫描到的设备", style = MaterialTheme.typography.titleMedium) }
|
||||
items(state.devices, key = { it.handle }) { device -> Card(Modifier.fillMaxWidth()) {
|
||||
val connected = state.identity != null && state.connectedDevice?.handle == device.handle
|
||||
Row(Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(device.name)
|
||||
Text("信号 ${device.signal} dBm${if (c.rememberedFor(device) != null) " · 已记忆" else ""}", style = MaterialTheme.typography.bodySmall)
|
||||
Text(if (connected) state.identity!!.text("device_name") else device.name)
|
||||
Text(if (connected) "已连接 · 蓝牙信号:${state.signal?.let { "$it dBm" } ?: "暂不可用"}"
|
||||
else "扫描信号 ${device.signal} dBm${if (c.rememberedFor(device) != null) " · 已记忆" else ""}", style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
Button({ c.connect(device, c.rememberedFor(device)?.id) }, enabled = state.phase != "正在连接") { Text("连接") }
|
||||
Button({ if (connected) c.disconnect() else c.connect(device, c.rememberedFor(device)?.id) },
|
||||
Modifier.testTag(if (connected) "connected_scan_disconnect" else "scan_connect"),
|
||||
enabled = state.phase != "正在连接") { Text(if (connected) "断开" else "连接") }
|
||||
}
|
||||
} }
|
||||
if (state.identity != null) item { Card(Modifier.fillMaxWidth()) { Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
@@ -130,13 +146,17 @@ private fun Frame(encoded: String?, checkedAt: Long?, decode: (String) -> ImageB
|
||||
val bitmap = remember(encoded) { encoded?.let(decode) }
|
||||
var now by remember { mutableLongStateOf(Clock.System.now().toEpochMilliseconds()) }
|
||||
LaunchedEffect(checkedAt) { while (true) { now = Clock.System.now().toEpochMilliseconds(); delay(1_000) } }
|
||||
Card(Modifier.fillMaxWidth()) { Column(Modifier.padding(12.dp)) {
|
||||
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("设备画面", Modifier.weight(1f), style = MaterialTheme.typography.titleMedium)
|
||||
TextButton(refresh, enabled = enabled) { Text("手动刷新") }
|
||||
val density = LocalDensity.current
|
||||
var refreshWidth by remember { mutableStateOf(96.dp) }
|
||||
Card(Modifier.fillMaxWidth().testTag("frame_card")) { Column(Modifier.fillMaxWidth().padding(12.dp), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Box(Modifier.fillMaxWidth()) {
|
||||
Text("设备画面", Modifier.fillMaxWidth().padding(horizontal = refreshWidth).align(Alignment.Center).testTag("frame_title"),
|
||||
style = MaterialTheme.typography.titleMedium, textAlign = TextAlign.Center)
|
||||
TextButton(refresh, Modifier.align(Alignment.CenterEnd).testTag("frame_refresh")
|
||||
.onSizeChanged { refreshWidth = with(density) { it.width.toDp() } }, enabled = enabled) { Text("手动刷新") }
|
||||
}
|
||||
if (bitmap != null) Image(bitmap, "设备当前输出帧", Modifier.size(160.dp)) else Text("等待画面")
|
||||
if (checkedAt != null && now - checkedAt > 6_000) Text("画面可能已过期,请刷新", style = MaterialTheme.typography.bodySmall)
|
||||
if (bitmap != null) Image(bitmap, "设备当前输出帧", Modifier.size(160.dp).testTag("frame_image")) else Text("等待画面", textAlign = TextAlign.Center)
|
||||
if (checkedAt != null && now - checkedAt > 6_000) Text("画面可能已过期,请刷新", style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center)
|
||||
} }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user