fix: buffered quick connect card input, quick device ip/port modifying
This commit is contained in:
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
- 带生物认证的锁屏密码自动填充 (入口位于虚拟按钮中)
|
||||||
- 可替换 scrcpy-server
|
- 可替换 scrcpy-server
|
||||||
- 利用 mDNS 服务实现自动连接启用无线调试的设备、自动发现等待配对设备的IP与端口
|
- 利用 mDNS 服务实现自动连接启用无线调试的设备、自动发现等待配对设备的IP与端口
|
||||||
- 自动横竖屏切换(算吗
|
- 自动横竖屏切换(算吗
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ android {
|
|||||||
applicationId = "io.github.miuzarte.scrcpyforandroid"
|
applicationId = "io.github.miuzarte.scrcpyforandroid"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 10
|
versionCode = 11
|
||||||
versionName = "0.1.4"
|
versionName = "0.1.5"
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
|
|||||||
@@ -19,8 +19,24 @@ class DeviceShortcuts(val devices: List<DeviceShortcut>) : List<DeviceShortcut>
|
|||||||
separator: String = DEFAULT_SEPARATOR,
|
separator: String = DEFAULT_SEPARATOR,
|
||||||
): DeviceShortcuts {
|
): DeviceShortcuts {
|
||||||
if (s.isBlank()) return DeviceShortcuts(emptyList())
|
if (s.isBlank()) return DeviceShortcuts(emptyList())
|
||||||
|
var nextLegacyId = 1
|
||||||
val list = s.splitToSequence(separator)
|
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()
|
.toList()
|
||||||
return DeviceShortcuts(list)
|
return DeviceShortcuts(list)
|
||||||
}
|
}
|
||||||
@@ -56,6 +72,7 @@ class DeviceShortcuts(val devices: List<DeviceShortcut>) : List<DeviceShortcut>
|
|||||||
val updateById = id != null
|
val updateById = id != null
|
||||||
|
|
||||||
val updated = DeviceShortcut(
|
val updated = DeviceShortcut(
|
||||||
|
id = old.id,
|
||||||
name = when {
|
name = when {
|
||||||
name == null -> old.name
|
name == null -> old.name
|
||||||
updateNameOnlyWhenEmpty && old.name.isNotBlank() -> old.name
|
updateNameOnlyWhenEmpty && old.name.isNotBlank() -> old.name
|
||||||
@@ -88,17 +105,35 @@ class DeviceShortcuts(val devices: List<DeviceShortcut>) : List<DeviceShortcut>
|
|||||||
shortcut: DeviceShortcut,
|
shortcut: DeviceShortcut,
|
||||||
index: Int? = null,
|
index: Int? = null,
|
||||||
): DeviceShortcuts {
|
): 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()
|
val newList = devices.toMutableList()
|
||||||
if (existingIdx >= 0) {
|
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 {
|
} else {
|
||||||
if (index != null) newList.add(index, shortcut)
|
if (index != null) newList.add(index, normalizedShortcut)
|
||||||
else newList.add(shortcut)
|
else newList.add(normalizedShortcut)
|
||||||
}
|
}
|
||||||
return DeviceShortcuts(newList)
|
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 {
|
fun move(fromIndex: Int, toIndex: Int): DeviceShortcuts {
|
||||||
if (fromIndex !in devices.indices || toIndex !in devices.indices) return this
|
if (fromIndex !in devices.indices || toIndex !in devices.indices) return this
|
||||||
if (fromIndex == toIndex) return this
|
if (fromIndex == toIndex) return this
|
||||||
@@ -121,8 +156,8 @@ class DeviceShortcuts(val devices: List<DeviceShortcut>) : List<DeviceShortcut>
|
|||||||
DeviceShortcuts(devices)
|
DeviceShortcuts(devices)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 增加 id 字段,解决编辑端口的问题
|
|
||||||
data class DeviceShortcut(
|
data class DeviceShortcut(
|
||||||
|
val id: String = "",
|
||||||
val name: String = "",
|
val name: String = "",
|
||||||
val host: String,
|
val host: String,
|
||||||
val port: Int = Defaults.ADB_PORT,
|
val port: Int = Defaults.ADB_PORT,
|
||||||
@@ -130,11 +165,10 @@ data class DeviceShortcut(
|
|||||||
val openFullscreenOnStart: Boolean = false,
|
val openFullscreenOnStart: Boolean = false,
|
||||||
val scrcpyProfileId: String = ScrcpyOptions.GLOBAL_PROFILE_ID,
|
val scrcpyProfileId: String = ScrcpyOptions.GLOBAL_PROFILE_ID,
|
||||||
) {
|
) {
|
||||||
val id: String get() = "$host:$port"
|
|
||||||
|
|
||||||
fun marshalToString(
|
fun marshalToString(
|
||||||
separator: String = DEFAULT_SEPARATOR,
|
separator: String = DEFAULT_SEPARATOR,
|
||||||
): String = listOf(
|
): String = listOf(
|
||||||
|
id.trim(),
|
||||||
name.trim(),
|
name.trim(),
|
||||||
host.trim(),
|
host.trim(),
|
||||||
port.toString(),
|
port.toString(),
|
||||||
@@ -150,10 +184,45 @@ data class DeviceShortcut(
|
|||||||
fun unmarshalFrom(
|
fun unmarshalFrom(
|
||||||
s: String,
|
s: String,
|
||||||
separator: String = DEFAULT_SEPARATOR,
|
separator: String = DEFAULT_SEPARATOR,
|
||||||
|
fallbackId: String? = null,
|
||||||
): DeviceShortcut? {
|
): DeviceShortcut? {
|
||||||
val parts = s.split(separator)
|
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 -> {
|
3, 4, 5, 6 -> {
|
||||||
|
val id = fallbackId ?: return null
|
||||||
val name = parts[0].trim()
|
val name = parts[0].trim()
|
||||||
val host = parts[1].trim()
|
val host = parts[1].trim()
|
||||||
val port = parts[2].trim().toIntOrNull() ?: Defaults.ADB_PORT
|
val port = parts[2].trim().toIntOrNull() ?: Defaults.ADB_PORT
|
||||||
@@ -169,6 +238,7 @@ data class DeviceShortcut(
|
|||||||
?: ScrcpyOptions.GLOBAL_PROFILE_ID
|
?: ScrcpyOptions.GLOBAL_PROFILE_ID
|
||||||
|
|
||||||
if (host.isNotBlank()) DeviceShortcut(
|
if (host.isNotBlank()) DeviceShortcut(
|
||||||
|
id = id,
|
||||||
name = name,
|
name = name,
|
||||||
host = host,
|
host = host,
|
||||||
port = port,
|
port = port,
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ fun DeviceTabPage(
|
|||||||
scrcpy: Scrcpy,
|
scrcpy: Scrcpy,
|
||||||
) {
|
) {
|
||||||
val activity = LocalActivity.current
|
val activity = LocalActivity.current
|
||||||
val fragmentActivity = remember(activity) {activity as? FragmentActivity}
|
val fragmentActivity = remember(activity) { activity as? FragmentActivity }
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -320,10 +320,14 @@ fun DeviceTabPage(
|
|||||||
VirtualButtonActions.parseStoredLayout(asBundle.virtualButtonsLayout)
|
VirtualButtonActions.parseStoredLayout(asBundle.virtualButtonsLayout)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var quickConnectInputTemp by rememberSaveable(qdBundle.quickConnectInput) {
|
||||||
|
mutableStateOf(qdBundle.quickConnectInput)
|
||||||
|
}
|
||||||
|
|
||||||
var savedShortcuts by remember {
|
var savedShortcuts by remember {
|
||||||
mutableStateOf(DeviceShortcuts.unmarshalFrom(qdBundle.quickDevicesList))
|
mutableStateOf(DeviceShortcuts.unmarshalFrom(qdBundle.quickDevicesList))
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(qdBundle.quickDevicesList) {
|
LaunchedEffect(qdBundle.quickDevicesList) {
|
||||||
savedShortcuts = DeviceShortcuts.unmarshalFrom(qdBundle.quickDevicesList)
|
savedShortcuts = DeviceShortcuts.unmarshalFrom(qdBundle.quickDevicesList)
|
||||||
}
|
}
|
||||||
@@ -1008,17 +1012,24 @@ fun DeviceTabPage(
|
|||||||
// "快速连接"
|
// "快速连接"
|
||||||
item {
|
item {
|
||||||
QuickConnectCard(
|
QuickConnectCard(
|
||||||
input = qdBundle.quickConnectInput,
|
input = quickConnectInputTemp,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
qdBundle = qdBundle.copy(quickConnectInput = it)
|
quickConnectInputTemp = it
|
||||||
|
},
|
||||||
|
onFocusLost = {
|
||||||
|
qdBundle = qdBundle.copy(
|
||||||
|
quickConnectInput = quickConnectInputTemp
|
||||||
|
)
|
||||||
},
|
},
|
||||||
enabled = !adbConnecting,
|
enabled = !adbConnecting,
|
||||||
onAddDevice = {
|
onAddDevice = {
|
||||||
val target = ConnectionTarget.unmarshalFrom(qdBundle.quickConnectInput)
|
val target = ConnectionTarget.unmarshalFrom(quickConnectInputTemp)
|
||||||
?: return@QuickConnectCard
|
?: return@QuickConnectCard
|
||||||
|
|
||||||
savedShortcuts = savedShortcuts.upsert(
|
savedShortcuts = savedShortcuts.upsert(
|
||||||
DeviceShortcut(host = target.host, port = target.port)
|
DeviceShortcut(host = target.host, port = target.port)
|
||||||
)
|
)
|
||||||
|
|
||||||
Log.d(
|
Log.d(
|
||||||
"SavedShortcuts",
|
"SavedShortcuts",
|
||||||
"size: ${savedShortcuts.size}, list: ${qdBundle.quickDevicesList}"
|
"size: ${savedShortcuts.size}, list: ${qdBundle.quickDevicesList}"
|
||||||
@@ -1026,8 +1037,9 @@ fun DeviceTabPage(
|
|||||||
snackbar.show("已添加设备: ${target.host}:${target.port}")
|
snackbar.show("已添加设备: ${target.host}:${target.port}")
|
||||||
},
|
},
|
||||||
onConnect = {
|
onConnect = {
|
||||||
val target = ConnectionTarget.unmarshalFrom(qdBundle.quickConnectInput)
|
val target = ConnectionTarget.unmarshalFrom(quickConnectInputTemp)
|
||||||
?: return@QuickConnectCard
|
?: return@QuickConnectCard
|
||||||
|
|
||||||
adbCallbacks.onQuickConnect(target)
|
adbCallbacks.onQuickConnect(target)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -492,7 +492,6 @@ fun SettingsPage(
|
|||||||
item {
|
item {
|
||||||
SectionSmallTitle("")
|
SectionSmallTitle("")
|
||||||
Card {
|
Card {
|
||||||
// TODO: 进入时无视自动更新检查的 CD, 主动触发一次
|
|
||||||
ArrowPreference(
|
ArrowPreference(
|
||||||
title = "关于",
|
title = "关于",
|
||||||
summary = updateSummary,
|
summary = updateSummary,
|
||||||
|
|||||||
@@ -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.Tick
|
||||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.VideoSource
|
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.VideoSource
|
||||||
|
|
||||||
// TODO: 配置切换
|
|
||||||
|
|
||||||
data class ClientOptions(
|
data class ClientOptions(
|
||||||
// var serial: String = "", // to server
|
// var serial: String = "", // to server
|
||||||
|
|
||||||
|
|||||||
@@ -953,6 +953,7 @@ internal fun DeviceTile(
|
|||||||
val trimmedHost = currentDraft.host.trim()
|
val trimmedHost = currentDraft.host.trim()
|
||||||
if (trimmedHost.isBlank()) return@LaunchedEffect
|
if (trimmedHost.isBlank()) return@LaunchedEffect
|
||||||
val updated = DeviceShortcut(
|
val updated = DeviceShortcut(
|
||||||
|
id = currentDraft.id,
|
||||||
name = currentDraft.name.trim(),
|
name = currentDraft.name.trim(),
|
||||||
host = trimmedHost,
|
host = trimmedHost,
|
||||||
port = currentDraft.port,
|
port = currentDraft.port,
|
||||||
@@ -1200,7 +1201,7 @@ internal fun DeviceTileList(
|
|||||||
internal fun QuickConnectCard(
|
internal fun QuickConnectCard(
|
||||||
input: String,
|
input: String,
|
||||||
onValueChange: (String) -> Unit,
|
onValueChange: (String) -> Unit,
|
||||||
onFocusChange: (() -> Unit)? = null,
|
onFocusLost: (() -> Unit)? = null,
|
||||||
onConnect: () -> Unit,
|
onConnect: () -> Unit,
|
||||||
onAddDevice: () -> Unit,
|
onAddDevice: () -> Unit,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
@@ -1240,7 +1241,7 @@ internal fun QuickConnectCard(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
||||||
onFocusLost = onFocusChange,
|
onFocusLost = onFocusLost,
|
||||||
)
|
)
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
|||||||
Reference in New Issue
Block a user