refactor: navigation and haptic move into composition locals
This commit is contained in:
@@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
|
||||
@@ -29,6 +30,10 @@ data class AppHaptics(
|
||||
val virtualKey: () -> Unit,
|
||||
)
|
||||
|
||||
val LocalAppHaptics = staticCompositionLocalOf<AppHaptics> {
|
||||
error("No AppHaptics provided")
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberAppHaptics(): AppHaptics {
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
|
||||
@@ -74,9 +74,8 @@ import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme
|
||||
import androidx.compose.ui.graphics.BlendMode as ComposeBlendMode
|
||||
|
||||
@Composable
|
||||
internal fun AboutScreen(
|
||||
onBack: () -> Unit,
|
||||
) {
|
||||
internal fun AboutScreen() {
|
||||
val navigator = LocalRootNavigator.current
|
||||
val topAppBarScrollBehavior = MiuixScrollBehavior()
|
||||
val lazyListState = rememberLazyListState()
|
||||
var logoHeightPx by remember { mutableIntStateOf(0) }
|
||||
@@ -102,9 +101,9 @@ internal fun AboutScreen(
|
||||
titleColor = colorScheme.onSurface.copy(alpha = scrollProgress),
|
||||
defaultWindowInsetsPadding = false,
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
IconButton(onClick = navigator.pop) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
contentDescription = "返回",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.github.miuzarte.scrcpyforandroid.pages
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.util.Log
|
||||
import android.view.WindowManager
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
@@ -28,7 +29,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import io.github.miuzarte.scrcpyforandroid.NativeCoreFacade
|
||||
import io.github.miuzarte.scrcpyforandroid.constants.Defaults
|
||||
import io.github.miuzarte.scrcpyforandroid.constants.UiSpacing
|
||||
import io.github.miuzarte.scrcpyforandroid.haptics.rememberAppHaptics
|
||||
import io.github.miuzarte.scrcpyforandroid.haptics.LocalAppHaptics
|
||||
import io.github.miuzarte.scrcpyforandroid.models.ConnectionTarget
|
||||
import io.github.miuzarte.scrcpyforandroid.models.DeviceShortcut
|
||||
import io.github.miuzarte.scrcpyforandroid.models.DeviceShortcuts
|
||||
@@ -37,7 +38,7 @@ import io.github.miuzarte.scrcpyforandroid.scaffolds.LazyColumn
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy
|
||||
import io.github.miuzarte.scrcpyforandroid.services.EventLogger
|
||||
import io.github.miuzarte.scrcpyforandroid.services.EventLogger.logEvent
|
||||
import io.github.miuzarte.scrcpyforandroid.services.SnackbarController
|
||||
import io.github.miuzarte.scrcpyforandroid.services.LocalSnackbarController
|
||||
import io.github.miuzarte.scrcpyforandroid.services.fetchConnectedDeviceInfo
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Settings
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Storage.appSettings
|
||||
@@ -90,13 +91,10 @@ fun DeviceTabScreen(
|
||||
nativeCore: NativeCoreFacade,
|
||||
adbService: NativeAdbService,
|
||||
scrcpy: Scrcpy,
|
||||
snackbar: SnackbarController,
|
||||
scrollBehavior: ScrollBehavior,
|
||||
onOpenVirtualButtonOrder: () -> Unit,
|
||||
onOpenReorderDevices: () -> Unit,
|
||||
onOpenAdvancedPage: () -> Unit,
|
||||
onOpenFullscreenPage: () -> Unit,
|
||||
) {
|
||||
val navigator = LocalRootNavigator.current
|
||||
var showThreePointMenu by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
Scaffold(
|
||||
@@ -121,7 +119,7 @@ fun DeviceTabScreen(
|
||||
showThreePointMenu = false
|
||||
},
|
||||
onOpenVirtualButtonOrder = {
|
||||
onOpenVirtualButtonOrder()
|
||||
navigator.push(RootScreen.VirtualButtonOrder)
|
||||
showThreePointMenu = false
|
||||
},
|
||||
canClearLogs = EventLogger.hasLogs(),
|
||||
@@ -140,10 +138,7 @@ fun DeviceTabScreen(
|
||||
nativeCore = nativeCore,
|
||||
adbService = adbService,
|
||||
scrcpy = scrcpy,
|
||||
snackbar = snackbar,
|
||||
scrollBehavior = scrollBehavior,
|
||||
onOpenAdvancedPage = onOpenAdvancedPage,
|
||||
onOpenFullscreenPage = onOpenFullscreenPage,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -154,17 +149,18 @@ fun DeviceTabPage(
|
||||
nativeCore: NativeCoreFacade,
|
||||
adbService: NativeAdbService,
|
||||
scrcpy: Scrcpy,
|
||||
snackbar: SnackbarController,
|
||||
scrollBehavior: ScrollBehavior,
|
||||
onOpenAdvancedPage: () -> Unit,
|
||||
onOpenFullscreenPage: () -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val taskScope = remember { CoroutineScope(Dispatchers.IO + SupervisorJob()) }
|
||||
val haptics = rememberAppHaptics()
|
||||
val activity = remember(context) { context as? Activity }
|
||||
|
||||
val haptics = LocalAppHaptics.current
|
||||
val navigator = LocalRootNavigator.current
|
||||
val fullscreenNavigationState = LocalFullscreenNavigationState.current
|
||||
val snackbar = LocalSnackbarController.current
|
||||
|
||||
val asBundleShared by appSettings.bundleState.collectAsState()
|
||||
val asBundleSharedLatest by rememberUpdatedState(asBundleShared)
|
||||
var asBundle by rememberSaveable(asBundleShared) { mutableStateOf(asBundleShared) }
|
||||
@@ -869,12 +865,11 @@ fun DeviceTabPage(
|
||||
SectionSmallTitle("Scrcpy")
|
||||
ConfigPanel(
|
||||
busy = busy,
|
||||
snackbar = snackbar,
|
||||
audioForwardingSupported = audioForwardingSupported,
|
||||
cameraMirroringSupported = cameraMirroringSupported,
|
||||
adbConnecting = adbConnecting,
|
||||
isQuickConnected = isQuickConnected,
|
||||
onOpenAdvanced = onOpenAdvancedPage,
|
||||
onOpenAdvanced = { navigator.push(RootScreen.Advanced) },
|
||||
onStartStopHaptic = { haptics.contextClick() },
|
||||
onStart = {
|
||||
runBusy("启动 scrcpy") {
|
||||
@@ -949,8 +944,16 @@ fun DeviceTabPage(
|
||||
previewControlsVisible = !previewControlsVisible
|
||||
},
|
||||
onOpenFullscreen = {
|
||||
if (sessionInfo != null) {
|
||||
onOpenFullscreenPage()
|
||||
val currentSession = sessionInfo
|
||||
if (currentSession != null) {
|
||||
fullscreenNavigationState.setOrientation(
|
||||
if (currentSession.width >= currentSession.height) {
|
||||
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
} else {
|
||||
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
}
|
||||
)
|
||||
navigator.push(RootScreen.Fullscreen)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -25,7 +25,7 @@ import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import io.github.miuzarte.scrcpyforandroid.NativeCoreFacade
|
||||
import io.github.miuzarte.scrcpyforandroid.haptics.rememberAppHaptics
|
||||
import io.github.miuzarte.scrcpyforandroid.haptics.LocalAppHaptics
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Settings
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Storage.appSettings
|
||||
@@ -42,16 +42,16 @@ import top.yukonga.miuix.kmp.basic.Scaffold
|
||||
|
||||
@Composable
|
||||
fun FullscreenControlScreen(
|
||||
onBack: () -> Unit,
|
||||
scrcpy: Scrcpy,
|
||||
nativeCore: NativeCoreFacade,
|
||||
onVideoSizeChanged: (width: Int, height: Int) -> Unit,
|
||||
) {
|
||||
BackHandler(enabled = true, onBack = onBack)
|
||||
val navigator = LocalRootNavigator.current
|
||||
BackHandler(enabled = true, onBack = navigator.pop)
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
val haptics = rememberAppHaptics()
|
||||
val haptics = LocalAppHaptics.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val taskScope = remember { CoroutineScope(SupervisorJob() + Dispatchers.IO) }
|
||||
|
||||
@@ -154,7 +154,7 @@ fun FullscreenControlScreen(
|
||||
FullscreenControlScreen(
|
||||
session = session,
|
||||
nativeCore = nativeCore,
|
||||
onDismiss = onBack,
|
||||
onDismiss = navigator.pop,
|
||||
showDebugInfo = fullscreenDebugInfo,
|
||||
currentFps = currentFps,
|
||||
enableBackHandler = false,
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package io.github.miuzarte.scrcpyforandroid.pages
|
||||
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
|
||||
class ServerPicker(
|
||||
val pick: () -> Unit,
|
||||
)
|
||||
|
||||
val LocalServerPicker = staticCompositionLocalOf<ServerPicker> {
|
||||
error("No ServerPicker provided")
|
||||
}
|
||||
@@ -11,16 +11,15 @@ import androidx.activity.compose.PredictiveBackHandler
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Devices
|
||||
import androidx.compose.material.icons.rounded.Settings
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
@@ -46,10 +45,12 @@ import io.github.miuzarte.scrcpyforandroid.BuildConfig
|
||||
import io.github.miuzarte.scrcpyforandroid.NativeCoreFacade
|
||||
import io.github.miuzarte.scrcpyforandroid.constants.ThemeModes
|
||||
import io.github.miuzarte.scrcpyforandroid.constants.UiMotion
|
||||
import io.github.miuzarte.scrcpyforandroid.models.DeviceShortcuts
|
||||
import io.github.miuzarte.scrcpyforandroid.haptics.LocalAppHaptics
|
||||
import io.github.miuzarte.scrcpyforandroid.haptics.rememberAppHaptics
|
||||
import io.github.miuzarte.scrcpyforandroid.nativecore.NativeAdbService
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy
|
||||
import io.github.miuzarte.scrcpyforandroid.services.AppUpdateChecker
|
||||
import io.github.miuzarte.scrcpyforandroid.services.LocalSnackbarController
|
||||
import io.github.miuzarte.scrcpyforandroid.services.SnackbarController
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.AppSettings
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Settings
|
||||
@@ -90,21 +91,19 @@ sealed interface RootScreen : NavKey {
|
||||
|
||||
@Composable
|
||||
fun MainScreen() {
|
||||
// Environment
|
||||
val context = LocalContext.current
|
||||
val appContext = context.applicationContext
|
||||
val scope = rememberCoroutineScope()
|
||||
val taskScope = remember { CoroutineScope(SupervisorJob() + Dispatchers.IO) }
|
||||
|
||||
val activity = remember(context) { context as? Activity }
|
||||
val initialOrientation = remember(activity) {
|
||||
activity?.requestedOrientation ?: ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
}
|
||||
DisposableEffect(activity) {
|
||||
onDispose {
|
||||
activity?.requestedOrientation = initialOrientation
|
||||
}
|
||||
}
|
||||
|
||||
// Scopes
|
||||
val scope = rememberCoroutineScope()
|
||||
val taskScope = remember { CoroutineScope(SupervisorJob() + Dispatchers.IO) }
|
||||
|
||||
// Core services
|
||||
val nativeCore = remember(appContext) {
|
||||
NativeCoreFacade.get(appContext)
|
||||
}
|
||||
@@ -112,6 +111,7 @@ fun MainScreen() {
|
||||
NativeAdbService(appContext)
|
||||
}
|
||||
|
||||
// Global controllers provided to the compose tree
|
||||
val snackHostState = remember { SnackbarHostState() }
|
||||
val snackbarController = remember(scope, snackHostState) {
|
||||
SnackbarController(
|
||||
@@ -119,6 +119,9 @@ fun MainScreen() {
|
||||
hostState = snackHostState,
|
||||
)
|
||||
}
|
||||
val haptics = rememberAppHaptics()
|
||||
|
||||
// Root navigation and UI chrome state
|
||||
val saveableStateHolder = rememberSaveableStateHolder()
|
||||
val tabs = remember { MainBottomTabDestination.entries }
|
||||
val pagerState = rememberPagerState(
|
||||
@@ -127,6 +130,19 @@ fun MainScreen() {
|
||||
val currentTab = tabs[pagerState.currentPage]
|
||||
val rootBackStack = remember { mutableStateListOf<NavKey>(RootScreen.Home) }
|
||||
val currentRootScreen = rootBackStack.lastOrNull() as? RootScreen ?: RootScreen.Home
|
||||
var showReorderDevices by rememberSaveable { mutableStateOf(false) }
|
||||
var fullscreenOrientation by rememberSaveable {
|
||||
mutableIntStateOf(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
|
||||
}
|
||||
var lastExitBackPressAtMs by rememberSaveable { mutableLongStateOf(0L) }
|
||||
|
||||
DisposableEffect(activity) {
|
||||
onDispose {
|
||||
activity?.requestedOrientation = initialOrientation
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll behaviors
|
||||
val devicesPageScrollBehavior = MiuixScrollBehavior(
|
||||
canScroll = { currentTab == MainBottomTabDestination.Device })
|
||||
val settingsPageScrollBehavior = MiuixScrollBehavior(
|
||||
@@ -140,17 +156,23 @@ fun MainScreen() {
|
||||
}
|
||||
})
|
||||
|
||||
fun popRoot() {
|
||||
if (rootBackStack.size > 1)
|
||||
rootBackStack.removeAt(rootBackStack.lastIndex)
|
||||
// Navigation helpers
|
||||
val rootNavigator = remember {
|
||||
RootNavigator(
|
||||
push = { rootBackStack.add(it) },
|
||||
pop = {
|
||||
if (rootBackStack.size > 1)
|
||||
rootBackStack.removeAt(rootBackStack.lastIndex)
|
||||
},
|
||||
)
|
||||
}
|
||||
val fullscreenNavigationState = remember {
|
||||
FullscreenNavigationState(
|
||||
setOrientation = { fullscreenOrientation = it }
|
||||
)
|
||||
}
|
||||
|
||||
// Unified back behavior:
|
||||
// 1) pop inner route
|
||||
// 2) switch tab back to Device
|
||||
// 3) double-back to exit and disconnect adb/scrcpy
|
||||
var lastExitBackPressAtMs by rememberSaveable { mutableLongStateOf(0L) }
|
||||
|
||||
// Shared settings bundles
|
||||
val asBundleShared by appSettings.bundleState.collectAsState()
|
||||
val asBundleSharedLatest by rememberUpdatedState(asBundleShared)
|
||||
var asBundle by rememberSaveable(asBundleShared) { mutableStateOf(asBundleShared) }
|
||||
@@ -197,6 +219,7 @@ fun MainScreen() {
|
||||
}
|
||||
}
|
||||
|
||||
// Scrcpy instance and session state
|
||||
val customServerUri = asBundle.customServerUri
|
||||
.ifBlank { null }
|
||||
val customServerVersion = asBundle.customServerVersion
|
||||
@@ -220,6 +243,35 @@ fun MainScreen() {
|
||||
}
|
||||
val currentSession by scrcpy.currentSessionState.collectAsState()
|
||||
|
||||
// Side-effect launchers and composition locals
|
||||
val picker = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.OpenDocument()
|
||||
) { uri: Uri? ->
|
||||
if (uri == null) return@rememberLauncherForActivityResult
|
||||
runCatching {
|
||||
context.contentResolver.takePersistableUriPermission(
|
||||
uri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
asBundle = asBundle.copy(customServerUri = uri.toString())
|
||||
}
|
||||
val serverPicker = remember(picker) {
|
||||
ServerPicker {
|
||||
picker.launch(
|
||||
arrayOf(
|
||||
"application/java-archive",
|
||||
"application/octet-stream",
|
||||
"*/*"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Derived flags
|
||||
val canNavigateBack = rootBackStack.size > 1
|
||||
|| pagerState.currentPage != MainBottomTabDestination.Device.ordinal
|
||||
|
||||
LaunchedEffect(asBundle.lastUpdateCheckAt) {
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - asBundle.lastUpdateCheckAt < AppUpdateChecker.CHECK_INTERVAL_MS) return@LaunchedEffect
|
||||
@@ -231,7 +283,7 @@ fun MainScreen() {
|
||||
|
||||
fun handleBackNavigation() {
|
||||
if (rootBackStack.size > 1) {
|
||||
popRoot()
|
||||
rootNavigator.pop()
|
||||
} else if (pagerState.currentPage != MainBottomTabDestination.Device.ordinal) {
|
||||
scope.launch {
|
||||
pagerState.animateScrollToPage(
|
||||
@@ -260,14 +312,6 @@ fun MainScreen() {
|
||||
}
|
||||
}
|
||||
|
||||
var showReorderDevices by rememberSaveable { mutableStateOf(false) }
|
||||
var fullscreenOrientation by rememberSaveable {
|
||||
mutableIntStateOf(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
|
||||
}
|
||||
|
||||
val canNavigateBack = rootBackStack.size > 1
|
||||
|| pagerState.currentPage != MainBottomTabDestination.Device.ordinal
|
||||
|
||||
BackHandler(enabled = currentRootScreen !is RootScreen.Fullscreen) {
|
||||
handleBackNavigation()
|
||||
}
|
||||
@@ -317,18 +361,6 @@ fun MainScreen() {
|
||||
LaunchedEffect(asBundle.adbKeyName) {
|
||||
adbService.keyName = asBundle.adbKeyName.ifBlank { AppSettings.ADB_KEY_NAME.defaultValue }
|
||||
}
|
||||
val picker = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.OpenDocument()
|
||||
) { uri: Uri? ->
|
||||
if (uri == null) return@rememberLauncherForActivityResult
|
||||
runCatching {
|
||||
context.contentResolver.takePersistableUriPermission(
|
||||
uri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
asBundle = asBundle.copy(customServerUri = uri.toString())
|
||||
}
|
||||
|
||||
val rootEntryProvider = entryProvider<NavKey> {
|
||||
entry(RootScreen.Home) {
|
||||
@@ -371,39 +403,13 @@ fun MainScreen() {
|
||||
nativeCore = nativeCore,
|
||||
adbService = adbService,
|
||||
scrcpy = scrcpy,
|
||||
snackbar = snackbarController,
|
||||
scrollBehavior = devicesPageScrollBehavior,
|
||||
onOpenVirtualButtonOrder = { rootBackStack.add(RootScreen.VirtualButtonOrder) },
|
||||
onOpenReorderDevices = { showReorderDevices = true },
|
||||
onOpenAdvancedPage = { rootBackStack.add(RootScreen.Advanced) },
|
||||
onOpenFullscreenPage = {
|
||||
currentSession?.let { session ->
|
||||
fullscreenOrientation =
|
||||
if (session.width >= session.height) {
|
||||
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
} else {
|
||||
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
}
|
||||
rootBackStack.add(RootScreen.Fullscreen)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
MainBottomTabDestination.Settings -> SettingsScreen(
|
||||
scrollBehavior = settingsPageScrollBehavior,
|
||||
snackbar = snackbarController,
|
||||
onOpenReorderDevices = { showReorderDevices = true },
|
||||
onOpenVirtualButtonOrder = { rootBackStack.add(RootScreen.VirtualButtonOrder) },
|
||||
onOpenAbout = { rootBackStack.add(RootScreen.About) },
|
||||
onPickServer = {
|
||||
picker.launch(
|
||||
arrayOf(
|
||||
"application/java-archive",
|
||||
"application/octet-stream",
|
||||
"*/*"
|
||||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -418,29 +424,23 @@ fun MainScreen() {
|
||||
|
||||
entry(RootScreen.Advanced) {
|
||||
ScrcpyAllOptionsScreen(
|
||||
onBack = ::popRoot,
|
||||
scrollBehavior = advancedPageScrollBehavior,
|
||||
snackbar = snackbarController,
|
||||
scrcpy = scrcpy,
|
||||
)
|
||||
}
|
||||
|
||||
entry(RootScreen.About) {
|
||||
AboutScreen(
|
||||
onBack = ::popRoot,
|
||||
)
|
||||
AboutScreen()
|
||||
}
|
||||
|
||||
entry(RootScreen.VirtualButtonOrder) {
|
||||
VirtualButtonOrderScreen(
|
||||
onBack = ::popRoot,
|
||||
scrollBehavior = advancedPageScrollBehavior,
|
||||
)
|
||||
}
|
||||
|
||||
entry(RootScreen.Fullscreen) {
|
||||
FullscreenControlScreen(
|
||||
onBack = ::popRoot,
|
||||
scrcpy = scrcpy,
|
||||
nativeCore = nativeCore,
|
||||
onVideoSizeChanged = { width, height ->
|
||||
@@ -465,9 +465,17 @@ fun MainScreen() {
|
||||
val themeController = remember(themeMode) { ThemeController(colorSchemeMode = themeMode) }
|
||||
|
||||
MiuixTheme(controller = themeController) {
|
||||
NavDisplay(
|
||||
entries = rootEntries,
|
||||
onBack = ::popRoot,
|
||||
)
|
||||
CompositionLocalProvider(
|
||||
LocalRootNavigator provides rootNavigator,
|
||||
LocalSnackbarController provides snackbarController,
|
||||
LocalAppHaptics provides haptics,
|
||||
LocalFullscreenNavigationState provides fullscreenNavigationState,
|
||||
LocalServerPicker provides serverPicker,
|
||||
) {
|
||||
NavDisplay(
|
||||
entries = rootEntries,
|
||||
onBack = rootNavigator.pop,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.github.miuzarte.scrcpyforandroid.pages
|
||||
|
||||
import android.content.pm.ActivityInfo
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
|
||||
class RootNavigator(
|
||||
val push: (RootScreen) -> Unit,
|
||||
val pop: () -> Unit,
|
||||
)
|
||||
|
||||
val LocalRootNavigator = staticCompositionLocalOf<RootNavigator> {
|
||||
error("No RootNavigator provided")
|
||||
}
|
||||
|
||||
class FullscreenNavigationState(
|
||||
val setOrientation: (Int) -> Unit,
|
||||
)
|
||||
|
||||
val LocalFullscreenNavigationState = staticCompositionLocalOf<FullscreenNavigationState> {
|
||||
error("No FullscreenNavigationState provided")
|
||||
}
|
||||
@@ -42,7 +42,7 @@ import io.github.miuzarte.scrcpyforandroid.scaffolds.SuperTextField
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Codec
|
||||
import io.github.miuzarte.scrcpyforandroid.services.SnackbarController
|
||||
import io.github.miuzarte.scrcpyforandroid.services.LocalSnackbarController
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Settings
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Storage.scrcpyOptions
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -69,17 +69,17 @@ import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
internal fun ScrcpyAllOptionsScreen(
|
||||
onBack: () -> Unit,
|
||||
scrollBehavior: ScrollBehavior,
|
||||
snackbar: SnackbarController,
|
||||
scrcpy: Scrcpy,
|
||||
) {
|
||||
val snackbar = LocalSnackbarController.current
|
||||
val navigator = LocalRootNavigator.current
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = "所有参数",
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
IconButton(onClick = navigator.pop) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
contentDescription = "返回"
|
||||
@@ -94,7 +94,6 @@ internal fun ScrcpyAllOptionsScreen(
|
||||
ScrcpyAllOptionsPage(
|
||||
contentPadding = contentPadding,
|
||||
scrollBehavior = scrollBehavior,
|
||||
snackbar = snackbar,
|
||||
scrcpy = scrcpy,
|
||||
)
|
||||
}
|
||||
@@ -104,12 +103,12 @@ internal fun ScrcpyAllOptionsScreen(
|
||||
internal fun ScrcpyAllOptionsPage(
|
||||
contentPadding: PaddingValues,
|
||||
scrollBehavior: ScrollBehavior,
|
||||
snackbar: SnackbarController,
|
||||
scrcpy: Scrcpy,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val taskScope = remember { CoroutineScope(Dispatchers.IO + SupervisorJob()) }
|
||||
val snackbar = LocalSnackbarController.current
|
||||
|
||||
var refreshBusy by rememberSaveable { mutableStateOf(false) }
|
||||
var listRefreshVersion by rememberSaveable { mutableIntStateOf(0) }
|
||||
|
||||
@@ -36,7 +36,7 @@ import io.github.miuzarte.scrcpyforandroid.scaffolds.SuperSlider
|
||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.SuperTextField
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy
|
||||
import io.github.miuzarte.scrcpyforandroid.services.AppUpdateChecker
|
||||
import io.github.miuzarte.scrcpyforandroid.services.SnackbarController
|
||||
import io.github.miuzarte.scrcpyforandroid.services.LocalSnackbarController
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.AppSettings
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.PreferenceMigration
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Settings
|
||||
@@ -64,11 +64,7 @@ import kotlin.system.exitProcess
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
scrollBehavior: ScrollBehavior,
|
||||
snackbar: SnackbarController,
|
||||
onOpenReorderDevices: () -> Unit,
|
||||
onOpenVirtualButtonOrder: () -> Unit,
|
||||
onOpenAbout: () -> Unit,
|
||||
onPickServer: () -> Unit,
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@@ -81,11 +77,7 @@ fun SettingsScreen(
|
||||
SettingsPage(
|
||||
contentPadding = pagePadding,
|
||||
scrollBehavior = scrollBehavior,
|
||||
snackbar = snackbar,
|
||||
onOpenReorderDevices = onOpenReorderDevices,
|
||||
onOpenVirtualButtonOrder = onOpenVirtualButtonOrder,
|
||||
onOpenAbout = onOpenAbout,
|
||||
onPickServer = onPickServer,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -94,11 +86,7 @@ fun SettingsScreen(
|
||||
fun SettingsPage(
|
||||
contentPadding: PaddingValues,
|
||||
scrollBehavior: ScrollBehavior,
|
||||
snackbar: SnackbarController,
|
||||
onOpenReorderDevices: () -> Unit,
|
||||
onOpenVirtualButtonOrder: () -> Unit,
|
||||
onOpenAbout: () -> Unit,
|
||||
onPickServer: () -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val appContext = context.applicationContext
|
||||
@@ -111,6 +99,10 @@ fun SettingsPage(
|
||||
val taskScope = remember { CoroutineScope(SupervisorJob() + Dispatchers.IO) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val snackbar = LocalSnackbarController.current
|
||||
val navigator = LocalRootNavigator.current
|
||||
val serverPicker = LocalServerPicker.current
|
||||
|
||||
val asBundleShared by appSettings.bundleState.collectAsState()
|
||||
val asBundleSharedLatest by rememberUpdatedState(asBundleShared)
|
||||
var asBundle by rememberSaveable(asBundleShared) { mutableStateOf(asBundleShared) }
|
||||
@@ -257,7 +249,7 @@ fun SettingsPage(
|
||||
ArrowPreference(
|
||||
title = "虚拟按钮排序",
|
||||
summary = "手动排序预览/全屏时的虚拟按钮,并选择哪些按钮展示在外",
|
||||
onClick = onOpenVirtualButtonOrder,
|
||||
onClick = { navigator.push(RootScreen.VirtualButtonOrder) },
|
||||
)
|
||||
SwitchPreference(
|
||||
title = "全屏显示虚拟按钮",
|
||||
@@ -307,13 +299,13 @@ fun SettingsPage(
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Clear,
|
||||
Icons.Rounded.Clear,
|
||||
contentDescription = "清空",
|
||||
)
|
||||
}
|
||||
IconButton(onClick = onPickServer) {
|
||||
IconButton(onClick = serverPicker.pick) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.FileOpen,
|
||||
Icons.Rounded.FileOpen,
|
||||
contentDescription = "选择文件",
|
||||
)
|
||||
}
|
||||
@@ -474,7 +466,7 @@ fun SettingsPage(
|
||||
ArrowPreference(
|
||||
title = "关于",
|
||||
summary = updateSummary,
|
||||
onClick = onOpenAbout,
|
||||
onClick = { navigator.push(RootScreen.About) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,16 +37,16 @@ import top.yukonga.miuix.kmp.preference.SwitchPreference
|
||||
|
||||
@Composable
|
||||
internal fun VirtualButtonOrderScreen(
|
||||
onBack: () -> Unit,
|
||||
scrollBehavior: ScrollBehavior,
|
||||
) {
|
||||
val navigator = LocalRootNavigator.current
|
||||
Scaffold(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = "虚拟按钮排序",
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
IconButton(onClick = navigator.pop) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
contentDescription = "返回"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.github.miuzarte.scrcpyforandroid.services
|
||||
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import top.yukonga.miuix.kmp.basic.SnackbarDuration
|
||||
@@ -29,3 +30,7 @@ class SnackbarController(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val LocalSnackbarController = staticCompositionLocalOf<SnackbarController> {
|
||||
error("No SnackbarController provided")
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ import io.github.miuzarte.scrcpyforandroid.scaffolds.SuperTextField
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Codec
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.TouchEventHandler
|
||||
import io.github.miuzarte.scrcpyforandroid.services.SnackbarController
|
||||
import io.github.miuzarte.scrcpyforandroid.services.LocalSnackbarController
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Settings
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Storage
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Storage.scrcpyOptions
|
||||
@@ -352,7 +352,6 @@ internal fun VirtualButtonCard(
|
||||
@Composable
|
||||
internal fun ConfigPanel(
|
||||
busy: Boolean,
|
||||
snackbar: SnackbarController,
|
||||
audioForwardingSupported: Boolean,
|
||||
cameraMirroringSupported: Boolean,
|
||||
adbConnecting: Boolean,
|
||||
@@ -366,6 +365,8 @@ internal fun ConfigPanel(
|
||||
) {
|
||||
val taskScope = remember { CoroutineScope(SupervisorJob() + Dispatchers.IO) }
|
||||
|
||||
val snackbar = LocalSnackbarController.current
|
||||
|
||||
val sessionStarted = sessionInfo != null
|
||||
|
||||
val soBundleShared by scrcpyOptions.bundleState.collectAsState()
|
||||
|
||||
@@ -20,7 +20,7 @@ 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 io.github.miuzarte.scrcpyforandroid.haptics.LocalAppHaptics
|
||||
import sh.calvin.reorderable.ReorderableColumn
|
||||
import sh.calvin.reorderable.ReorderableRow
|
||||
import top.yukonga.miuix.kmp.basic.Card
|
||||
@@ -50,7 +50,7 @@ class ReorderableList(
|
||||
|
||||
@Composable
|
||||
operator fun invoke() {
|
||||
val haptics = rememberAppHaptics()
|
||||
val haptics = LocalAppHaptics.current
|
||||
val items = itemsProvider()
|
||||
when (orientation) {
|
||||
Orientation.Column -> {
|
||||
@@ -81,7 +81,7 @@ class ReorderableList(
|
||||
horizontalArrangement = Arrangement.spacedBy(UiSpacing.Small)
|
||||
) {
|
||||
if (item.icon != null) Icon(
|
||||
imageVector = item.icon,
|
||||
item.icon,
|
||||
contentDescription = item.title
|
||||
)
|
||||
Column {
|
||||
|
||||
@@ -25,7 +25,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
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 io.github.miuzarte.scrcpyforandroid.haptics.LocalAppHaptics
|
||||
import top.yukonga.miuix.kmp.basic.Card
|
||||
import top.yukonga.miuix.kmp.basic.CardDefaults.defaultColors
|
||||
import top.yukonga.miuix.kmp.basic.Icon
|
||||
@@ -67,7 +67,7 @@ internal fun StatusCardLayout(
|
||||
spec: StatusCardSpec,
|
||||
busyLabel: String?,
|
||||
) {
|
||||
val haptics = rememberAppHaptics()
|
||||
val haptics = LocalAppHaptics.current
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -91,7 +91,7 @@ internal fun StatusCardLayout(
|
||||
contentAlignment = Alignment.BottomEnd,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = spec.big.icon,
|
||||
spec.big.icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(170.dp),
|
||||
tint = spec.big.iconTint,
|
||||
@@ -153,7 +153,7 @@ internal fun StatusCardLayout(
|
||||
|
||||
@Composable
|
||||
private fun StatusMetricCard(spec: StatusSmallCardSpec, modifier: Modifier) {
|
||||
val haptics = rememberAppHaptics()
|
||||
val haptics = LocalAppHaptics.current
|
||||
Card(
|
||||
modifier = modifier,
|
||||
insideMargin = PaddingValues(UiSpacing.Large),
|
||||
|
||||
@@ -33,7 +33,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.github.miuzarte.scrcpyforandroid.constants.UiAndroidKeycodes
|
||||
import io.github.miuzarte.scrcpyforandroid.constants.UiSpacing
|
||||
import io.github.miuzarte.scrcpyforandroid.haptics.rememberAppHaptics
|
||||
import io.github.miuzarte.scrcpyforandroid.haptics.LocalAppHaptics
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.AppSettings
|
||||
import kotlinx.coroutines.launch
|
||||
import top.yukonga.miuix.kmp.basic.Button
|
||||
@@ -171,7 +171,7 @@ class VirtualButtonBar(
|
||||
onAction: (VirtualButtonAction) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val haptics = rememberAppHaptics()
|
||||
val haptics = LocalAppHaptics.current
|
||||
val activeContainerColor = MiuixTheme.colorScheme.primary
|
||||
val disabledContainerColor = MiuixTheme.colorScheme.primary.copy(alpha = 0.35f)
|
||||
val activeContentColor = MiuixTheme.colorScheme.onPrimary
|
||||
@@ -236,7 +236,7 @@ class VirtualButtonBar(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val haptics = rememberAppHaptics()
|
||||
val haptics = LocalAppHaptics.current
|
||||
var showMorePopup by remember { mutableStateOf(false) }
|
||||
|
||||
Row(
|
||||
@@ -293,7 +293,7 @@ class VirtualButtonBar(
|
||||
renderInRootScaffold: Boolean,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val haptics = rememberAppHaptics()
|
||||
val haptics = LocalAppHaptics.current
|
||||
val spinnerItems = remember(moreActions) {
|
||||
moreActions.map { action ->
|
||||
SpinnerEntry(
|
||||
|
||||
Reference in New Issue
Block a user