feat: a profile selector on top of config panel

合并多个 IP 到一个设备后不能特地为远程设备使用限制码率的配置了,外面再放一个
This commit is contained in:
Miuzarte
2026-06-01 00:20:46 +08:00
parent 63787857e9
commit e48f61a61e
5 changed files with 69 additions and 13 deletions

View File

@@ -136,11 +136,11 @@ class DeviceShortcuts(val devices: List<DeviceShortcut>): List<DeviceShortcut> b
val updateById = id != null
val updatedAddresses = when {
updateById -> {
val h = host ?: old.host
updateById && host != null -> {
val p = port ?: old.port
listOf("$h:$p")
listOf("$host:$p")
}
updateById -> old.addresses
newPort != null -> {
old.addresses.map { addr ->

View File

@@ -32,6 +32,7 @@ import io.github.miuzarte.scrcpyforandroid.scaffolds.LazyColumn
import io.github.miuzarte.scrcpyforandroid.scaffolds.SectionSmallTitle
import io.github.miuzarte.scrcpyforandroid.scrcpy.ClientOptions
import io.github.miuzarte.scrcpyforandroid.services.*
import io.github.miuzarte.scrcpyforandroid.storage.Storage.scrcpyProfiles
import io.github.miuzarte.scrcpyforandroid.ui.BlurredBar
import io.github.miuzarte.scrcpyforandroid.ui.LocalEnableBlur
import io.github.miuzarte.scrcpyforandroid.ui.contextClick
@@ -202,6 +203,7 @@ internal fun DeviceTabPage(
val connectedScrcpyProfileId by viewModel.connectedScrcpyProfileId.collectAsState()
val connectedScrcpyBundle by viewModel.connectedScrcpyBundle.collectAsState()
val connectedScrcpyProfileName by viewModel.connectedScrcpyProfileName.collectAsState()
val scrcpyProfilesState by scrcpyProfiles.state.collectAsState()
val canShowPreviewControls by viewModel.canShowPreviewControls.collectAsState()
val virtualButtonLayout by viewModel.virtualButtonLayout.collectAsState()
@@ -580,6 +582,45 @@ internal fun DeviceTabPage(
}
}
@Composable
fun ProfilesTabRow() {
val profileTabs = remember(scrcpyProfilesState.profiles) {
scrcpyProfilesState.profiles.map { it.name }
}
val profileIds = remember(scrcpyProfilesState.profiles) {
scrcpyProfilesState.profiles.map { it.id }
}
val selectedProfileIndex = remember(connectedScrcpyProfileId, profileIds) {
profileIds.indexOf(connectedScrcpyProfileId).coerceAtLeast(0)
}
val textGlobal = stringResource(R.string.text_global)
TabRow(
tabs = profileTabs,
selectedTabIndex = selectedProfileIndex,
onTabSelected = { index ->
val newProfileId = profileIds.getOrNull(index) ?: return@TabRow
if (newProfileId == connectedScrcpyProfileId) return@TabRow
haptic.contextClick()
val device = currentTarget?.let { ct ->
savedShortcuts.firstOrNull { it.matchesAddress(ct) }
}
if (device != null) {
viewModel.updateShortcut(id = device.id, scrcpyProfileId = newProfileId)
}
val profileName = profileTabs.getOrElse(index) { textGlobal }
AppRuntime.snackbar(R.string.device_switched_profile, device?.name ?: "", profileName)
},
modifier = Modifier
.padding(bottom = UiSpacing.Medium),
minWidth = 96.dp,
maxWidth = 192.dp,
height = 48.dp,
itemSpacing = UiSpacing.Medium,
)
}
val pageContentPadding = contentPadding
val pageBottomInnerPadding = bottomInnerPadding
@@ -615,12 +656,14 @@ internal fun DeviceTabPage(
}
item { VirtualButtonsSection() }
item {
if (sessionInfo == null) ProfilesTabRow()
if (useTwoPaneConfigPanel) ScrcpyConfigSectionForTwoPane()
else ScrcpyConfigSection()
}
} else {
item {
SectionSmallTitle(stringResource(R.string.device_section_scrcpy))
if (sessionInfo == null) ProfilesTabRow()
if (useTwoPaneConfigPanel) ScrcpyConfigSectionForTwoPane()
else ScrcpyConfigSection()
}

View File

@@ -141,7 +141,7 @@ internal class DeviceTabViewModel(
) { session, shortcuts ->
val target = session.currentTarget
if (session.isConnected && target != null)
shortcuts.get(target.host, target.port)?.scrcpyProfileId
shortcuts.firstOrNull { it.matchesAddress(target) }?.scrcpyProfileId
?: session.connectedScrcpyProfileId
else
session.connectedScrcpyProfileId
@@ -907,7 +907,7 @@ internal class DeviceTabViewModel(
Triple(connected, target, shortcuts)
}.collect { (connected, target, shortcuts) ->
if (!connected || target == null) return@collect
val boundProfileId = shortcuts.get(target.host, target.port)
val boundProfileId = shortcuts.firstOrNull { it.matchesAddress(target) }
?.scrcpyProfileId ?: ScrcpyOptions.GLOBAL_PROFILE_ID
if (boundProfileId != connectionState.value.adbSession.connectedScrcpyProfileId) {
connectionController.syncConnectedScrcpyProfileId(boundProfileId)

View File

@@ -48,6 +48,7 @@ internal fun RecordPreferencesScreen(
profileId: String,
scrcpy: Scrcpy,
) {
val haptic = LocalHapticFeedback.current
val navigator = LocalRootNavigator.current
val blurBackdrop = rememberBlurBackdrop(LocalEnableBlur.current)
val blurActive = blurBackdrop != null
@@ -63,7 +64,12 @@ internal fun RecordPreferencesScreen(
if (blurActive) Color.Transparent
else colorScheme.surface,
navigationIcon = {
IconButton(onClick = navigator.pop) {
IconButton(
onClick = {
haptic.contextClick()
navigator.pop()
},
) {
Icon(
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
contentDescription = stringResource(R.string.cd_back),

View File

@@ -63,6 +63,7 @@ internal fun ScrcpyAllOptionsScreen(
scrollBehavior: ScrollBehavior,
scrcpy: Scrcpy,
) {
val haptic = LocalHapticFeedback.current
val navigator = LocalRootNavigator.current
val blurBackdrop = rememberBlurBackdrop(LocalEnableBlur.current)
val blurActive = blurBackdrop != null
@@ -148,11 +149,11 @@ internal fun ScrcpyAllOptionsScreen(
suspend fun bindCurrentConnectedDevice(profileId: String) {
val target = AppRuntime.currentConnectionTarget ?: return
val shortcuts = DeviceShortcuts.unmarshalFrom(qdBundleShared.quickDevicesList)
val updated = shortcuts.update(
host = target.host,
port = target.port,
val device = shortcuts.firstOrNull { it.matchesAddress(target) }
val updated = if (device != null) shortcuts.update(
id = device.id,
scrcpyProfileId = profileId,
)
) else shortcuts
if (updated != shortcuts) {
quickDevices.updateBundle { bundle ->
bundle.copy(quickDevicesList = updated.marshalToString())
@@ -169,7 +170,12 @@ internal fun ScrcpyAllOptionsScreen(
if (blurActive) Color.Transparent
else colorScheme.surface,
navigationIcon = {
IconButton(onClick = navigator.pop) {
IconButton(
onClick = {
haptic.contextClick()
navigator.pop()
},
) {
Icon(
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
contentDescription = stringResource(R.string.cd_back),
@@ -205,6 +211,7 @@ internal fun ScrcpyAllOptionsScreen(
val nextProfileId = profileIds.getOrNull(index)
?: return@TabRow
if (nextProfileId == selectedProfileId) return@TabRow
haptic.contextClick()
scope.launch {
saveBundleForProfile(selectedProfileId, soBundleState.value)
bindCurrentConnectedDevice(nextProfileId)
@@ -220,8 +227,8 @@ internal fun ScrcpyAllOptionsScreen(
}
},
modifier = Modifier
.padding(bottom = UiSpacing.Medium)
.padding(horizontal = UiSpacing.Medium),
.padding(bottom = UiSpacing.PageHorizontal)
.padding(horizontal = UiSpacing.PageVertical),
minWidth = 96.dp,
maxWidth = 192.dp,
height = 48.dp,