enhance: Settings and Virtual Button Management
- 用 `reorderable` 重构排序组件 - 重构虚拟按钮配置储存逻辑 - 虚拟图标现在可以选择是否显示文字描述 - 统一所有图标风格为 `Rounded` - `StatusCard` 状态卡大卡的颜色现在优先取决于应用内设置,而非系统设置
This commit is contained in:
@@ -11,9 +11,10 @@
|
|||||||
<img src="https://github.com/user-attachments/assets/74170ada-6dee-4ec7-ab24-c5ef2a231a47" height="320" alt="Screenshot 2" />
|
<img src="https://github.com/user-attachments/assets/74170ada-6dee-4ec7-ab24-c5ef2a231a47" height="320" alt="Screenshot 2" />
|
||||||
<img src="https://github.com/user-attachments/assets/6301f2fb-624b-4209-b548-6f37b9bcedc8" height="320" alt="Screenshot 3" />
|
<img src="https://github.com/user-attachments/assets/6301f2fb-624b-4209-b548-6f37b9bcedc8" height="320" alt="Screenshot 3" />
|
||||||
<img src="https://github.com/user-attachments/assets/f513b7ba-0389-4176-8382-c1a08c4eba99" height="320" alt="Screenshot 4" />
|
<img src="https://github.com/user-attachments/assets/f513b7ba-0389-4176-8382-c1a08c4eba99" height="320" alt="Screenshot 4" />
|
||||||
|
<img src="https://github.com/user-attachments/assets/7a50bd1f-8095-4269-8e58-88316d86e3d8" height="320" alt="Screenshot 5" />
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://github.com/user-attachments/assets/1b52a662-108d-49cb-a86e-eccc8ac12d64" height="160" alt="Screenshot 5" />
|
<img src="https://github.com/user-attachments/assets/1b52a662-108d-49cb-a86e-eccc8ac12d64" height="160" alt="Screenshot 6" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -24,10 +25,11 @@
|
|||||||
|
|
||||||
## 已知问题 / TODO
|
## 已知问题 / TODO
|
||||||
|
|
||||||
- 组件排序动画一坨
|
|
||||||
- 退出全屏时的横竖屏状态可能不对,断开 scrcpy 重连就好
|
- 退出全屏时的横竖屏状态可能不对,断开 scrcpy 重连就好
|
||||||
- 如果受控机在锁屏时处理网络连接较慢,会导致应用界面无响应过长时间被系统杀掉(点名你米)
|
- 如果受控机在锁屏时处理网络连接较慢,会导致应用界面无响应过长时间被系统杀掉(点名你米)
|
||||||
- 虚拟按键的截图实现方式为发送 `keycode 120`,安卓官方的定义为 `System Request / Print Screen key.`,不同的厂商有不同的实现,在某些类原生(`AxionOS`) 上的行为是软重启
|
- 虚拟按键的截图实现方式为发送
|
||||||
|
`keycode 120`,安卓官方([keycodes.h#349](https://android.googlesource.com/platform/frameworks/native/+/master/include/android/keycodes.h#349))的定义为
|
||||||
|
`System Request / Print Screen key.`,不同的厂商有不同的实现,在某些类原生(`AxionOS`) 上的行为是软重启
|
||||||
- I18N
|
- I18N
|
||||||
|
|
||||||
## 建议搭配模块
|
## 建议搭配模块
|
||||||
|
|||||||
@@ -12,6 +12,10 @@
|
|||||||
"name": "miuix",
|
"name": "miuix",
|
||||||
"path": "../miuix"
|
"path": "../miuix"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Reorderable",
|
||||||
|
"path": "../Reorderable"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "shizuku",
|
"name": "shizuku",
|
||||||
"path": "../shizuku"
|
"path": "../shizuku"
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ dependencies {
|
|||||||
implementation("org.lsposed.libcxx:libcxx:27.0.12077973")
|
implementation("org.lsposed.libcxx:libcxx:27.0.12077973")
|
||||||
implementation("org.bouncycastle:bcpkix-jdk18on:1.80")
|
implementation("org.bouncycastle:bcpkix-jdk18on:1.80")
|
||||||
implementation("org.conscrypt:conscrypt-android:2.5.2")
|
implementation("org.conscrypt:conscrypt-android:2.5.2")
|
||||||
|
implementation("sh.calvin.reorderable:reorderable:3.0.0")
|
||||||
|
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||||
|
|||||||
@@ -65,9 +65,9 @@ object AppDefaults {
|
|||||||
const val SHOW_FULLSCREEN_VIRTUAL_BUTTONS = true
|
const val SHOW_FULLSCREEN_VIRTUAL_BUTTONS = true
|
||||||
const val KEEP_SCREEN_ON_WHEN_STREAMING = false
|
const val KEEP_SCREEN_ON_WHEN_STREAMING = false
|
||||||
const val DEVICE_PREVIEW_CARD_HEIGHT_DP = 320
|
const val DEVICE_PREVIEW_CARD_HEIGHT_DP = 320
|
||||||
const val VIRTUAL_BUTTONS_OUTSIDE = "more,home,back"
|
const val PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT = true
|
||||||
const val VIRTUAL_BUTTONS_IN_MORE =
|
const val VIRTUAL_BUTTONS_LAYOUT =
|
||||||
"app_switch,menu,notification,volume_up,volume_down,volume_mute,power,screenshot"
|
"more:1,app_switch:1,home:0,back:1,menu:0,notification:0,volume_up:0,volume_down:0,volume_mute:0,power:0,screenshot:0"
|
||||||
|
|
||||||
const val CUSTOM_SERVER_URI = ""
|
const val CUSTOM_SERVER_URI = ""
|
||||||
|
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ object AppPreferenceKeys {
|
|||||||
const val SHOW_FULLSCREEN_VIRTUAL_BUTTONS = "show_fullscreen_virtual_buttons"
|
const val SHOW_FULLSCREEN_VIRTUAL_BUTTONS = "show_fullscreen_virtual_buttons"
|
||||||
const val KEEP_SCREEN_ON_WHEN_STREAMING = "keep_screen_on_when_streaming"
|
const val KEEP_SCREEN_ON_WHEN_STREAMING = "keep_screen_on_when_streaming"
|
||||||
const val DEVICE_PREVIEW_CARD_HEIGHT_DP = "device_preview_card_height_dp"
|
const val DEVICE_PREVIEW_CARD_HEIGHT_DP = "device_preview_card_height_dp"
|
||||||
const val VIRTUAL_BUTTONS_OUTSIDE = "virtual_buttons_outside"
|
const val PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT = "preview_virtual_button_show_text"
|
||||||
const val VIRTUAL_BUTTONS_IN_MORE = "virtual_buttons_in_more"
|
const val VIRTUAL_BUTTONS_LAYOUT = "virtual_buttons_layout"
|
||||||
|
|
||||||
const val CUSTOM_SERVER_URI = "custom_server_uri"
|
const val CUSTOM_SERVER_URI = "custom_server_uri"
|
||||||
|
|
||||||
|
|||||||
@@ -7,26 +7,87 @@ import androidx.compose.runtime.rememberUpdatedState
|
|||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun performHaptics(type: HapticFeedbackType): Unit {
|
||||||
|
LocalHapticFeedback.current.performHapticFeedback(type)
|
||||||
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class AppHaptics(
|
data class AppHaptics(
|
||||||
val press: () -> Unit,
|
|
||||||
val confirm: () -> Unit,
|
val confirm: () -> Unit,
|
||||||
|
val contextClick: () -> Unit,
|
||||||
|
val gestureEnd: () -> Unit,
|
||||||
|
val gestureThresholdActivate: () -> Unit,
|
||||||
|
val keyboardTap: () -> Unit,
|
||||||
|
val longPress: () -> Unit,
|
||||||
|
val reject: () -> Unit,
|
||||||
|
val segmentFrequentTick: () -> Unit,
|
||||||
|
val segmentTick: () -> Unit,
|
||||||
|
val textHandleMove: () -> Unit,
|
||||||
|
val toggleOff: () -> Unit,
|
||||||
|
val toggleOn: () -> Unit,
|
||||||
|
val virtualKey: () -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberAppHaptics(): AppHaptics {
|
fun rememberAppHaptics(): AppHaptics {
|
||||||
val hapticFeedback = LocalHapticFeedback.current
|
val hapticFeedback = LocalHapticFeedback.current
|
||||||
val pressAction = rememberUpdatedState {
|
|
||||||
|
val performConfirm = rememberUpdatedState {
|
||||||
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.Confirm)
|
||||||
|
}
|
||||||
|
val performContextClick = rememberUpdatedState {
|
||||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.ContextClick)
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.ContextClick)
|
||||||
}
|
}
|
||||||
val confirmAction = rememberUpdatedState {
|
val performGestureEnd = rememberUpdatedState {
|
||||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.Confirm)
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.GestureEnd)
|
||||||
|
}
|
||||||
|
val performGestureThresholdActivate = rememberUpdatedState {
|
||||||
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.GestureThresholdActivate)
|
||||||
|
}
|
||||||
|
val performKeyboardTan = rememberUpdatedState {
|
||||||
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.KeyboardTap)
|
||||||
|
}
|
||||||
|
val performLongPress = rememberUpdatedState {
|
||||||
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
}
|
||||||
|
val performReject = rememberUpdatedState {
|
||||||
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.Reject)
|
||||||
|
}
|
||||||
|
val performSegmentFrequentTick = rememberUpdatedState {
|
||||||
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.SegmentFrequentTick)
|
||||||
|
}
|
||||||
|
val performSegmentTick = rememberUpdatedState {
|
||||||
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.SegmentTick)
|
||||||
|
}
|
||||||
|
val performTextHandleMove = rememberUpdatedState {
|
||||||
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.TextHandleMove)
|
||||||
|
}
|
||||||
|
val performToggleOff = rememberUpdatedState {
|
||||||
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.ToggleOff)
|
||||||
|
}
|
||||||
|
val performToggleOn = rememberUpdatedState {
|
||||||
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.ToggleOn)
|
||||||
|
}
|
||||||
|
val performVirtualKey = rememberUpdatedState {
|
||||||
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.VirtualKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
return remember {
|
return remember {
|
||||||
AppHaptics(
|
AppHaptics(
|
||||||
press = { pressAction.value.invoke() },
|
confirm = { performConfirm.value.invoke() },
|
||||||
confirm = { confirmAction.value.invoke() },
|
contextClick = { performContextClick.value.invoke() },
|
||||||
|
gestureEnd = { performGestureEnd.value.invoke() },
|
||||||
|
gestureThresholdActivate = { performGestureThresholdActivate.value.invoke() },
|
||||||
|
keyboardTap = { performKeyboardTan.value.invoke() },
|
||||||
|
longPress = { performLongPress.value.invoke() },
|
||||||
|
reject = { performReject.value.invoke() },
|
||||||
|
segmentFrequentTick = { performSegmentFrequentTick.value.invoke() },
|
||||||
|
segmentTick = { performSegmentTick.value.invoke() },
|
||||||
|
textHandleMove = { performTextHandleMove.value.invoke() },
|
||||||
|
toggleOff = { performToggleOff.value.invoke() },
|
||||||
|
toggleOn = { performToggleOn.value.invoke() },
|
||||||
|
virtualKey = { performVirtualKey.value.invoke() },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.util.Log
|
|||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
@@ -48,11 +47,8 @@ import io.github.miuzarte.scrcpyforandroid.widgets.LogsPanel
|
|||||||
import io.github.miuzarte.scrcpyforandroid.widgets.PairingCard
|
import io.github.miuzarte.scrcpyforandroid.widgets.PairingCard
|
||||||
import io.github.miuzarte.scrcpyforandroid.widgets.PreviewCard
|
import io.github.miuzarte.scrcpyforandroid.widgets.PreviewCard
|
||||||
import io.github.miuzarte.scrcpyforandroid.widgets.QuickConnectCard
|
import io.github.miuzarte.scrcpyforandroid.widgets.QuickConnectCard
|
||||||
|
import io.github.miuzarte.scrcpyforandroid.widgets.ReorderableList
|
||||||
import io.github.miuzarte.scrcpyforandroid.widgets.SectionSmallTitle
|
import io.github.miuzarte.scrcpyforandroid.widgets.SectionSmallTitle
|
||||||
import io.github.miuzarte.scrcpyforandroid.widgets.SortDropPayload
|
|
||||||
import io.github.miuzarte.scrcpyforandroid.widgets.SortTransferDirection
|
|
||||||
import io.github.miuzarte.scrcpyforandroid.widgets.SortableCardItem
|
|
||||||
import io.github.miuzarte.scrcpyforandroid.widgets.SortableCardList
|
|
||||||
import io.github.miuzarte.scrcpyforandroid.widgets.StatusCard
|
import io.github.miuzarte.scrcpyforandroid.widgets.StatusCard
|
||||||
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonAction
|
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonAction
|
||||||
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonActions
|
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonActions
|
||||||
@@ -73,7 +69,6 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
private const val ADB_CONNECT_TIMEOUT_MS = 3_000L
|
private const val ADB_CONNECT_TIMEOUT_MS = 3_000L
|
||||||
private const val ADB_KEEPALIVE_INTERVAL_MS = 3_000L
|
private const val ADB_KEEPALIVE_INTERVAL_MS = 3_000L
|
||||||
@@ -125,9 +120,10 @@ fun DeviceTabScreen(
|
|||||||
nativeCore: NativeCoreFacade,
|
nativeCore: NativeCoreFacade,
|
||||||
snack: SnackbarHostState,
|
snack: SnackbarHostState,
|
||||||
scrollBehavior: ScrollBehavior,
|
scrollBehavior: ScrollBehavior,
|
||||||
virtualButtonsOutside: List<String>,
|
virtualButtonsLayout: String,
|
||||||
virtualButtonsInMore: List<String>,
|
showPreviewVirtualButtonText: Boolean,
|
||||||
previewCardHeightDp: Int,
|
previewCardHeightDp: Int,
|
||||||
|
themeBaseIndex: Int,
|
||||||
customServerUri: String?,
|
customServerUri: String?,
|
||||||
serverRemotePath: String,
|
serverRemotePath: String,
|
||||||
onServerRemotePathChange: (String) -> Unit,
|
onServerRemotePathChange: (String) -> Unit,
|
||||||
@@ -218,8 +214,8 @@ fun DeviceTabScreen(
|
|||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val haptics = rememberAppHaptics()
|
val haptics = rememberAppHaptics()
|
||||||
val virtualButtonLayout = remember(virtualButtonsOutside, virtualButtonsInMore) {
|
val virtualButtonLayout = remember(virtualButtonsLayout) {
|
||||||
VirtualButtonActions.resolveLayout(virtualButtonsOutside, virtualButtonsInMore)
|
VirtualButtonActions.splitLayout(VirtualButtonActions.parseStoredLayout(virtualButtonsLayout))
|
||||||
}
|
}
|
||||||
val initialSettings = remember(context) { loadDevicePageSettings(context) }
|
val initialSettings = remember(context) { loadDevicePageSettings(context) }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -945,34 +941,33 @@ fun DeviceTabScreen(
|
|||||||
|
|
||||||
SuperBottomSheet(
|
SuperBottomSheet(
|
||||||
show = showReorderSheet,
|
show = showReorderSheet,
|
||||||
title = "调整设备排序",
|
title = "快速设备排序",
|
||||||
onDismissRequest = { showReorderSheet = false },
|
onDismissRequest = { showReorderSheet = false },
|
||||||
) {
|
) {
|
||||||
SortableCardList(
|
val list = remember {
|
||||||
title = "设备列表",
|
ReorderableList(
|
||||||
items = quickDevices.map { device ->
|
{
|
||||||
SortableCardItem(
|
quickDevices.map { device ->
|
||||||
id = device.id,
|
ReorderableList.Item(
|
||||||
title = device.name.ifBlank { device.host },
|
id = device.id,
|
||||||
subtitle = "${device.host}:${device.port}",
|
title = device.name.ifBlank { device.host },
|
||||||
)
|
subtitle = "${device.host}:${device.port}",
|
||||||
},
|
)
|
||||||
modifier = Modifier.padding(horizontal = UiSpacing.CardContent),
|
}
|
||||||
transferDirection = SortTransferDirection.NONE,
|
},
|
||||||
onLongPressHaptic = { haptics.press() },
|
onSettle = { fromIndex, toIndex ->
|
||||||
onDrop = { payload: SortDropPayload ->
|
if (fromIndex < 0) return@ReorderableList
|
||||||
val fromIndex = quickDevices.indexOfFirst { it.id == payload.itemId }
|
val to = toIndex.coerceIn(0, quickDevices.size)
|
||||||
if (fromIndex < 0) return@SortableCardList
|
if (fromIndex == to) return@ReorderableList
|
||||||
val steps = (payload.deltaY / 54f).roundToInt()
|
|
||||||
if (steps == 0) return@SortableCardList
|
val moved = quickDevices.removeAt(fromIndex)
|
||||||
val toIndex = (fromIndex + steps).coerceIn(0, quickDevices.lastIndex)
|
quickDevices.add(to.coerceIn(0, quickDevices.size), moved)
|
||||||
if (toIndex == fromIndex) return@SortableCardList
|
saveQuickDevices(context, quickDevices)
|
||||||
val moved = quickDevices.removeAt(fromIndex)
|
},
|
||||||
quickDevices.add(toIndex, moved)
|
)
|
||||||
saveQuickDevices(context, quickDevices)
|
}
|
||||||
},
|
list()
|
||||||
)
|
Spacer(Modifier.height(UiSpacing.BottomSheetBottom))
|
||||||
Spacer(Modifier.height(UiSpacing.Large))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendVirtualButtonAction(action: VirtualButtonAction) {
|
fun sendVirtualButtonAction(action: VirtualButtonAction) {
|
||||||
@@ -1022,6 +1017,7 @@ fun DeviceTabScreen(
|
|||||||
sessionInfo = sessionInfo,
|
sessionInfo = sessionInfo,
|
||||||
busyLabel = null,
|
busyLabel = null,
|
||||||
connectedDeviceLabel = connectedDeviceLabel,
|
connectedDeviceLabel = connectedDeviceLabel,
|
||||||
|
themeBaseIndex = themeBaseIndex,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1043,7 +1039,7 @@ fun DeviceTabScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onAction = {
|
onAction = {
|
||||||
haptics.press()
|
haptics.contextClick()
|
||||||
if (isConnectedTarget) {
|
if (isConnectedTarget) {
|
||||||
activeDeviceActionId = device.id
|
activeDeviceActionId = device.id
|
||||||
runAdbConnect("断开 ADB", onFinished = { activeDeviceActionId = null }) {
|
runAdbConnect("断开 ADB", onFinished = { activeDeviceActionId = null }) {
|
||||||
@@ -1114,7 +1110,7 @@ fun DeviceTabScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
SectionSmallTitle(text = "无线配对")
|
SectionSmallTitle("无线配对")
|
||||||
// "使用配对码配对设备"
|
// "使用配对码配对设备"
|
||||||
PairingCard(
|
PairingCard(
|
||||||
busy = busy,
|
busy = busy,
|
||||||
@@ -1167,7 +1163,7 @@ fun DeviceTabScreen(
|
|||||||
audioCodec = audioCodec,
|
audioCodec = audioCodec,
|
||||||
onAudioCodecChange = onAudioCodecChange,
|
onAudioCodecChange = onAudioCodecChange,
|
||||||
onOpenAdvanced = onOpenAdvancedPage,
|
onOpenAdvanced = onOpenAdvancedPage,
|
||||||
onStartStopHaptic = { haptics.press() },
|
onStartStopHaptic = { haptics.contextClick() },
|
||||||
onStart = {
|
onStart = {
|
||||||
runBusy("启动 scrcpy") {
|
runBusy("启动 scrcpy") {
|
||||||
if (noVideo && !audioEnabled) {
|
if (noVideo && !audioEnabled) {
|
||||||
@@ -1316,17 +1312,13 @@ fun DeviceTabScreen(
|
|||||||
previewHeightDp = previewCardHeightDp.coerceAtLeast(120),
|
previewHeightDp = previewCardHeightDp.coerceAtLeast(120),
|
||||||
controlsVisible = previewControlsVisible,
|
controlsVisible = previewControlsVisible,
|
||||||
onTapped = {
|
onTapped = {
|
||||||
previewControlsVisible = true
|
previewControlsVisible = !previewControlsVisible
|
||||||
scope.launch {
|
|
||||||
delay(2000)
|
|
||||||
previewControlsVisible = false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onOpenFullscreen = {
|
onOpenFullscreen = {
|
||||||
val info = sessionInfo ?: return@PreviewCard
|
val info = sessionInfo ?: return@PreviewCard
|
||||||
onOpenFullscreenPage(info)
|
onOpenFullscreenPage(info)
|
||||||
},
|
},
|
||||||
onOpenFullscreenHaptic = { haptics.press() },
|
onOpenFullscreenHaptic = { haptics.contextClick() },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
@@ -1334,8 +1326,7 @@ fun DeviceTabScreen(
|
|||||||
busy = busy,
|
busy = busy,
|
||||||
outsideActions = virtualButtonLayout.first,
|
outsideActions = virtualButtonLayout.first,
|
||||||
moreActions = virtualButtonLayout.second,
|
moreActions = virtualButtonLayout.second,
|
||||||
onPressHaptic = { haptics.press() },
|
showText = showPreviewVirtualButtonText,
|
||||||
onConfirmHaptic = { haptics.confirm() },
|
|
||||||
onAction = ::sendVirtualButtonAction,
|
onAction = ::sendVirtualButtonAction,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,8 +38,7 @@ data class FullscreenControlLaunch(
|
|||||||
fun FullscreenControlPage(
|
fun FullscreenControlPage(
|
||||||
launch: FullscreenControlLaunch,
|
launch: FullscreenControlLaunch,
|
||||||
nativeCore: NativeCoreFacade,
|
nativeCore: NativeCoreFacade,
|
||||||
virtualButtonsOutside: List<String>,
|
virtualButtonsLayout: String,
|
||||||
virtualButtonsInMore: List<String>,
|
|
||||||
showDebugInfo: Boolean,
|
showDebugInfo: Boolean,
|
||||||
showVirtualButtons: Boolean,
|
showVirtualButtons: Boolean,
|
||||||
onVideoSizeChanged: (width: Int, height: Int) -> Unit,
|
onVideoSizeChanged: (width: Int, height: Int) -> Unit,
|
||||||
@@ -51,8 +50,8 @@ fun FullscreenControlPage(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val haptics = rememberAppHaptics()
|
val haptics = rememberAppHaptics()
|
||||||
val activity = remember(context) { context as? Activity }
|
val activity = remember(context) { context as? Activity }
|
||||||
val virtualButtonLayout = remember(virtualButtonsOutside, virtualButtonsInMore) {
|
val virtualButtonLayout = remember(virtualButtonsLayout) {
|
||||||
VirtualButtonActions.resolveLayout(virtualButtonsOutside, virtualButtonsInMore)
|
VirtualButtonActions.splitLayout(VirtualButtonActions.parseStoredLayout(virtualButtonsLayout))
|
||||||
}
|
}
|
||||||
val bar = remember(virtualButtonLayout) {
|
val bar = remember(virtualButtonLayout) {
|
||||||
VirtualButtonBar(
|
VirtualButtonBar(
|
||||||
@@ -150,8 +149,6 @@ fun FullscreenControlPage(
|
|||||||
if (showVirtualButtons) {
|
if (showVirtualButtons) {
|
||||||
bar.Fullscreen(
|
bar.Fullscreen(
|
||||||
modifier = Modifier.align(Alignment.BottomCenter),
|
modifier = Modifier.align(Alignment.BottomCenter),
|
||||||
onPressHaptic = { haptics.press() },
|
|
||||||
onConfirmHaptic = { haptics.confirm() },
|
|
||||||
onAction = { action ->
|
onAction = { action ->
|
||||||
action.keycode?.let(::sendKeycode)
|
action.keycode?.let(::sendKeycode)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ import androidx.compose.foundation.pager.HorizontalPager
|
|||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.Devices
|
import androidx.compose.material.icons.rounded.Devices
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.rounded.MoreVert
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.rounded.Settings
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -54,7 +54,6 @@ import io.github.miuzarte.scrcpyforandroid.services.MainSettings
|
|||||||
import io.github.miuzarte.scrcpyforandroid.services.loadDevicePageSettings
|
import io.github.miuzarte.scrcpyforandroid.services.loadDevicePageSettings
|
||||||
import io.github.miuzarte.scrcpyforandroid.services.loadMainSettings
|
import io.github.miuzarte.scrcpyforandroid.services.loadMainSettings
|
||||||
import io.github.miuzarte.scrcpyforandroid.services.saveMainSettings
|
import io.github.miuzarte.scrcpyforandroid.services.saveMainSettings
|
||||||
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonActions
|
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -82,8 +81,8 @@ private enum class MainTabDestination(
|
|||||||
val label: String,
|
val label: String,
|
||||||
val icon: ImageVector,
|
val icon: ImageVector,
|
||||||
) {
|
) {
|
||||||
Device(title = "设备", label = "设备", icon = Icons.Default.Devices),
|
Device(title = "设备", label = "设备", icon = Icons.Rounded.Devices),
|
||||||
Settings(title = "设置", label = "设置", icon = Icons.Default.Settings),
|
Settings(title = "设置", label = "设置", icon = Icons.Rounded.Settings),
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed interface RootScreen : NavKey {
|
private sealed interface RootScreen : NavKey {
|
||||||
@@ -134,14 +133,10 @@ fun MainPage() {
|
|||||||
var monetEnabled by rememberSaveable { mutableStateOf(initialSettings.monetEnabled) }
|
var monetEnabled by rememberSaveable { mutableStateOf(initialSettings.monetEnabled) }
|
||||||
var fullscreenDebugInfoEnabled by rememberSaveable { mutableStateOf(initialSettings.fullscreenDebugInfoEnabled) }
|
var fullscreenDebugInfoEnabled by rememberSaveable { mutableStateOf(initialSettings.fullscreenDebugInfoEnabled) }
|
||||||
var showFullscreenVirtualButtons by rememberSaveable { mutableStateOf(initialSettings.showFullscreenVirtualButtons) }
|
var showFullscreenVirtualButtons by rememberSaveable { mutableStateOf(initialSettings.showFullscreenVirtualButtons) }
|
||||||
|
var showPreviewVirtualButtonText by rememberSaveable { mutableStateOf(initialSettings.showPreviewVirtualButtonText) }
|
||||||
var keepScreenOnWhenStreamingEnabled by rememberSaveable { mutableStateOf(initialSettings.keepScreenOnWhenStreamingEnabled) }
|
var keepScreenOnWhenStreamingEnabled by rememberSaveable { mutableStateOf(initialSettings.keepScreenOnWhenStreamingEnabled) }
|
||||||
var devicePreviewCardHeightDp by rememberSaveable { mutableIntStateOf(initialSettings.devicePreviewCardHeightDp) }
|
var devicePreviewCardHeightDp by rememberSaveable { mutableIntStateOf(initialSettings.devicePreviewCardHeightDp) }
|
||||||
var virtualButtonsOutside by rememberSaveable(stateSaver = stringListSaver) {
|
var virtualButtonsLayout by rememberSaveable { mutableStateOf(initialSettings.virtualButtonsLayout) }
|
||||||
mutableStateOf(VirtualButtonActions.parseStoredIds(initialSettings.virtualButtonsOutside))
|
|
||||||
}
|
|
||||||
var virtualButtonsInMore by rememberSaveable(stateSaver = stringListSaver) {
|
|
||||||
mutableStateOf(VirtualButtonActions.parseStoredIds(initialSettings.virtualButtonsInMore))
|
|
||||||
}
|
|
||||||
var customServerUri by rememberSaveable { mutableStateOf(initialSettings.customServerUri) }
|
var customServerUri by rememberSaveable { mutableStateOf(initialSettings.customServerUri) }
|
||||||
var serverRemotePath by rememberSaveable { mutableStateOf(initialSettings.serverRemotePath) }
|
var serverRemotePath by rememberSaveable { mutableStateOf(initialSettings.serverRemotePath) }
|
||||||
var adbKeyName by rememberSaveable { mutableStateOf(initialSettings.adbKeyName) }
|
var adbKeyName by rememberSaveable { mutableStateOf(initialSettings.adbKeyName) }
|
||||||
@@ -241,10 +236,10 @@ fun MainPage() {
|
|||||||
monetEnabled,
|
monetEnabled,
|
||||||
fullscreenDebugInfoEnabled,
|
fullscreenDebugInfoEnabled,
|
||||||
showFullscreenVirtualButtons,
|
showFullscreenVirtualButtons,
|
||||||
|
showPreviewVirtualButtonText,
|
||||||
keepScreenOnWhenStreamingEnabled,
|
keepScreenOnWhenStreamingEnabled,
|
||||||
devicePreviewCardHeightDp,
|
devicePreviewCardHeightDp,
|
||||||
virtualButtonsOutside,
|
virtualButtonsLayout,
|
||||||
virtualButtonsInMore,
|
|
||||||
customServerUri,
|
customServerUri,
|
||||||
serverRemotePath,
|
serverRemotePath,
|
||||||
adbKeyName,
|
adbKeyName,
|
||||||
@@ -262,10 +257,10 @@ fun MainPage() {
|
|||||||
monetEnabled = monetEnabled,
|
monetEnabled = monetEnabled,
|
||||||
fullscreenDebugInfoEnabled = fullscreenDebugInfoEnabled,
|
fullscreenDebugInfoEnabled = fullscreenDebugInfoEnabled,
|
||||||
showFullscreenVirtualButtons = showFullscreenVirtualButtons,
|
showFullscreenVirtualButtons = showFullscreenVirtualButtons,
|
||||||
|
showPreviewVirtualButtonText = showPreviewVirtualButtonText,
|
||||||
keepScreenOnWhenStreamingEnabled = keepScreenOnWhenStreamingEnabled,
|
keepScreenOnWhenStreamingEnabled = keepScreenOnWhenStreamingEnabled,
|
||||||
devicePreviewCardHeightDp = devicePreviewCardHeightDp,
|
devicePreviewCardHeightDp = devicePreviewCardHeightDp,
|
||||||
virtualButtonsOutside = VirtualButtonActions.encodeStoredIds(virtualButtonsOutside),
|
virtualButtonsLayout = virtualButtonsLayout,
|
||||||
virtualButtonsInMore = VirtualButtonActions.encodeStoredIds(virtualButtonsInMore),
|
|
||||||
customServerUri = customServerUri,
|
customServerUri = customServerUri,
|
||||||
serverRemotePath = serverRemotePath,
|
serverRemotePath = serverRemotePath,
|
||||||
adbKeyName = adbKeyName,
|
adbKeyName = adbKeyName,
|
||||||
@@ -398,7 +393,7 @@ fun MainPage() {
|
|||||||
holdDownState = showDeviceMenu,
|
holdDownState = showDeviceMenu,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.MoreVert,
|
Icons.Rounded.MoreVert,
|
||||||
contentDescription = "更多"
|
contentDescription = "更多"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -429,8 +424,10 @@ fun MainPage() {
|
|||||||
nativeCore = nativeCore,
|
nativeCore = nativeCore,
|
||||||
snack = snackHostState,
|
snack = snackHostState,
|
||||||
scrollBehavior = deviceScrollBehavior,
|
scrollBehavior = deviceScrollBehavior,
|
||||||
virtualButtonsOutside = virtualButtonsOutside,
|
virtualButtonsLayout = virtualButtonsLayout,
|
||||||
virtualButtonsInMore = virtualButtonsInMore,
|
showPreviewVirtualButtonText = showPreviewVirtualButtonText,
|
||||||
|
previewCardHeightDp = devicePreviewCardHeightDp,
|
||||||
|
themeBaseIndex = themeBaseIndex,
|
||||||
customServerUri = customServerUri,
|
customServerUri = customServerUri,
|
||||||
serverRemotePath = serverRemotePath,
|
serverRemotePath = serverRemotePath,
|
||||||
onServerRemotePathChange = { serverRemotePath = it },
|
onServerRemotePathChange = { serverRemotePath = it },
|
||||||
@@ -556,7 +553,6 @@ fun MainPage() {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
previewCardHeightDp = devicePreviewCardHeightDp,
|
|
||||||
adbPairingAutoDiscoverOnDialogOpen = adbPairingAutoDiscoverOnDialogOpen,
|
adbPairingAutoDiscoverOnDialogOpen = adbPairingAutoDiscoverOnDialogOpen,
|
||||||
adbAutoReconnectPairedDevice = adbAutoReconnectPairedDevice,
|
adbAutoReconnectPairedDevice = adbAutoReconnectPairedDevice,
|
||||||
adbMdnsLanDiscoveryEnabled = adbMdnsLanDiscoveryEnabled,
|
adbMdnsLanDiscoveryEnabled = adbMdnsLanDiscoveryEnabled,
|
||||||
@@ -589,6 +585,12 @@ fun MainPage() {
|
|||||||
onDevicePreviewCardHeightDpChange = {
|
onDevicePreviewCardHeightDpChange = {
|
||||||
devicePreviewCardHeightDp = it.coerceAtLeast(120)
|
devicePreviewCardHeightDp = it.coerceAtLeast(120)
|
||||||
},
|
},
|
||||||
|
onOpenReorderDevices = {
|
||||||
|
openReorderDevicesAction?.invoke()
|
||||||
|
},
|
||||||
|
onOpenVirtualButtonOrder = {
|
||||||
|
rootBackStack.add(RootScreen.VirtualButtonOrder)
|
||||||
|
},
|
||||||
showFullscreenVirtualButtons = showFullscreenVirtualButtons,
|
showFullscreenVirtualButtons = showFullscreenVirtualButtons,
|
||||||
onShowFullscreenVirtualButtonsChange = {
|
onShowFullscreenVirtualButtonsChange = {
|
||||||
showFullscreenVirtualButtons = it
|
showFullscreenVirtualButtons = it
|
||||||
@@ -756,12 +758,12 @@ fun MainPage() {
|
|||||||
VirtualButtonOrderPage(
|
VirtualButtonOrderPage(
|
||||||
contentPadding = pagePadding,
|
contentPadding = pagePadding,
|
||||||
scrollBehavior = advancedScrollBehavior,
|
scrollBehavior = advancedScrollBehavior,
|
||||||
outsideIds = virtualButtonsOutside,
|
layoutString = virtualButtonsLayout,
|
||||||
moreIds = virtualButtonsInMore,
|
onLayoutChange = { layout ->
|
||||||
onLayoutChange = { outside, more ->
|
virtualButtonsLayout = layout
|
||||||
virtualButtonsOutside = outside
|
|
||||||
virtualButtonsInMore = more
|
|
||||||
},
|
},
|
||||||
|
showPreviewText = showPreviewVirtualButtonText,
|
||||||
|
onShowPreviewTextChange = { showPreviewVirtualButtonText = it },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -770,8 +772,7 @@ fun MainPage() {
|
|||||||
FullscreenControlPage(
|
FullscreenControlPage(
|
||||||
launch = screen.launch,
|
launch = screen.launch,
|
||||||
nativeCore = nativeCore,
|
nativeCore = nativeCore,
|
||||||
virtualButtonsOutside = virtualButtonsOutside,
|
virtualButtonsLayout = virtualButtonsLayout,
|
||||||
virtualButtonsInMore = virtualButtonsInMore,
|
|
||||||
showDebugInfo = fullscreenDebugInfoEnabled,
|
showDebugInfo = fullscreenDebugInfoEnabled,
|
||||||
showVirtualButtons = showFullscreenVirtualButtons,
|
showVirtualButtons = showFullscreenVirtualButtons,
|
||||||
onVideoSizeChanged = { width, height ->
|
onVideoSizeChanged = { width, height ->
|
||||||
@@ -817,7 +818,7 @@ private fun DeviceMenuPopup(
|
|||||||
) {
|
) {
|
||||||
ListPopupColumn {
|
ListPopupColumn {
|
||||||
DeviceMenuPopupItem(
|
DeviceMenuPopupItem(
|
||||||
text = "调整设备排序",
|
text = "快速设备排序",
|
||||||
optionSize = 3,
|
optionSize = 3,
|
||||||
index = 0,
|
index = 0,
|
||||||
onClick = onReorderDevices,
|
onClick = onReorderDevices,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package io.github.miuzarte.scrcpyforandroid.pages
|
package io.github.miuzarte.scrcpyforandroid.pages
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -7,11 +8,13 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Clear
|
import androidx.compose.material.icons.rounded.Clear
|
||||||
import androidx.compose.material.icons.filled.FolderOpen
|
import androidx.compose.material.icons.rounded.FileOpen
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.core.net.toUri
|
||||||
import io.github.miuzarte.scrcpyforandroid.constants.AppDefaults
|
import io.github.miuzarte.scrcpyforandroid.constants.AppDefaults
|
||||||
import io.github.miuzarte.scrcpyforandroid.constants.UiSpacing
|
import io.github.miuzarte.scrcpyforandroid.constants.UiSpacing
|
||||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.AppPageLazyColumn
|
import io.github.miuzarte.scrcpyforandroid.scaffolds.AppPageLazyColumn
|
||||||
@@ -23,6 +26,7 @@ import top.yukonga.miuix.kmp.basic.IconButton
|
|||||||
import top.yukonga.miuix.kmp.basic.ScrollBehavior
|
import top.yukonga.miuix.kmp.basic.ScrollBehavior
|
||||||
import top.yukonga.miuix.kmp.basic.Text
|
import top.yukonga.miuix.kmp.basic.Text
|
||||||
import top.yukonga.miuix.kmp.basic.TextField
|
import top.yukonga.miuix.kmp.basic.TextField
|
||||||
|
import top.yukonga.miuix.kmp.extra.SuperArrow
|
||||||
import top.yukonga.miuix.kmp.extra.SuperDropdown
|
import top.yukonga.miuix.kmp.extra.SuperDropdown
|
||||||
import top.yukonga.miuix.kmp.extra.SuperSwitch
|
import top.yukonga.miuix.kmp.extra.SuperSwitch
|
||||||
import top.yukonga.miuix.kmp.theme.ColorSchemeMode
|
import top.yukonga.miuix.kmp.theme.ColorSchemeMode
|
||||||
@@ -66,6 +70,8 @@ fun SettingsScreen(
|
|||||||
devicePreviewCardHeightDp: Int,
|
devicePreviewCardHeightDp: Int,
|
||||||
onDevicePreviewCardHeightDpChange: (Int) -> Unit,
|
onDevicePreviewCardHeightDpChange: (Int) -> Unit,
|
||||||
showFullscreenVirtualButtons: Boolean,
|
showFullscreenVirtualButtons: Boolean,
|
||||||
|
onOpenReorderDevices: () -> Unit,
|
||||||
|
onOpenVirtualButtonOrder: () -> Unit,
|
||||||
onShowFullscreenVirtualButtonsChange: (Boolean) -> Unit,
|
onShowFullscreenVirtualButtonsChange: (Boolean) -> Unit,
|
||||||
customServerUri: String?,
|
customServerUri: String?,
|
||||||
onPickServer: () -> Unit,
|
onPickServer: () -> Unit,
|
||||||
@@ -81,6 +87,7 @@ fun SettingsScreen(
|
|||||||
scrollBehavior: ScrollBehavior,
|
scrollBehavior: ScrollBehavior,
|
||||||
) {
|
) {
|
||||||
val baseModeItems = THEME_BASE_OPTIONS.map { it.label }
|
val baseModeItems = THEME_BASE_OPTIONS.map { it.label }
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
// 设置
|
// 设置
|
||||||
AppPageLazyColumn(
|
AppPageLazyColumn(
|
||||||
@@ -88,7 +95,7 @@ fun SettingsScreen(
|
|||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
SectionSmallTitle(text = "主题")
|
SectionSmallTitle("主题")
|
||||||
Card {
|
Card {
|
||||||
SuperDropdown(
|
SuperDropdown(
|
||||||
title = "外观模式",
|
title = "外观模式",
|
||||||
@@ -105,7 +112,7 @@ fun SettingsScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SectionSmallTitle(text = "投屏")
|
SectionSmallTitle("投屏")
|
||||||
Card {
|
Card {
|
||||||
SuperSwitch(
|
SuperSwitch(
|
||||||
title = "启用调试信息",
|
title = "启用调试信息",
|
||||||
@@ -114,8 +121,8 @@ fun SettingsScreen(
|
|||||||
onCheckedChange = onFullscreenDebugInfoEnabledChange,
|
onCheckedChange = onFullscreenDebugInfoEnabledChange,
|
||||||
)
|
)
|
||||||
SuperSwitch(
|
SuperSwitch(
|
||||||
title = "投屏时不允许息屏",
|
title = "投屏时保持屏幕常亮",
|
||||||
summary = "Scrcpy 启动后保持本机常亮,避免锁屏导致 ADB 断开",
|
summary = "Scrcpy 启动后保持本机屏幕常亮,避免锁屏导致 ADB 断开",
|
||||||
checked = keepScreenOnWhenStreamingEnabled,
|
checked = keepScreenOnWhenStreamingEnabled,
|
||||||
onCheckedChange = onKeepScreenOnWhenStreamingEnabledChange,
|
onCheckedChange = onKeepScreenOnWhenStreamingEnabledChange,
|
||||||
)
|
)
|
||||||
@@ -140,6 +147,16 @@ fun SettingsScreen(
|
|||||||
?.let { onDevicePreviewCardHeightDpChange(it.coerceAtLeast(120)) }
|
?.let { onDevicePreviewCardHeightDpChange(it.coerceAtLeast(120)) }
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
SuperArrow(
|
||||||
|
title = "快速设备排序",
|
||||||
|
summary = "手动排序设备页的快速设备",
|
||||||
|
onClick = onOpenReorderDevices,
|
||||||
|
)
|
||||||
|
SuperArrow(
|
||||||
|
title = "虚拟按钮排序",
|
||||||
|
summary = "手动排序预览/全屏时的虚拟按钮,并选择哪些按钮展示在外",
|
||||||
|
onClick = onOpenVirtualButtonOrder,
|
||||||
|
)
|
||||||
SuperSwitch(
|
SuperSwitch(
|
||||||
title = "全屏显示虚拟按钮",
|
title = "全屏显示虚拟按钮",
|
||||||
summary = "在全屏控制页底部显示返回键、主页键等虚拟按钮",
|
summary = "在全屏控制页底部显示返回键、主页键等虚拟按钮",
|
||||||
@@ -148,7 +165,7 @@ fun SettingsScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SectionSmallTitle(text = "scrcpy-server")
|
SectionSmallTitle("scrcpy-server")
|
||||||
Card {
|
Card {
|
||||||
Spacer(modifier = Modifier.padding(top = UiSpacing.CardContent))
|
Spacer(modifier = Modifier.padding(top = UiSpacing.CardContent))
|
||||||
Text(
|
Text(
|
||||||
@@ -171,10 +188,10 @@ fun SettingsScreen(
|
|||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
Row(modifier = Modifier.padding(end = UiSpacing.SectionTitleLeadingGap)) {
|
Row(modifier = Modifier.padding(end = UiSpacing.SectionTitleLeadingGap)) {
|
||||||
if (customServerUri != null) IconButton(onClick = onClearServer) {
|
if (customServerUri != null) IconButton(onClick = onClearServer) {
|
||||||
Icon(Icons.Default.Clear, contentDescription = "清空")
|
Icon(Icons.Rounded.Clear, contentDescription = "清空")
|
||||||
}
|
}
|
||||||
IconButton(onClick = onPickServer) {
|
IconButton(onClick = onPickServer) {
|
||||||
Icon(Icons.Default.FolderOpen, contentDescription = "选择文件")
|
Icon(Icons.Rounded.FileOpen, contentDescription = "选择文件")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -199,7 +216,7 @@ fun SettingsScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SectionSmallTitle(text = "ADB")
|
SectionSmallTitle("ADB")
|
||||||
Card {
|
Card {
|
||||||
Text(
|
Text(
|
||||||
text = "自定义 ADB 密钥名",
|
text = "自定义 ADB 密钥名",
|
||||||
@@ -232,6 +249,21 @@ fun SettingsScreen(
|
|||||||
onCheckedChange = onAdbAutoReconnectPairedDeviceChange,
|
onCheckedChange = onAdbAutoReconnectPairedDeviceChange,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SectionSmallTitle("关于")
|
||||||
|
Card {
|
||||||
|
SuperArrow(
|
||||||
|
title = "前往仓库",
|
||||||
|
summary = "github.com/Miuzarte/ScrcpyForAndroid",
|
||||||
|
onClick = {
|
||||||
|
val intent = Intent(
|
||||||
|
Intent.ACTION_VIEW,
|
||||||
|
"https://github.com/Miuzarte/ScrcpyForAndroid".toUri()
|
||||||
|
)
|
||||||
|
context.startActivity(intent)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 放进 [AppPageLazyColumn] 里
|
// TODO: 放进 [AppPageLazyColumn] 里
|
||||||
|
|||||||
@@ -1,169 +1,86 @@
|
|||||||
package io.github.miuzarte.scrcpyforandroid.pages
|
package io.github.miuzarte.scrcpyforandroid.pages
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.toMutableStateList
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import io.github.miuzarte.scrcpyforandroid.constants.UiSpacing
|
|
||||||
import io.github.miuzarte.scrcpyforandroid.haptics.rememberAppHaptics
|
|
||||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.AppPageLazyColumn
|
import io.github.miuzarte.scrcpyforandroid.scaffolds.AppPageLazyColumn
|
||||||
import io.github.miuzarte.scrcpyforandroid.widgets.SortDropPayload
|
import io.github.miuzarte.scrcpyforandroid.widgets.ReorderableList
|
||||||
import io.github.miuzarte.scrcpyforandroid.widgets.SortTransferDirection
|
|
||||||
import io.github.miuzarte.scrcpyforandroid.widgets.SortableCardItem
|
|
||||||
import io.github.miuzarte.scrcpyforandroid.widgets.SortableCardList
|
|
||||||
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonAction
|
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonAction
|
||||||
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonActions
|
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonActions
|
||||||
import top.yukonga.miuix.kmp.basic.Card
|
import top.yukonga.miuix.kmp.basic.Card
|
||||||
import top.yukonga.miuix.kmp.basic.ScrollBehavior
|
import top.yukonga.miuix.kmp.basic.ScrollBehavior
|
||||||
import top.yukonga.miuix.kmp.basic.Text
|
import top.yukonga.miuix.kmp.extra.SuperSwitch
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
private const val LIST_REORDER_STEP_PX = 54f
|
|
||||||
private const val LIST_TRANSFER_STEP_PX = 72f
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun VirtualButtonOrderPage(
|
internal fun VirtualButtonOrderPage(
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
scrollBehavior: ScrollBehavior,
|
scrollBehavior: ScrollBehavior,
|
||||||
outsideIds: List<String>,
|
layoutString: String,
|
||||||
moreIds: List<String>,
|
onLayoutChange: (String) -> Unit,
|
||||||
onLayoutChange: (outside: List<String>, more: List<String>) -> Unit,
|
showPreviewText: Boolean,
|
||||||
|
onShowPreviewTextChange: (Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
val haptics = rememberAppHaptics()
|
var buttonItems by remember(layoutString) {
|
||||||
val normalized = remember(outsideIds, moreIds) {
|
mutableStateOf(VirtualButtonActions.parseStoredLayout(layoutString))
|
||||||
VirtualButtonActions.resolveLayout(outsideIds, moreIds)
|
|
||||||
}
|
|
||||||
val outsideState = remember { normalized.first.map { it.id }.toMutableStateList() }
|
|
||||||
val moreState = remember { normalized.second.map { it.id }.toMutableStateList() }
|
|
||||||
|
|
||||||
LaunchedEffect(outsideIds, moreIds) {
|
|
||||||
val resolved = VirtualButtonActions.resolveLayout(outsideIds, moreIds)
|
|
||||||
outsideState.clear()
|
|
||||||
outsideState.addAll(resolved.first.map { it.id })
|
|
||||||
moreState.clear()
|
|
||||||
moreState.addAll(resolved.second.map { it.id })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun emit() {
|
fun emitChanges() {
|
||||||
onLayoutChange(outsideState.toList(), moreState.toList())
|
onLayoutChange(VirtualButtonActions.encodeStoredLayout(buttonItems))
|
||||||
}
|
|
||||||
|
|
||||||
fun reorderInside(
|
|
||||||
list: androidx.compose.runtime.snapshots.SnapshotStateList<String>,
|
|
||||||
itemId: String,
|
|
||||||
deltaY: Float
|
|
||||||
) {
|
|
||||||
val fromIndex = list.indexOf(itemId)
|
|
||||||
if (fromIndex < 0) return
|
|
||||||
|
|
||||||
val steps = (deltaY / LIST_REORDER_STEP_PX).roundToInt()
|
|
||||||
if (steps == 0) return
|
|
||||||
|
|
||||||
val toIndex = (fromIndex + steps).coerceIn(0, list.lastIndex)
|
|
||||||
if (toIndex == fromIndex) return
|
|
||||||
|
|
||||||
val moved = list.removeAt(fromIndex)
|
|
||||||
list.add(toIndex, moved)
|
|
||||||
emit()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun transferToOther(
|
|
||||||
from: androidx.compose.runtime.snapshots.SnapshotStateList<String>,
|
|
||||||
to: androidx.compose.runtime.snapshots.SnapshotStateList<String>,
|
|
||||||
itemId: String,
|
|
||||||
deltaY: Float,
|
|
||||||
) {
|
|
||||||
val fromIndex = from.indexOf(itemId)
|
|
||||||
if (fromIndex < 0) return
|
|
||||||
|
|
||||||
val steps = (deltaY / LIST_REORDER_STEP_PX).roundToInt()
|
|
||||||
val baseIndex = fromIndex + steps
|
|
||||||
val insertIndex = baseIndex.coerceIn(0, to.size)
|
|
||||||
|
|
||||||
from.removeAt(fromIndex)
|
|
||||||
to.add(insertIndex, itemId)
|
|
||||||
emit()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun handleOutsideDrop(payload: SortDropPayload) {
|
|
||||||
val transfer =
|
|
||||||
payload.transferDirection == SortTransferDirection.TO_RIGHT && payload.deltaX >= LIST_TRANSFER_STEP_PX
|
|
||||||
if (transfer) {
|
|
||||||
if (payload.itemId == VirtualButtonAction.MORE.id) return
|
|
||||||
transferToOther(outsideState, moreState, payload.itemId, payload.deltaY)
|
|
||||||
} else {
|
|
||||||
reorderInside(outsideState, payload.itemId, payload.deltaY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun handleMoreDrop(payload: SortDropPayload) {
|
|
||||||
val transfer =
|
|
||||||
payload.transferDirection == SortTransferDirection.TO_LEFT && payload.deltaX <= -LIST_TRANSFER_STEP_PX
|
|
||||||
if (transfer) {
|
|
||||||
transferToOther(moreState, outsideState, payload.itemId, payload.deltaY)
|
|
||||||
} else {
|
|
||||||
reorderInside(moreState, payload.itemId, payload.deltaY)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AppPageLazyColumn(
|
AppPageLazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
) {
|
) {
|
||||||
|
// 按钮显示文本开关
|
||||||
item {
|
item {
|
||||||
Card(modifier = Modifier.fillMaxWidth()) {
|
Card {
|
||||||
Text(
|
SuperSwitch(
|
||||||
text = "长按可在两列间拖动排序\n“更多”不可移入右侧菜单",
|
title = "按钮显示文本",
|
||||||
modifier = Modifier.padding(UiSpacing.CardContent),
|
summary = "超过3个建议关闭,只对预览卡下方的虚拟按钮生效",
|
||||||
|
checked = showPreviewText,
|
||||||
|
onCheckedChange = onShowPreviewTextChange,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
Row(
|
ReorderableList(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
itemsProvider = {
|
||||||
) {
|
buttonItems.map { item ->
|
||||||
SortableCardList(
|
val action = item.action
|
||||||
title = "外部按钮",
|
ReorderableList.Item(
|
||||||
items = outsideState.map { id ->
|
|
||||||
val action = VirtualButtonAction.entries.first { it.id == id }
|
|
||||||
SortableCardItem(
|
|
||||||
id = action.id,
|
id = action.id,
|
||||||
title = action.title,
|
icon = action.icon,
|
||||||
subtitle = "显示在预览与全屏底部",
|
title = if (action.keycode == null) action.title else "${action.title} (${action.keycode})",
|
||||||
|
subtitle = if (item.showOutside) "显示在外部" else "显示在更多菜单内",
|
||||||
|
checked = item.showOutside,
|
||||||
|
checkboxEnabled = action != VirtualButtonAction.MORE,
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
modifier = Modifier
|
},
|
||||||
.weight(1f)
|
orientation = ReorderableList.Orientation.Column,
|
||||||
.padding(end = UiSpacing.Small),
|
onSettle = { fromIndex, toIndex ->
|
||||||
transferDirection = SortTransferDirection.TO_RIGHT,
|
buttonItems = buttonItems.toMutableList().apply {
|
||||||
onLongPressHaptic = haptics.press,
|
add(toIndex, removeAt(fromIndex))
|
||||||
onDrop = ::handleOutsideDrop,
|
}
|
||||||
)
|
emitChanges()
|
||||||
|
},
|
||||||
SortableCardList(
|
showCheckbox = true,
|
||||||
title = "更多菜单",
|
onCheckboxChange = { id, checked ->
|
||||||
items = moreState.map { id ->
|
buttonItems = buttonItems.map { item ->
|
||||||
val action = VirtualButtonAction.entries.first { it.id == id }
|
if (item.action.id == id) {
|
||||||
SortableCardItem(
|
item.copy(showOutside = checked)
|
||||||
id = action.id,
|
} else {
|
||||||
title = action.title,
|
item
|
||||||
subtitle = "显示在“更多”弹窗中",
|
}
|
||||||
)
|
}
|
||||||
},
|
emitChanges()
|
||||||
modifier = Modifier
|
},
|
||||||
.weight(1f)
|
)()
|
||||||
.padding(start = UiSpacing.Small),
|
|
||||||
transferDirection = SortTransferDirection.TO_LEFT,
|
|
||||||
onLongPressHaptic = haptics.press,
|
|
||||||
onDrop = ::handleMoreDrop,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
@@ -22,6 +24,7 @@ fun AppPageLazyColumn(
|
|||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
scrollBehavior: ScrollBehavior,
|
scrollBehavior: ScrollBehavior,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
state: LazyListState = rememberLazyListState(),
|
||||||
itemSpacing: Dp = UiSpacing.PageItem,
|
itemSpacing: Dp = UiSpacing.PageItem,
|
||||||
horizontalPadding: Dp = UiSpacing.PageHorizontal,
|
horizontalPadding: Dp = UiSpacing.PageHorizontal,
|
||||||
verticalPadding: Dp = UiSpacing.PageVertical,
|
verticalPadding: Dp = UiSpacing.PageVertical,
|
||||||
@@ -44,6 +47,7 @@ fun AppPageLazyColumn(
|
|||||||
.overScrollVertical()
|
.overScrollVertical()
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||||
.padding(contentPadding),
|
.padding(contentPadding),
|
||||||
|
state = state,
|
||||||
contentPadding = PaddingValues(horizontal = horizontalPadding, vertical = verticalPadding),
|
contentPadding = PaddingValues(horizontal = horizontalPadding, vertical = verticalPadding),
|
||||||
verticalArrangement = Arrangement.spacedBy(itemSpacing),
|
verticalArrangement = Arrangement.spacedBy(itemSpacing),
|
||||||
content = content,
|
content = content,
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ internal data class MainSettings(
|
|||||||
val monetEnabled: Boolean = AppDefaults.MONET,
|
val monetEnabled: Boolean = AppDefaults.MONET,
|
||||||
val fullscreenDebugInfoEnabled: Boolean = AppDefaults.FULLSCREEN_DEBUG_INFO,
|
val fullscreenDebugInfoEnabled: Boolean = AppDefaults.FULLSCREEN_DEBUG_INFO,
|
||||||
val showFullscreenVirtualButtons: Boolean = AppDefaults.SHOW_FULLSCREEN_VIRTUAL_BUTTONS,
|
val showFullscreenVirtualButtons: Boolean = AppDefaults.SHOW_FULLSCREEN_VIRTUAL_BUTTONS,
|
||||||
|
val showPreviewVirtualButtonText: Boolean = AppDefaults.PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT,
|
||||||
val keepScreenOnWhenStreamingEnabled: Boolean = AppDefaults.KEEP_SCREEN_ON_WHEN_STREAMING,
|
val keepScreenOnWhenStreamingEnabled: Boolean = AppDefaults.KEEP_SCREEN_ON_WHEN_STREAMING,
|
||||||
val devicePreviewCardHeightDp: Int = AppDefaults.DEVICE_PREVIEW_CARD_HEIGHT_DP,
|
val devicePreviewCardHeightDp: Int = AppDefaults.DEVICE_PREVIEW_CARD_HEIGHT_DP,
|
||||||
val virtualButtonsOutside: String = AppDefaults.VIRTUAL_BUTTONS_OUTSIDE,
|
val virtualButtonsLayout: String = AppDefaults.VIRTUAL_BUTTONS_LAYOUT,
|
||||||
val virtualButtonsInMore: String = AppDefaults.VIRTUAL_BUTTONS_IN_MORE,
|
|
||||||
val customServerUri: String? = AppDefaults.CUSTOM_SERVER_URI,
|
val customServerUri: String? = AppDefaults.CUSTOM_SERVER_URI,
|
||||||
val serverRemotePath: String = AppDefaults.SERVER_REMOTE_PATH_INPUT,
|
val serverRemotePath: String = AppDefaults.SERVER_REMOTE_PATH_INPUT,
|
||||||
val adbKeyName: String = AppDefaults.ADB_KEY_NAME_INPUT,
|
val adbKeyName: String = AppDefaults.ADB_KEY_NAME_INPUT,
|
||||||
@@ -101,6 +101,10 @@ internal fun loadMainSettings(context: Context): MainSettings {
|
|||||||
AppPreferenceKeys.SHOW_FULLSCREEN_VIRTUAL_BUTTONS,
|
AppPreferenceKeys.SHOW_FULLSCREEN_VIRTUAL_BUTTONS,
|
||||||
AppDefaults.SHOW_FULLSCREEN_VIRTUAL_BUTTONS,
|
AppDefaults.SHOW_FULLSCREEN_VIRTUAL_BUTTONS,
|
||||||
),
|
),
|
||||||
|
showPreviewVirtualButtonText = prefs.getBoolean(
|
||||||
|
AppPreferenceKeys.PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT,
|
||||||
|
AppDefaults.PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT,
|
||||||
|
),
|
||||||
keepScreenOnWhenStreamingEnabled = prefs.getBoolean(
|
keepScreenOnWhenStreamingEnabled = prefs.getBoolean(
|
||||||
AppPreferenceKeys.KEEP_SCREEN_ON_WHEN_STREAMING,
|
AppPreferenceKeys.KEEP_SCREEN_ON_WHEN_STREAMING,
|
||||||
AppDefaults.KEEP_SCREEN_ON_WHEN_STREAMING,
|
AppDefaults.KEEP_SCREEN_ON_WHEN_STREAMING,
|
||||||
@@ -109,14 +113,10 @@ internal fun loadMainSettings(context: Context): MainSettings {
|
|||||||
AppPreferenceKeys.DEVICE_PREVIEW_CARD_HEIGHT_DP,
|
AppPreferenceKeys.DEVICE_PREVIEW_CARD_HEIGHT_DP,
|
||||||
AppDefaults.DEVICE_PREVIEW_CARD_HEIGHT_DP,
|
AppDefaults.DEVICE_PREVIEW_CARD_HEIGHT_DP,
|
||||||
).coerceAtLeast(120),
|
).coerceAtLeast(120),
|
||||||
virtualButtonsOutside = prefs.getString(
|
virtualButtonsLayout = prefs.getString(
|
||||||
AppPreferenceKeys.VIRTUAL_BUTTONS_OUTSIDE,
|
AppPreferenceKeys.VIRTUAL_BUTTONS_LAYOUT,
|
||||||
AppDefaults.VIRTUAL_BUTTONS_OUTSIDE,
|
AppDefaults.VIRTUAL_BUTTONS_LAYOUT,
|
||||||
).orEmpty().ifBlank { AppDefaults.VIRTUAL_BUTTONS_OUTSIDE },
|
).orEmpty().ifBlank { AppDefaults.VIRTUAL_BUTTONS_LAYOUT },
|
||||||
virtualButtonsInMore = prefs.getString(
|
|
||||||
AppPreferenceKeys.VIRTUAL_BUTTONS_IN_MORE,
|
|
||||||
AppDefaults.VIRTUAL_BUTTONS_IN_MORE,
|
|
||||||
).orEmpty().ifBlank { AppDefaults.VIRTUAL_BUTTONS_IN_MORE },
|
|
||||||
customServerUri = prefs.getString(
|
customServerUri = prefs.getString(
|
||||||
AppPreferenceKeys.CUSTOM_SERVER_URI,
|
AppPreferenceKeys.CUSTOM_SERVER_URI,
|
||||||
AppDefaults.CUSTOM_SERVER_URI
|
AppDefaults.CUSTOM_SERVER_URI
|
||||||
@@ -177,6 +177,10 @@ internal fun saveMainSettings(context: Context, settings: MainSettings) {
|
|||||||
AppPreferenceKeys.SHOW_FULLSCREEN_VIRTUAL_BUTTONS,
|
AppPreferenceKeys.SHOW_FULLSCREEN_VIRTUAL_BUTTONS,
|
||||||
settings.showFullscreenVirtualButtons,
|
settings.showFullscreenVirtualButtons,
|
||||||
)
|
)
|
||||||
|
.putBoolean(
|
||||||
|
AppPreferenceKeys.PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT,
|
||||||
|
settings.showPreviewVirtualButtonText,
|
||||||
|
)
|
||||||
.putBoolean(
|
.putBoolean(
|
||||||
AppPreferenceKeys.KEEP_SCREEN_ON_WHEN_STREAMING,
|
AppPreferenceKeys.KEEP_SCREEN_ON_WHEN_STREAMING,
|
||||||
settings.keepScreenOnWhenStreamingEnabled,
|
settings.keepScreenOnWhenStreamingEnabled,
|
||||||
@@ -186,12 +190,8 @@ internal fun saveMainSettings(context: Context, settings: MainSettings) {
|
|||||||
settings.devicePreviewCardHeightDp.coerceAtLeast(120),
|
settings.devicePreviewCardHeightDp.coerceAtLeast(120),
|
||||||
)
|
)
|
||||||
.putString(
|
.putString(
|
||||||
AppPreferenceKeys.VIRTUAL_BUTTONS_OUTSIDE,
|
AppPreferenceKeys.VIRTUAL_BUTTONS_LAYOUT,
|
||||||
settings.virtualButtonsOutside,
|
settings.virtualButtonsLayout,
|
||||||
)
|
|
||||||
.putString(
|
|
||||||
AppPreferenceKeys.VIRTUAL_BUTTONS_IN_MORE,
|
|
||||||
settings.virtualButtonsInMore,
|
|
||||||
)
|
)
|
||||||
.putString(
|
.putString(
|
||||||
AppPreferenceKeys.CUSTOM_SERVER_URI,
|
AppPreferenceKeys.CUSTOM_SERVER_URI,
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ import androidx.compose.foundation.shape.CircleShape
|
|||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.AddLink
|
import androidx.compose.material.icons.rounded.AddLink
|
||||||
import androidx.compose.material.icons.filled.Fullscreen
|
|
||||||
import androidx.compose.material.icons.rounded.CheckCircleOutline
|
import androidx.compose.material.icons.rounded.CheckCircleOutline
|
||||||
|
import androidx.compose.material.icons.rounded.Fullscreen
|
||||||
import androidx.compose.material.icons.rounded.LinkOff
|
import androidx.compose.material.icons.rounded.LinkOff
|
||||||
import androidx.compose.material.icons.rounded.Wifi
|
import androidx.compose.material.icons.rounded.Wifi
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -123,18 +123,28 @@ internal fun StatusCard(
|
|||||||
sessionInfo: ScrcpySessionInfo?,
|
sessionInfo: ScrcpySessionInfo?,
|
||||||
busyLabel: String?,
|
busyLabel: String?,
|
||||||
connectedDeviceLabel: String,
|
connectedDeviceLabel: String,
|
||||||
|
themeBaseIndex: Int,
|
||||||
) {
|
) {
|
||||||
val cleanStatusLine = normalizeStatusLine(statusLine)
|
val cleanStatusLine = normalizeStatusLine(statusLine)
|
||||||
|
|
||||||
|
// 根据应用主题设置决定是否使用深色模式
|
||||||
|
val isDarkTheme = when (themeBaseIndex) {
|
||||||
|
0 -> isSystemInDarkTheme() // 跟随系统
|
||||||
|
1 -> false // 浅色
|
||||||
|
2 -> true // 深色
|
||||||
|
else -> isSystemInDarkTheme()
|
||||||
|
}
|
||||||
|
|
||||||
val spec = when {
|
val spec = when {
|
||||||
streaming && sessionInfo != null -> {
|
streaming && sessionInfo != null -> {
|
||||||
val streamCardColor = when {
|
val streamCardColor = when {
|
||||||
isDynamicColor -> MiuixTheme.colorScheme.secondaryContainer
|
isDynamicColor -> MiuixTheme.colorScheme.secondaryContainer
|
||||||
isSystemInDarkTheme() -> Color(0xFF1A3825)
|
isDarkTheme -> Color(0xFF1A3825)
|
||||||
else -> Color(0xFFDFFAE4)
|
else -> Color(0xFFDFFAE4)
|
||||||
}
|
}
|
||||||
val streamTextColor = when {
|
val streamTextColor = when {
|
||||||
isDynamicColor -> MiuixTheme.colorScheme.onSecondaryContainer
|
isDynamicColor -> MiuixTheme.colorScheme.onSecondaryContainer
|
||||||
isSystemInDarkTheme() -> Color.White
|
isDarkTheme -> Color.White
|
||||||
else -> Color(0xFF111111)
|
else -> Color(0xFF111111)
|
||||||
}
|
}
|
||||||
val streamIconColor = if (isDynamicColor) {
|
val streamIconColor = if (isDynamicColor) {
|
||||||
@@ -300,12 +310,17 @@ internal fun PreviewCard(
|
|||||||
) {
|
) {
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
onOpenFullscreenHaptic?.invoke()
|
if (alpha > 0.1) {
|
||||||
onOpenFullscreen()
|
onOpenFullscreenHaptic?.invoke()
|
||||||
|
onOpenFullscreen()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.alpha(alpha),
|
modifier = Modifier.alpha(alpha),
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Fullscreen, contentDescription = "全屏")
|
Icon(
|
||||||
|
Icons.Rounded.Fullscreen,
|
||||||
|
contentDescription = "全屏",
|
||||||
|
)
|
||||||
Spacer(Modifier.width(UiSpacing.SectionTitleBottom))
|
Spacer(Modifier.width(UiSpacing.SectionTitleBottom))
|
||||||
Text("全屏")
|
Text("全屏")
|
||||||
}
|
}
|
||||||
@@ -320,8 +335,7 @@ internal fun VirtualButtonCard(
|
|||||||
busy: Boolean,
|
busy: Boolean,
|
||||||
outsideActions: List<VirtualButtonAction>,
|
outsideActions: List<VirtualButtonAction>,
|
||||||
moreActions: List<VirtualButtonAction>,
|
moreActions: List<VirtualButtonAction>,
|
||||||
onPressHaptic: () -> Unit,
|
showText: Boolean,
|
||||||
onConfirmHaptic: () -> Unit,
|
|
||||||
onAction: (VirtualButtonAction) -> Unit,
|
onAction: (VirtualButtonAction) -> Unit,
|
||||||
) {
|
) {
|
||||||
val bar = remember(outsideActions, moreActions) {
|
val bar = remember(outsideActions, moreActions) {
|
||||||
@@ -334,8 +348,7 @@ internal fun VirtualButtonCard(
|
|||||||
Card {
|
Card {
|
||||||
bar.Preview(
|
bar.Preview(
|
||||||
enabled = !busy,
|
enabled = !busy,
|
||||||
onPressHaptic = onPressHaptic,
|
showText = showText,
|
||||||
onConfirmHaptic = onConfirmHaptic,
|
|
||||||
onAction = onAction,
|
onAction = onAction,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -374,7 +387,7 @@ internal fun ConfigPanel(
|
|||||||
val audioBitRatePresetIndex =
|
val audioBitRatePresetIndex =
|
||||||
presetIndexFromInput(audioBitRateKbps.toString(), ScrcpyPresets.AudioBitRate)
|
presetIndexFromInput(audioBitRateKbps.toString(), ScrcpyPresets.AudioBitRate)
|
||||||
|
|
||||||
SectionSmallTitle(text = "Scrcpy")
|
SectionSmallTitle("Scrcpy")
|
||||||
Card {
|
Card {
|
||||||
SuperSwitch(
|
SuperSwitch(
|
||||||
title = "音频转发",
|
title = "音频转发",
|
||||||
@@ -1021,7 +1034,7 @@ internal fun DeviceTile(
|
|||||||
else MiuixTheme.colorScheme.surfaceContainer.copy(alpha = 0.6f),
|
else MiuixTheme.colorScheme.surfaceContainer.copy(alpha = 0.6f),
|
||||||
),
|
),
|
||||||
pressFeedbackType = PressFeedbackType.Sink,
|
pressFeedbackType = PressFeedbackType.Sink,
|
||||||
onClick = haptics.press,
|
onClick = haptics.contextClick,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -1109,7 +1122,7 @@ internal fun QuickConnectCard(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.AddLink,
|
Icons.Rounded.AddLink,
|
||||||
contentDescription = "快速连接",
|
contentDescription = "快速连接",
|
||||||
tint = MiuixTheme.colorScheme.onPrimaryContainer,
|
tint = MiuixTheme.colorScheme.onPrimaryContainer,
|
||||||
)
|
)
|
||||||
@@ -1180,7 +1193,7 @@ internal fun DeviceEditorScreen(
|
|||||||
.padding(contentPadding)
|
.padding(contentPadding)
|
||||||
.padding(UiSpacing.PageHorizontal),
|
.padding(UiSpacing.PageHorizontal),
|
||||||
) {
|
) {
|
||||||
SectionSmallTitle(text = "编辑设备")
|
SectionSmallTitle("编辑设备")
|
||||||
Card {
|
Card {
|
||||||
TextField(
|
TextField(
|
||||||
value = name,
|
value = name,
|
||||||
|
|||||||
@@ -0,0 +1,217 @@
|
|||||||
|
package io.github.miuzarte.scrcpyforandroid.widgets
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.DragIndicator
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.key
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.state.ToggleableState
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import io.github.miuzarte.scrcpyforandroid.constants.UiSpacing
|
||||||
|
import io.github.miuzarte.scrcpyforandroid.haptics.rememberAppHaptics
|
||||||
|
import sh.calvin.reorderable.ReorderableColumn
|
||||||
|
import sh.calvin.reorderable.ReorderableRow
|
||||||
|
import top.yukonga.miuix.kmp.basic.Card
|
||||||
|
import top.yukonga.miuix.kmp.basic.Checkbox
|
||||||
|
import top.yukonga.miuix.kmp.basic.Icon
|
||||||
|
import top.yukonga.miuix.kmp.basic.Text
|
||||||
|
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
||||||
|
|
||||||
|
class ReorderableList(
|
||||||
|
private val itemsProvider: () -> List<Item>,
|
||||||
|
private val orientation: Orientation = Orientation.Column,
|
||||||
|
private val onSettle: (fromIndex: Int, toIndex: Int) -> Unit = { _, _ -> },
|
||||||
|
private val modifier: Modifier = Modifier,
|
||||||
|
private val showCheckbox: Boolean = false,
|
||||||
|
private val onCheckboxChange: ((String, Boolean) -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
enum class Orientation { Column, Row }
|
||||||
|
|
||||||
|
data class Item(
|
||||||
|
val id: String,
|
||||||
|
val icon: ImageVector? = null,
|
||||||
|
val title: String,
|
||||||
|
val subtitle: String,
|
||||||
|
val checked: Boolean = true,
|
||||||
|
val checkboxEnabled: Boolean = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
operator fun invoke() {
|
||||||
|
val haptics = rememberAppHaptics()
|
||||||
|
val items = itemsProvider()
|
||||||
|
when (orientation) {
|
||||||
|
Orientation.Column -> {
|
||||||
|
ReorderableColumn(
|
||||||
|
list = items,
|
||||||
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(UiSpacing.Small),
|
||||||
|
onSettle = onSettle,
|
||||||
|
onMove = haptics.segmentTick,
|
||||||
|
) { _, item, _ ->
|
||||||
|
key(item.id) {
|
||||||
|
ReorderableItem {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(
|
||||||
|
horizontal = UiSpacing.CardTitle,
|
||||||
|
vertical = UiSpacing.FieldLabelBottom
|
||||||
|
),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
if (item.icon != null) {
|
||||||
|
Icon(
|
||||||
|
item.icon,
|
||||||
|
contentDescription = item.title
|
||||||
|
)
|
||||||
|
Spacer(Modifier.padding(horizontal = 4.dp))
|
||||||
|
}
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = item.title,
|
||||||
|
color = MiuixTheme.colorScheme.onSurface,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
)
|
||||||
|
if (item.subtitle.isNotBlank()) {
|
||||||
|
Text(
|
||||||
|
text = item.subtitle,
|
||||||
|
color = MiuixTheme.colorScheme.onSurfaceVariantSummary,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
if (showCheckbox) {
|
||||||
|
Checkbox(
|
||||||
|
state = if (item.checked) ToggleableState.On else ToggleableState.Off,
|
||||||
|
onClick = {
|
||||||
|
onCheckboxChange?.invoke(item.id, !item.checked)
|
||||||
|
},
|
||||||
|
enabled = item.checkboxEnabled
|
||||||
|
)
|
||||||
|
Spacer(Modifier.padding(horizontal = 4.dp))
|
||||||
|
}
|
||||||
|
IconButton(
|
||||||
|
onClick = {},
|
||||||
|
modifier = Modifier
|
||||||
|
.draggableHandle(
|
||||||
|
onDragStarted = {
|
||||||
|
haptics.longPress()
|
||||||
|
},
|
||||||
|
onDragStopped = {
|
||||||
|
haptics.confirm()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.DragIndicator,
|
||||||
|
contentDescription = "拖动排序",
|
||||||
|
tint = MiuixTheme.colorScheme.onSurfaceVariantSummary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Orientation.Row -> {
|
||||||
|
ReorderableRow(
|
||||||
|
list = items,
|
||||||
|
modifier = modifier.fillMaxHeight(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(UiSpacing.Small),
|
||||||
|
onSettle = onSettle,
|
||||||
|
onMove = haptics.segmentTick,
|
||||||
|
) { _, item, _ ->
|
||||||
|
key(item.id) {
|
||||||
|
ReorderableItem {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxHeight(),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
horizontal = UiSpacing.CardTitle,
|
||||||
|
vertical = UiSpacing.FieldLabelBottom
|
||||||
|
),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
if (showCheckbox) {
|
||||||
|
Checkbox(
|
||||||
|
state = if (item.checked) ToggleableState.On else ToggleableState.Off,
|
||||||
|
onClick = {
|
||||||
|
onCheckboxChange?.invoke(item.id, !item.checked)
|
||||||
|
},
|
||||||
|
enabled = item.checkboxEnabled
|
||||||
|
)
|
||||||
|
Spacer(Modifier.padding(horizontal = 4.dp))
|
||||||
|
}
|
||||||
|
IconButton(
|
||||||
|
onClick = {},
|
||||||
|
modifier = Modifier
|
||||||
|
.draggableHandle(
|
||||||
|
onDragStarted = {
|
||||||
|
haptics.longPress()
|
||||||
|
},
|
||||||
|
onDragStopped = {
|
||||||
|
haptics.confirm()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.DragIndicator,
|
||||||
|
contentDescription = "拖动排序",
|
||||||
|
tint = MiuixTheme.colorScheme.onSurfaceVariantSummary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(Modifier.padding(UiSpacing.CardContent))
|
||||||
|
Text(
|
||||||
|
text = item.title,
|
||||||
|
color = MiuixTheme.colorScheme.onSurface,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
)
|
||||||
|
if (item.subtitle.isNotBlank()) {
|
||||||
|
Text(
|
||||||
|
text = item.subtitle,
|
||||||
|
color = MiuixTheme.colorScheme.onSurfaceVariantSummary,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
package io.github.miuzarte.scrcpyforandroid.widgets
|
|
||||||
|
|
||||||
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.DragIndicator
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.compose.ui.zIndex
|
|
||||||
import io.github.miuzarte.scrcpyforandroid.constants.UiSpacing
|
|
||||||
import top.yukonga.miuix.kmp.basic.Card
|
|
||||||
import top.yukonga.miuix.kmp.basic.Icon
|
|
||||||
import top.yukonga.miuix.kmp.basic.Text
|
|
||||||
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
|
||||||
|
|
||||||
enum class SortTransferDirection {
|
|
||||||
NONE,
|
|
||||||
TO_LEFT,
|
|
||||||
TO_RIGHT,
|
|
||||||
}
|
|
||||||
|
|
||||||
data class SortableCardItem(
|
|
||||||
val id: String,
|
|
||||||
val title: String,
|
|
||||||
val subtitle: String = "",
|
|
||||||
)
|
|
||||||
|
|
||||||
data class SortDropPayload(
|
|
||||||
val itemId: String,
|
|
||||||
val deltaX: Float,
|
|
||||||
val deltaY: Float,
|
|
||||||
val transferDirection: SortTransferDirection,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a titled list of sortable cards.
|
|
||||||
*
|
|
||||||
* Each item can be long-pressed and dragged. When a drag ends the
|
|
||||||
* `onDrop` callback receives a payload describing the item id and
|
|
||||||
* the drag delta which callers can use to perform reordering or
|
|
||||||
* transfer actions.
|
|
||||||
*
|
|
||||||
* @param title The section title displayed above the list.
|
|
||||||
* @param items The list of items to render as sortable cards.
|
|
||||||
* @param modifier Optional [Modifier] applied to the outer column.
|
|
||||||
* @param transferDirection Hint for transfer direction used by callers.
|
|
||||||
* @param onLongPressHaptic Optional callback invoked when an item is long-pressed.
|
|
||||||
* @param onDrop Callback invoked when a dragged item is dropped.
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun SortableCardList(
|
|
||||||
title: String,
|
|
||||||
items: List<SortableCardItem>,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
transferDirection: SortTransferDirection = SortTransferDirection.NONE,
|
|
||||||
onLongPressHaptic: (() -> Unit)? = null,
|
|
||||||
onDrop: (SortDropPayload) -> Unit,
|
|
||||||
) {
|
|
||||||
Column(modifier = modifier) {
|
|
||||||
SectionSmallTitle(text = title, showLeadingSpacer = false)
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(UiSpacing.FieldLabelBottom),
|
|
||||||
) {
|
|
||||||
items.forEach { item ->
|
|
||||||
var dragX by remember(item.id) { mutableFloatStateOf(0f) }
|
|
||||||
var dragY by remember(item.id) { mutableFloatStateOf(0f) }
|
|
||||||
var dragging by remember(item.id) { androidx.compose.runtime.mutableStateOf(false) }
|
|
||||||
|
|
||||||
Card(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.zIndex(if (dragging) 1f else 0f)
|
|
||||||
.graphicsLayer {
|
|
||||||
translationX = if (dragging) dragX else 0f
|
|
||||||
translationY = if (dragging) dragY else 0f
|
|
||||||
}
|
|
||||||
.pointerInput(item.id) {
|
|
||||||
detectDragGesturesAfterLongPress(
|
|
||||||
onDragStart = {
|
|
||||||
dragging = true
|
|
||||||
dragX = 0f
|
|
||||||
dragY = 0f
|
|
||||||
onLongPressHaptic?.invoke()
|
|
||||||
},
|
|
||||||
onDrag = { change, dragAmount ->
|
|
||||||
change.consume()
|
|
||||||
dragX += dragAmount.x
|
|
||||||
dragY += dragAmount.y
|
|
||||||
},
|
|
||||||
onDragEnd = {
|
|
||||||
onDrop(
|
|
||||||
SortDropPayload(
|
|
||||||
itemId = item.id,
|
|
||||||
deltaX = dragX,
|
|
||||||
deltaY = dragY,
|
|
||||||
transferDirection = transferDirection,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
dragging = false
|
|
||||||
dragX = 0f
|
|
||||||
dragY = 0f
|
|
||||||
},
|
|
||||||
onDragCancel = {
|
|
||||||
dragging = false
|
|
||||||
dragX = 0f
|
|
||||||
dragY = 0f
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(
|
|
||||||
horizontal = UiSpacing.CardContent,
|
|
||||||
vertical = UiSpacing.FieldLabelBottom
|
|
||||||
),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
|
||||||
Text(
|
|
||||||
text = item.title,
|
|
||||||
color = MiuixTheme.colorScheme.onSurface,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
)
|
|
||||||
if (item.subtitle.isNotBlank()) {
|
|
||||||
Text(
|
|
||||||
text = item.subtitle,
|
|
||||||
color = MiuixTheme.colorScheme.onSurfaceVariantSummary,
|
|
||||||
fontSize = 13.sp,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Icon(
|
|
||||||
Icons.Default.DragIndicator,
|
|
||||||
contentDescription = "拖动排序",
|
|
||||||
tint = MiuixTheme.colorScheme.onSurfaceVariantSummary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -81,7 +81,7 @@ internal fun StatusCardLayout(
|
|||||||
.fillMaxHeight(),
|
.fillMaxHeight(),
|
||||||
colors = defaultColors(color = spec.big.containerColor),
|
colors = defaultColors(color = spec.big.containerColor),
|
||||||
pressFeedbackType = PressFeedbackType.Tilt,
|
pressFeedbackType = PressFeedbackType.Tilt,
|
||||||
onClick = haptics.press,
|
onClick = haptics.contextClick,
|
||||||
) {
|
) {
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
Box(
|
Box(
|
||||||
@@ -158,7 +158,7 @@ private fun StatusMetricCard(spec: StatusSmallCardSpec, modifier: Modifier) {
|
|||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
insideMargin = PaddingValues(UiSpacing.Large),
|
insideMargin = PaddingValues(UiSpacing.Large),
|
||||||
pressFeedbackType = PressFeedbackType.Tilt,
|
pressFeedbackType = PressFeedbackType.Tilt,
|
||||||
onClick = haptics.press,
|
onClick = haptics.contextClick,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = spec.title,
|
text = spec.title,
|
||||||
|
|||||||
@@ -4,21 +4,23 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.automirrored.filled.VolumeDown
|
import androidx.compose.material.icons.automirrored.rounded.VolumeDown
|
||||||
import androidx.compose.material.icons.automirrored.filled.VolumeOff
|
import androidx.compose.material.icons.automirrored.rounded.VolumeOff
|
||||||
import androidx.compose.material.icons.automirrored.filled.VolumeUp
|
import androidx.compose.material.icons.automirrored.rounded.VolumeUp
|
||||||
import androidx.compose.material.icons.filled.Apps
|
import androidx.compose.material.icons.rounded.Apps
|
||||||
import androidx.compose.material.icons.filled.Home
|
import androidx.compose.material.icons.rounded.Home
|
||||||
import androidx.compose.material.icons.filled.Menu
|
import androidx.compose.material.icons.rounded.Menu
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.rounded.MoreVert
|
||||||
import androidx.compose.material.icons.filled.Notifications
|
import androidx.compose.material.icons.rounded.Notifications
|
||||||
import androidx.compose.material.icons.filled.PhotoCamera
|
import androidx.compose.material.icons.rounded.PowerSettingsNew
|
||||||
import androidx.compose.material.icons.filled.PowerSettingsNew
|
import androidx.compose.material.icons.rounded.Screenshot
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -28,15 +30,19 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.github.miuzarte.scrcpyforandroid.constants.AppDefaults
|
||||||
import io.github.miuzarte.scrcpyforandroid.constants.UiAndroidKeycodes
|
import io.github.miuzarte.scrcpyforandroid.constants.UiAndroidKeycodes
|
||||||
import io.github.miuzarte.scrcpyforandroid.constants.UiSpacing
|
import io.github.miuzarte.scrcpyforandroid.constants.UiSpacing
|
||||||
|
import io.github.miuzarte.scrcpyforandroid.haptics.rememberAppHaptics
|
||||||
import top.yukonga.miuix.kmp.basic.Button
|
import top.yukonga.miuix.kmp.basic.Button
|
||||||
import top.yukonga.miuix.kmp.basic.ButtonDefaults
|
import top.yukonga.miuix.kmp.basic.ButtonDefaults
|
||||||
import top.yukonga.miuix.kmp.basic.DropdownImpl
|
|
||||||
import top.yukonga.miuix.kmp.basic.Icon
|
import top.yukonga.miuix.kmp.basic.Icon
|
||||||
import top.yukonga.miuix.kmp.basic.ListPopupColumn
|
import top.yukonga.miuix.kmp.basic.ListPopupColumn
|
||||||
import top.yukonga.miuix.kmp.basic.ListPopupDefaults
|
import top.yukonga.miuix.kmp.basic.ListPopupDefaults
|
||||||
import top.yukonga.miuix.kmp.basic.PopupPositionProvider
|
import top.yukonga.miuix.kmp.basic.PopupPositionProvider
|
||||||
|
import top.yukonga.miuix.kmp.basic.SpinnerDefaults
|
||||||
|
import top.yukonga.miuix.kmp.basic.SpinnerEntry
|
||||||
|
import top.yukonga.miuix.kmp.basic.SpinnerItemImpl
|
||||||
import top.yukonga.miuix.kmp.basic.Text
|
import top.yukonga.miuix.kmp.basic.Text
|
||||||
import top.yukonga.miuix.kmp.extra.SuperListPopup
|
import top.yukonga.miuix.kmp.extra.SuperListPopup
|
||||||
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
||||||
@@ -50,13 +56,13 @@ enum class VirtualButtonAction(
|
|||||||
MORE(
|
MORE(
|
||||||
"more",
|
"more",
|
||||||
"更多",
|
"更多",
|
||||||
Icons.Default.MoreVert,
|
Icons.Rounded.MoreVert,
|
||||||
null
|
null
|
||||||
),
|
),
|
||||||
HOME(
|
HOME(
|
||||||
"home",
|
"home",
|
||||||
"主页",
|
"主页",
|
||||||
Icons.Default.Home,
|
Icons.Rounded.Home,
|
||||||
UiAndroidKeycodes.HOME
|
UiAndroidKeycodes.HOME
|
||||||
),
|
),
|
||||||
BACK(
|
BACK(
|
||||||
@@ -68,97 +74,87 @@ enum class VirtualButtonAction(
|
|||||||
APP_SWITCH(
|
APP_SWITCH(
|
||||||
"app_switch",
|
"app_switch",
|
||||||
"多任务",
|
"多任务",
|
||||||
Icons.Default.Apps,
|
Icons.Rounded.Apps,
|
||||||
UiAndroidKeycodes.APP_SWITCH
|
UiAndroidKeycodes.APP_SWITCH
|
||||||
),
|
),
|
||||||
MENU(
|
MENU(
|
||||||
"menu",
|
"menu",
|
||||||
"菜单",
|
"菜单",
|
||||||
Icons.Default.Menu,
|
Icons.Rounded.Menu,
|
||||||
UiAndroidKeycodes.MENU
|
UiAndroidKeycodes.MENU
|
||||||
),
|
),
|
||||||
NOTIFICATION(
|
NOTIFICATION(
|
||||||
"notification",
|
"notification",
|
||||||
"通知栏",
|
"通知栏",
|
||||||
Icons.Default.Notifications,
|
Icons.Rounded.Notifications,
|
||||||
UiAndroidKeycodes.NOTIFICATION
|
UiAndroidKeycodes.NOTIFICATION
|
||||||
),
|
),
|
||||||
VOLUME_UP(
|
VOLUME_UP(
|
||||||
"volume_up",
|
"volume_up",
|
||||||
"音量+",
|
"音量+",
|
||||||
Icons.AutoMirrored.Filled.VolumeUp,
|
Icons.AutoMirrored.Rounded.VolumeUp,
|
||||||
UiAndroidKeycodes.VOLUME_UP
|
UiAndroidKeycodes.VOLUME_UP
|
||||||
),
|
),
|
||||||
VOLUME_DOWN(
|
VOLUME_DOWN(
|
||||||
"volume_down",
|
"volume_down",
|
||||||
"音量-",
|
"音量-",
|
||||||
Icons.AutoMirrored.Filled.VolumeDown,
|
Icons.AutoMirrored.Rounded.VolumeDown,
|
||||||
UiAndroidKeycodes.VOLUME_DOWN
|
UiAndroidKeycodes.VOLUME_DOWN
|
||||||
),
|
),
|
||||||
VOLUME_MUTE(
|
VOLUME_MUTE(
|
||||||
"volume_mute",
|
"volume_mute",
|
||||||
"静音",
|
"静音",
|
||||||
Icons.AutoMirrored.Filled.VolumeOff,
|
Icons.AutoMirrored.Rounded.VolumeOff,
|
||||||
UiAndroidKeycodes.VOLUME_MUTE
|
UiAndroidKeycodes.VOLUME_MUTE
|
||||||
),
|
),
|
||||||
POWER(
|
POWER(
|
||||||
"power",
|
"power",
|
||||||
"锁屏",
|
"锁屏",
|
||||||
Icons.Default.PowerSettingsNew,
|
Icons.Rounded.PowerSettingsNew,
|
||||||
UiAndroidKeycodes.POWER
|
UiAndroidKeycodes.POWER
|
||||||
),
|
),
|
||||||
SCREENSHOT(
|
SCREENSHOT(
|
||||||
"screenshot",
|
"screenshot",
|
||||||
"截图",
|
"截图",
|
||||||
Icons.Default.PhotoCamera,
|
Icons.Rounded.Screenshot,
|
||||||
UiAndroidKeycodes.SYSRQ
|
UiAndroidKeycodes.SYSRQ
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class VirtualButtonItem(
|
||||||
|
val action: VirtualButtonAction,
|
||||||
|
val showOutside: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
object VirtualButtonActions {
|
object VirtualButtonActions {
|
||||||
val all = VirtualButtonAction.entries
|
val all = VirtualButtonAction.entries
|
||||||
|
|
||||||
private val byId = all.associateBy { it.id }
|
private val byId = all.associateBy { it.id }
|
||||||
|
|
||||||
fun parseStoredIds(raw: String): List<String> {
|
fun parseStoredLayout(raw: String): List<VirtualButtonItem> {
|
||||||
if (raw.isBlank()) return emptyList()
|
if (raw.isBlank())
|
||||||
|
return parseStoredLayout(AppDefaults.VIRTUAL_BUTTONS_LAYOUT)
|
||||||
|
|
||||||
return raw.split(',').mapNotNull { item ->
|
return raw.split(',').mapNotNull { item ->
|
||||||
val id = item.trim()
|
val parts = item.trim().split(':')
|
||||||
id.ifBlank { null }
|
if (parts.size != 2) return@mapNotNull null
|
||||||
|
val id = parts[0]
|
||||||
|
val showOutside = parts[1] == "1"
|
||||||
|
val action = byId[id] ?: return@mapNotNull null
|
||||||
|
VirtualButtonItem(action, showOutside)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun encodeStoredIds(ids: List<String>): String = ids.joinToString(",")
|
fun encodeStoredLayout(items: List<VirtualButtonItem>): String {
|
||||||
|
return items.joinToString(",") { item ->
|
||||||
fun resolveLayout(
|
"${item.action.id}:${if (item.showOutside) "1" else "0"}"
|
||||||
outsideIds: List<String>,
|
|
||||||
moreIds: List<String>,
|
|
||||||
): Pair<List<VirtualButtonAction>, List<VirtualButtonAction>> {
|
|
||||||
val outside = mutableListOf<VirtualButtonAction>()
|
|
||||||
val overflow = mutableListOf<VirtualButtonAction>()
|
|
||||||
val used = mutableSetOf<String>()
|
|
||||||
|
|
||||||
outsideIds.forEach { id ->
|
|
||||||
val action = byId[id] ?: return@forEach
|
|
||||||
if (used.add(action.id)) outside.add(action)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (used.add(VirtualButtonAction.MORE.id)) {
|
fun splitLayout(items: List<VirtualButtonItem>): Pair<List<VirtualButtonAction>, List<VirtualButtonAction>> {
|
||||||
outside.add(VirtualButtonAction.MORE)
|
val outside = items.filter { it.showOutside }.map { it.action }
|
||||||
}
|
val more = items.filter { !it.showOutside }.map { it.action }
|
||||||
|
return outside to more
|
||||||
moreIds.forEach { id ->
|
|
||||||
if (id == VirtualButtonAction.MORE.id) return@forEach
|
|
||||||
val action = byId[id] ?: return@forEach
|
|
||||||
if (used.add(action.id)) overflow.add(action)
|
|
||||||
}
|
|
||||||
|
|
||||||
all.forEach { action ->
|
|
||||||
if (action == VirtualButtonAction.MORE) return@forEach
|
|
||||||
if (used.add(action.id)) overflow.add(action)
|
|
||||||
}
|
|
||||||
|
|
||||||
return outside to overflow
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,11 +165,11 @@ class VirtualButtonBar(
|
|||||||
@Composable
|
@Composable
|
||||||
fun Preview(
|
fun Preview(
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
onPressHaptic: () -> Unit,
|
showText: Boolean,
|
||||||
onConfirmHaptic: () -> Unit,
|
|
||||||
onAction: (VirtualButtonAction) -> Unit,
|
onAction: (VirtualButtonAction) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
val haptics = rememberAppHaptics()
|
||||||
val activeContainerColor = MiuixTheme.colorScheme.primary
|
val activeContainerColor = MiuixTheme.colorScheme.primary
|
||||||
val disabledContainerColor = MiuixTheme.colorScheme.primary.copy(alpha = 0.35f)
|
val disabledContainerColor = MiuixTheme.colorScheme.primary.copy(alpha = 0.35f)
|
||||||
val activeContentColor = MiuixTheme.colorScheme.onPrimary
|
val activeContentColor = MiuixTheme.colorScheme.onPrimary
|
||||||
@@ -188,7 +184,7 @@ class VirtualButtonBar(
|
|||||||
Box(modifier = Modifier.weight(1f)) {
|
Box(modifier = Modifier.weight(1f)) {
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
onPressHaptic()
|
haptics.contextClick()
|
||||||
if (action == VirtualButtonAction.MORE) {
|
if (action == VirtualButtonAction.MORE) {
|
||||||
showMorePopup = true
|
showMorePopup = true
|
||||||
} else {
|
} else {
|
||||||
@@ -201,6 +197,7 @@ class VirtualButtonBar(
|
|||||||
color = activeContainerColor,
|
color = activeContainerColor,
|
||||||
disabledColor = disabledContainerColor,
|
disabledColor = disabledContainerColor,
|
||||||
),
|
),
|
||||||
|
insideMargin = PaddingValues(0.dp),
|
||||||
) {
|
) {
|
||||||
val contentColor = if (enabled) activeContentColor else disabledContentColor
|
val contentColor = if (enabled) activeContentColor else disabledContentColor
|
||||||
Icon(
|
Icon(
|
||||||
@@ -209,15 +206,16 @@ class VirtualButtonBar(
|
|||||||
modifier = Modifier.size(18.dp),
|
modifier = Modifier.size(18.dp),
|
||||||
tint = contentColor,
|
tint = contentColor,
|
||||||
)
|
)
|
||||||
androidx.compose.foundation.layout.Spacer(Modifier.width(UiSpacing.Small))
|
if (showText) {
|
||||||
Text(action.title, color = contentColor)
|
Spacer(Modifier.width(UiSpacing.Small))
|
||||||
|
Text(action.title, color = contentColor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (action == VirtualButtonAction.MORE) {
|
if (action == VirtualButtonAction.MORE) {
|
||||||
MorePopup(
|
MorePopup(
|
||||||
show = showMorePopup,
|
show = showMorePopup,
|
||||||
moreActions = moreActions,
|
moreActions = moreActions,
|
||||||
onDismiss = { showMorePopup = false },
|
onDismiss = { showMorePopup = false },
|
||||||
onConfirmHaptic = onConfirmHaptic,
|
|
||||||
onAction = {
|
onAction = {
|
||||||
onAction(it)
|
onAction(it)
|
||||||
showMorePopup = false
|
showMorePopup = false
|
||||||
@@ -232,11 +230,10 @@ class VirtualButtonBar(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Fullscreen(
|
fun Fullscreen(
|
||||||
onPressHaptic: () -> Unit,
|
|
||||||
onConfirmHaptic: () -> Unit,
|
|
||||||
onAction: (VirtualButtonAction) -> Unit,
|
onAction: (VirtualButtonAction) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
val haptics = rememberAppHaptics()
|
||||||
var showMorePopup by remember { mutableStateOf(false) }
|
var showMorePopup by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
@@ -247,7 +244,7 @@ class VirtualButtonBar(
|
|||||||
Box(modifier = Modifier.weight(1f)) {
|
Box(modifier = Modifier.weight(1f)) {
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
onPressHaptic()
|
haptics.contextClick()
|
||||||
if (action == VirtualButtonAction.MORE) {
|
if (action == VirtualButtonAction.MORE) {
|
||||||
showMorePopup = true
|
showMorePopup = true
|
||||||
} else {
|
} else {
|
||||||
@@ -270,7 +267,6 @@ class VirtualButtonBar(
|
|||||||
show = showMorePopup,
|
show = showMorePopup,
|
||||||
moreActions = moreActions,
|
moreActions = moreActions,
|
||||||
onDismiss = { showMorePopup = false },
|
onDismiss = { showMorePopup = false },
|
||||||
onConfirmHaptic = onConfirmHaptic,
|
|
||||||
onAction = {
|
onAction = {
|
||||||
onAction(it)
|
onAction(it)
|
||||||
showMorePopup = false
|
showMorePopup = false
|
||||||
@@ -288,10 +284,26 @@ class VirtualButtonBar(
|
|||||||
show: Boolean,
|
show: Boolean,
|
||||||
moreActions: List<VirtualButtonAction>,
|
moreActions: List<VirtualButtonAction>,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onConfirmHaptic: () -> Unit,
|
|
||||||
onAction: (VirtualButtonAction) -> Unit,
|
onAction: (VirtualButtonAction) -> Unit,
|
||||||
renderInRootScaffold: Boolean,
|
renderInRootScaffold: Boolean,
|
||||||
) {
|
) {
|
||||||
|
val haptics = rememberAppHaptics()
|
||||||
|
val spinnerItems = remember(moreActions) {
|
||||||
|
moreActions.map { action ->
|
||||||
|
SpinnerEntry(
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
action.icon,
|
||||||
|
contentDescription = action.title,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = UiSpacing.CardContent),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
title = action.title,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SuperListPopup(
|
SuperListPopup(
|
||||||
show = show,
|
show = show,
|
||||||
popupPositionProvider = ListPopupDefaults.ContextMenuPositionProvider,
|
popupPositionProvider = ListPopupDefaults.ContextMenuPositionProvider,
|
||||||
@@ -301,15 +313,17 @@ class VirtualButtonBar(
|
|||||||
enableWindowDim = false,
|
enableWindowDim = false,
|
||||||
) {
|
) {
|
||||||
ListPopupColumn {
|
ListPopupColumn {
|
||||||
moreActions.forEachIndexed { index, action ->
|
spinnerItems.forEachIndexed { index, entry ->
|
||||||
DropdownImpl(
|
SpinnerItemImpl(
|
||||||
text = action.title,
|
entry = entry,
|
||||||
optionSize = moreActions.size,
|
entryCount = spinnerItems.size,
|
||||||
isSelected = false,
|
isSelected = false,
|
||||||
index = index,
|
index = index,
|
||||||
onSelectedIndexChange = {
|
spinnerColors = SpinnerDefaults.spinnerColors(),
|
||||||
onConfirmHaptic()
|
dialogMode = false,
|
||||||
onAction(action)
|
onSelectedIndexChange = { selectedIdx ->
|
||||||
|
haptics.confirm()
|
||||||
|
onAction(moreActions[selectedIdx])
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user