fix: buffered quick connect card input, quick device ip/port modifying

This commit is contained in:
Miuzarte
2026-04-14 00:24:54 +08:00
parent 3a6df23a6c
commit 89c111e23b
7 changed files with 103 additions and 22 deletions

View File

@@ -40,8 +40,8 @@ android {
applicationId = "io.github.miuzarte.scrcpyforandroid"
minSdk = 26
targetSdk = 36
versionCode = 10
versionName = "0.1.4"
versionCode = 11
versionName = "0.1.5"
externalNativeBuild {
cmake {

View File

@@ -19,8 +19,24 @@ class DeviceShortcuts(val devices: List<DeviceShortcut>) : List<DeviceShortcut>
separator: String = DEFAULT_SEPARATOR,
): DeviceShortcuts {
if (s.isBlank()) return DeviceShortcuts(emptyList())
var nextLegacyId = 1
val list = s.splitToSequence(separator)
.mapNotNull { DeviceShortcut.unmarshalFrom(it) }
.mapNotNull { raw ->
val firstValue = raw.split(DeviceShortcut.DEFAULT_SEPARATOR, limit = 2)
.firstOrNull()
?.trim()
.orEmpty()
if (firstValue.toIntOrNull() != null) {
DeviceShortcut.unmarshalFrom(raw)
} else {
DeviceShortcut.unmarshalFrom(
s = raw,
fallbackId = nextLegacyId.toString(),
).also {
if (it != null) nextLegacyId++
}
}
}
.toList()
return DeviceShortcuts(list)
}
@@ -56,6 +72,7 @@ class DeviceShortcuts(val devices: List<DeviceShortcut>) : List<DeviceShortcut>
val updateById = id != null
val updated = DeviceShortcut(
id = old.id,
name = when {
name == null -> old.name
updateNameOnlyWhenEmpty && old.name.isNotBlank() -> old.name
@@ -88,17 +105,35 @@ class DeviceShortcuts(val devices: List<DeviceShortcut>) : List<DeviceShortcut>
shortcut: DeviceShortcut,
index: Int? = null,
): DeviceShortcuts {
val existingIdx = getIndex(shortcut.id)
val normalizedShortcut = normalizeId(shortcut)
val existingById = getIndex(normalizedShortcut.id)
val existingIdx = if (existingById >= 0) {
existingById
} else {
getIndex(normalizedShortcut.host, normalizedShortcut.port)
}
val newList = devices.toMutableList()
if (existingIdx >= 0) {
newList[existingIdx] = shortcut
// Keep existing id stable when matching by host:port.
val existingId = devices[existingIdx].id
newList[existingIdx] = normalizedShortcut.copy(id = existingId)
} else {
if (index != null) newList.add(index, shortcut)
else newList.add(shortcut)
if (index != null) newList.add(index, normalizedShortcut)
else newList.add(normalizedShortcut)
}
return DeviceShortcuts(newList)
}
private fun normalizeId(shortcut: DeviceShortcut): DeviceShortcut {
if (shortcut.id.toIntOrNull() != null) return shortcut
return shortcut.copy(id = nextId())
}
private fun nextId(): String {
val maxId = devices.maxOfOrNull { it.id.toIntOrNull() ?: 0 } ?: 0
return (maxId + 1).toString()
}
fun move(fromIndex: Int, toIndex: Int): DeviceShortcuts {
if (fromIndex !in devices.indices || toIndex !in devices.indices) return this
if (fromIndex == toIndex) return this
@@ -121,8 +156,8 @@ class DeviceShortcuts(val devices: List<DeviceShortcut>) : List<DeviceShortcut>
DeviceShortcuts(devices)
}
// TODO: 增加 id 字段,解决编辑端口的问题
data class DeviceShortcut(
val id: String = "",
val name: String = "",
val host: String,
val port: Int = Defaults.ADB_PORT,
@@ -130,11 +165,10 @@ data class DeviceShortcut(
val openFullscreenOnStart: Boolean = false,
val scrcpyProfileId: String = ScrcpyOptions.GLOBAL_PROFILE_ID,
) {
val id: String get() = "$host:$port"
fun marshalToString(
separator: String = DEFAULT_SEPARATOR,
): String = listOf(
id.trim(),
name.trim(),
host.trim(),
port.toString(),
@@ -150,10 +184,45 @@ data class DeviceShortcut(
fun unmarshalFrom(
s: String,
separator: String = DEFAULT_SEPARATOR,
fallbackId: String? = null,
): DeviceShortcut? {
val parts = s.split(separator)
return when (parts.size) {
val idInData = parts.firstOrNull()
?.trim()
?.takeIf { it.toIntOrNull() != null }
return if (idInData != null) when (parts.size) {
4, 5, 6, 7 -> {
val name = parts[1].trim()
val host = parts[2].trim()
val port = parts[3].trim().toIntOrNull() ?: Defaults.ADB_PORT
val startScrcpyOnConnect = parts.getOrNull(4)
?.trim() == "1"
val openFullscreenOnStart = startScrcpyOnConnect
&& parts.getOrNull(5)
?.trim() == "1"
val scrcpyProfileId = parts.getOrNull(6)
?.trim()
.takeUnless { it.isNullOrBlank() }
?: ScrcpyOptions.GLOBAL_PROFILE_ID
if (host.isNotBlank()) DeviceShortcut(
id = idInData,
name = name,
host = host,
port = port,
startScrcpyOnConnect = startScrcpyOnConnect,
openFullscreenOnStart = openFullscreenOnStart,
scrcpyProfileId = scrcpyProfileId,
)
else null
}
else -> null
}
else when (parts.size) {
3, 4, 5, 6 -> {
val id = fallbackId ?: return null
val name = parts[0].trim()
val host = parts[1].trim()
val port = parts[2].trim().toIntOrNull() ?: Defaults.ADB_PORT
@@ -169,6 +238,7 @@ data class DeviceShortcut(
?: ScrcpyOptions.GLOBAL_PROFILE_ID
if (host.isNotBlank()) DeviceShortcut(
id = id,
name = name,
host = host,
port = port,

View File

@@ -185,7 +185,7 @@ fun DeviceTabPage(
scrcpy: Scrcpy,
) {
val activity = LocalActivity.current
val fragmentActivity = remember(activity) {activity as? FragmentActivity}
val fragmentActivity = remember(activity) { activity as? FragmentActivity }
val context = LocalContext.current
val scope = rememberCoroutineScope()
@@ -320,10 +320,14 @@ fun DeviceTabPage(
VirtualButtonActions.parseStoredLayout(asBundle.virtualButtonsLayout)
)
}
var quickConnectInputTemp by rememberSaveable(qdBundle.quickConnectInput) {
mutableStateOf(qdBundle.quickConnectInput)
}
var savedShortcuts by remember {
mutableStateOf(DeviceShortcuts.unmarshalFrom(qdBundle.quickDevicesList))
}
LaunchedEffect(qdBundle.quickDevicesList) {
savedShortcuts = DeviceShortcuts.unmarshalFrom(qdBundle.quickDevicesList)
}
@@ -1008,17 +1012,24 @@ fun DeviceTabPage(
// "快速连接"
item {
QuickConnectCard(
input = qdBundle.quickConnectInput,
input = quickConnectInputTemp,
onValueChange = {
qdBundle = qdBundle.copy(quickConnectInput = it)
quickConnectInputTemp = it
},
onFocusLost = {
qdBundle = qdBundle.copy(
quickConnectInput = quickConnectInputTemp
)
},
enabled = !adbConnecting,
onAddDevice = {
val target = ConnectionTarget.unmarshalFrom(qdBundle.quickConnectInput)
val target = ConnectionTarget.unmarshalFrom(quickConnectInputTemp)
?: return@QuickConnectCard
savedShortcuts = savedShortcuts.upsert(
DeviceShortcut(host = target.host, port = target.port)
)
Log.d(
"SavedShortcuts",
"size: ${savedShortcuts.size}, list: ${qdBundle.quickDevicesList}"
@@ -1026,8 +1037,9 @@ fun DeviceTabPage(
snackbar.show("已添加设备: ${target.host}:${target.port}")
},
onConnect = {
val target = ConnectionTarget.unmarshalFrom(qdBundle.quickConnectInput)
val target = ConnectionTarget.unmarshalFrom(quickConnectInputTemp)
?: return@QuickConnectCard
adbCallbacks.onQuickConnect(target)
},
)

View File

@@ -492,7 +492,6 @@ fun SettingsPage(
item {
SectionSmallTitle("")
Card {
// TODO: 进入时无视自动更新检查的 CD, 主动触发一次
ArrowPreference(
title = "关于",
summary = updateSummary,

View File

@@ -11,8 +11,6 @@ import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.OrientationLock
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Tick
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.VideoSource
// TODO: 配置切换
data class ClientOptions(
// var serial: String = "", // to server

View File

@@ -953,6 +953,7 @@ internal fun DeviceTile(
val trimmedHost = currentDraft.host.trim()
if (trimmedHost.isBlank()) return@LaunchedEffect
val updated = DeviceShortcut(
id = currentDraft.id,
name = currentDraft.name.trim(),
host = trimmedHost,
port = currentDraft.port,
@@ -1200,7 +1201,7 @@ internal fun DeviceTileList(
internal fun QuickConnectCard(
input: String,
onValueChange: (String) -> Unit,
onFocusChange: (() -> Unit)? = null,
onFocusLost: (() -> Unit)? = null,
onConnect: () -> Unit,
onAddDevice: () -> Unit,
enabled: Boolean = true,
@@ -1240,7 +1241,7 @@ internal fun QuickConnectCard(
modifier = Modifier.fillMaxWidth(),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
onFocusLost = onFocusChange,
onFocusLost = onFocusLost,
)
Row(
modifier = Modifier.fillMaxWidth(),