feat: a profile selector on top of config panel
合并多个 IP 到一个设备后不能特地为远程设备使用限制码率的配置了,外面再放一个
This commit is contained in:
@@ -136,11 +136,11 @@ class DeviceShortcuts(val devices: List<DeviceShortcut>): List<DeviceShortcut> b
|
|||||||
val updateById = id != null
|
val updateById = id != null
|
||||||
|
|
||||||
val updatedAddresses = when {
|
val updatedAddresses = when {
|
||||||
updateById -> {
|
updateById && host != null -> {
|
||||||
val h = host ?: old.host
|
|
||||||
val p = port ?: old.port
|
val p = port ?: old.port
|
||||||
listOf("$h:$p")
|
listOf("$host:$p")
|
||||||
}
|
}
|
||||||
|
updateById -> old.addresses
|
||||||
|
|
||||||
newPort != null -> {
|
newPort != null -> {
|
||||||
old.addresses.map { addr ->
|
old.addresses.map { addr ->
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import io.github.miuzarte.scrcpyforandroid.scaffolds.LazyColumn
|
|||||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.SectionSmallTitle
|
import io.github.miuzarte.scrcpyforandroid.scaffolds.SectionSmallTitle
|
||||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.ClientOptions
|
import io.github.miuzarte.scrcpyforandroid.scrcpy.ClientOptions
|
||||||
import io.github.miuzarte.scrcpyforandroid.services.*
|
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.BlurredBar
|
||||||
import io.github.miuzarte.scrcpyforandroid.ui.LocalEnableBlur
|
import io.github.miuzarte.scrcpyforandroid.ui.LocalEnableBlur
|
||||||
import io.github.miuzarte.scrcpyforandroid.ui.contextClick
|
import io.github.miuzarte.scrcpyforandroid.ui.contextClick
|
||||||
@@ -202,6 +203,7 @@ internal fun DeviceTabPage(
|
|||||||
val connectedScrcpyProfileId by viewModel.connectedScrcpyProfileId.collectAsState()
|
val connectedScrcpyProfileId by viewModel.connectedScrcpyProfileId.collectAsState()
|
||||||
val connectedScrcpyBundle by viewModel.connectedScrcpyBundle.collectAsState()
|
val connectedScrcpyBundle by viewModel.connectedScrcpyBundle.collectAsState()
|
||||||
val connectedScrcpyProfileName by viewModel.connectedScrcpyProfileName.collectAsState()
|
val connectedScrcpyProfileName by viewModel.connectedScrcpyProfileName.collectAsState()
|
||||||
|
val scrcpyProfilesState by scrcpyProfiles.state.collectAsState()
|
||||||
val canShowPreviewControls by viewModel.canShowPreviewControls.collectAsState()
|
val canShowPreviewControls by viewModel.canShowPreviewControls.collectAsState()
|
||||||
val virtualButtonLayout by viewModel.virtualButtonLayout.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 pageContentPadding = contentPadding
|
||||||
val pageBottomInnerPadding = bottomInnerPadding
|
val pageBottomInnerPadding = bottomInnerPadding
|
||||||
|
|
||||||
@@ -615,12 +656,14 @@ internal fun DeviceTabPage(
|
|||||||
}
|
}
|
||||||
item { VirtualButtonsSection() }
|
item { VirtualButtonsSection() }
|
||||||
item {
|
item {
|
||||||
|
if (sessionInfo == null) ProfilesTabRow()
|
||||||
if (useTwoPaneConfigPanel) ScrcpyConfigSectionForTwoPane()
|
if (useTwoPaneConfigPanel) ScrcpyConfigSectionForTwoPane()
|
||||||
else ScrcpyConfigSection()
|
else ScrcpyConfigSection()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
item {
|
item {
|
||||||
SectionSmallTitle(stringResource(R.string.device_section_scrcpy))
|
SectionSmallTitle(stringResource(R.string.device_section_scrcpy))
|
||||||
|
if (sessionInfo == null) ProfilesTabRow()
|
||||||
if (useTwoPaneConfigPanel) ScrcpyConfigSectionForTwoPane()
|
if (useTwoPaneConfigPanel) ScrcpyConfigSectionForTwoPane()
|
||||||
else ScrcpyConfigSection()
|
else ScrcpyConfigSection()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ internal class DeviceTabViewModel(
|
|||||||
) { session, shortcuts ->
|
) { session, shortcuts ->
|
||||||
val target = session.currentTarget
|
val target = session.currentTarget
|
||||||
if (session.isConnected && target != null)
|
if (session.isConnected && target != null)
|
||||||
shortcuts.get(target.host, target.port)?.scrcpyProfileId
|
shortcuts.firstOrNull { it.matchesAddress(target) }?.scrcpyProfileId
|
||||||
?: session.connectedScrcpyProfileId
|
?: session.connectedScrcpyProfileId
|
||||||
else
|
else
|
||||||
session.connectedScrcpyProfileId
|
session.connectedScrcpyProfileId
|
||||||
@@ -907,7 +907,7 @@ internal class DeviceTabViewModel(
|
|||||||
Triple(connected, target, shortcuts)
|
Triple(connected, target, shortcuts)
|
||||||
}.collect { (connected, target, shortcuts) ->
|
}.collect { (connected, target, shortcuts) ->
|
||||||
if (!connected || target == null) return@collect
|
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
|
?.scrcpyProfileId ?: ScrcpyOptions.GLOBAL_PROFILE_ID
|
||||||
if (boundProfileId != connectionState.value.adbSession.connectedScrcpyProfileId) {
|
if (boundProfileId != connectionState.value.adbSession.connectedScrcpyProfileId) {
|
||||||
connectionController.syncConnectedScrcpyProfileId(boundProfileId)
|
connectionController.syncConnectedScrcpyProfileId(boundProfileId)
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ internal fun RecordPreferencesScreen(
|
|||||||
profileId: String,
|
profileId: String,
|
||||||
scrcpy: Scrcpy,
|
scrcpy: Scrcpy,
|
||||||
) {
|
) {
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
val navigator = LocalRootNavigator.current
|
val navigator = LocalRootNavigator.current
|
||||||
val blurBackdrop = rememberBlurBackdrop(LocalEnableBlur.current)
|
val blurBackdrop = rememberBlurBackdrop(LocalEnableBlur.current)
|
||||||
val blurActive = blurBackdrop != null
|
val blurActive = blurBackdrop != null
|
||||||
@@ -63,7 +64,12 @@ internal fun RecordPreferencesScreen(
|
|||||||
if (blurActive) Color.Transparent
|
if (blurActive) Color.Transparent
|
||||||
else colorScheme.surface,
|
else colorScheme.surface,
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = navigator.pop) {
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
haptic.contextClick()
|
||||||
|
navigator.pop()
|
||||||
|
},
|
||||||
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
|
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
|
||||||
contentDescription = stringResource(R.string.cd_back),
|
contentDescription = stringResource(R.string.cd_back),
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ internal fun ScrcpyAllOptionsScreen(
|
|||||||
scrollBehavior: ScrollBehavior,
|
scrollBehavior: ScrollBehavior,
|
||||||
scrcpy: Scrcpy,
|
scrcpy: Scrcpy,
|
||||||
) {
|
) {
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
val navigator = LocalRootNavigator.current
|
val navigator = LocalRootNavigator.current
|
||||||
val blurBackdrop = rememberBlurBackdrop(LocalEnableBlur.current)
|
val blurBackdrop = rememberBlurBackdrop(LocalEnableBlur.current)
|
||||||
val blurActive = blurBackdrop != null
|
val blurActive = blurBackdrop != null
|
||||||
@@ -148,11 +149,11 @@ internal fun ScrcpyAllOptionsScreen(
|
|||||||
suspend fun bindCurrentConnectedDevice(profileId: String) {
|
suspend fun bindCurrentConnectedDevice(profileId: String) {
|
||||||
val target = AppRuntime.currentConnectionTarget ?: return
|
val target = AppRuntime.currentConnectionTarget ?: return
|
||||||
val shortcuts = DeviceShortcuts.unmarshalFrom(qdBundleShared.quickDevicesList)
|
val shortcuts = DeviceShortcuts.unmarshalFrom(qdBundleShared.quickDevicesList)
|
||||||
val updated = shortcuts.update(
|
val device = shortcuts.firstOrNull { it.matchesAddress(target) }
|
||||||
host = target.host,
|
val updated = if (device != null) shortcuts.update(
|
||||||
port = target.port,
|
id = device.id,
|
||||||
scrcpyProfileId = profileId,
|
scrcpyProfileId = profileId,
|
||||||
)
|
) else shortcuts
|
||||||
if (updated != shortcuts) {
|
if (updated != shortcuts) {
|
||||||
quickDevices.updateBundle { bundle ->
|
quickDevices.updateBundle { bundle ->
|
||||||
bundle.copy(quickDevicesList = updated.marshalToString())
|
bundle.copy(quickDevicesList = updated.marshalToString())
|
||||||
@@ -169,7 +170,12 @@ internal fun ScrcpyAllOptionsScreen(
|
|||||||
if (blurActive) Color.Transparent
|
if (blurActive) Color.Transparent
|
||||||
else colorScheme.surface,
|
else colorScheme.surface,
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = navigator.pop) {
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
haptic.contextClick()
|
||||||
|
navigator.pop()
|
||||||
|
},
|
||||||
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
|
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
|
||||||
contentDescription = stringResource(R.string.cd_back),
|
contentDescription = stringResource(R.string.cd_back),
|
||||||
@@ -205,6 +211,7 @@ internal fun ScrcpyAllOptionsScreen(
|
|||||||
val nextProfileId = profileIds.getOrNull(index)
|
val nextProfileId = profileIds.getOrNull(index)
|
||||||
?: return@TabRow
|
?: return@TabRow
|
||||||
if (nextProfileId == selectedProfileId) return@TabRow
|
if (nextProfileId == selectedProfileId) return@TabRow
|
||||||
|
haptic.contextClick()
|
||||||
scope.launch {
|
scope.launch {
|
||||||
saveBundleForProfile(selectedProfileId, soBundleState.value)
|
saveBundleForProfile(selectedProfileId, soBundleState.value)
|
||||||
bindCurrentConnectedDevice(nextProfileId)
|
bindCurrentConnectedDevice(nextProfileId)
|
||||||
@@ -220,8 +227,8 @@ internal fun ScrcpyAllOptionsScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(bottom = UiSpacing.Medium)
|
.padding(bottom = UiSpacing.PageHorizontal)
|
||||||
.padding(horizontal = UiSpacing.Medium),
|
.padding(horizontal = UiSpacing.PageVertical),
|
||||||
minWidth = 96.dp,
|
minWidth = 96.dp,
|
||||||
maxWidth = 192.dp,
|
maxWidth = 192.dp,
|
||||||
height = 48.dp,
|
height = 48.dp,
|
||||||
|
|||||||
Reference in New Issue
Block a user