improve: complete haptic feedback
This commit is contained in:
@@ -6,14 +6,7 @@ import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -22,21 +15,12 @@ import androidx.compose.material.icons.rounded.Block
|
||||
import androidx.compose.material.icons.rounded.DeleteOutline
|
||||
import androidx.compose.material.icons.rounded.Edit
|
||||
import androidx.compose.material.icons.rounded.Password
|
||||
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
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
@@ -45,11 +29,7 @@ import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import io.github.miuzarte.scrcpyforandroid.constants.UiSpacing
|
||||
import io.github.miuzarte.scrcpyforandroid.password.BiometricGate
|
||||
import io.github.miuzarte.scrcpyforandroid.password.PasswordCreatedState
|
||||
import io.github.miuzarte.scrcpyforandroid.password.PasswordEntry
|
||||
import io.github.miuzarte.scrcpyforandroid.password.PasswordRepository
|
||||
import io.github.miuzarte.scrcpyforandroid.password.PasswordSanitizer
|
||||
import io.github.miuzarte.scrcpyforandroid.password.*
|
||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.LazyColumn
|
||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.ReorderableList
|
||||
import io.github.miuzarte.scrcpyforandroid.services.AppRuntime
|
||||
@@ -57,28 +37,12 @@ import io.github.miuzarte.scrcpyforandroid.services.LocalSnackbarController
|
||||
import io.github.miuzarte.scrcpyforandroid.services.SnackbarController
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Settings
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Storage.appSettings
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.confirm
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.contextClick
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.createThemeController
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.rememberBlurBackdrop
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import top.yukonga.miuix.kmp.basic.ButtonDefaults
|
||||
import top.yukonga.miuix.kmp.basic.Card
|
||||
import top.yukonga.miuix.kmp.basic.DropdownEntry
|
||||
import top.yukonga.miuix.kmp.basic.DropdownItem
|
||||
import top.yukonga.miuix.kmp.basic.Icon
|
||||
import top.yukonga.miuix.kmp.basic.IconButton
|
||||
import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior
|
||||
import top.yukonga.miuix.kmp.basic.Scaffold
|
||||
import top.yukonga.miuix.kmp.basic.ScrollBehavior
|
||||
import top.yukonga.miuix.kmp.basic.SnackbarHost
|
||||
import top.yukonga.miuix.kmp.basic.SnackbarHostState
|
||||
import top.yukonga.miuix.kmp.basic.Text
|
||||
import top.yukonga.miuix.kmp.basic.TextButton
|
||||
import top.yukonga.miuix.kmp.basic.TextField
|
||||
import top.yukonga.miuix.kmp.basic.TopAppBar
|
||||
import kotlinx.coroutines.*
|
||||
import top.yukonga.miuix.kmp.basic.*
|
||||
import top.yukonga.miuix.kmp.blur.layerBackdrop
|
||||
import top.yukonga.miuix.kmp.icon.MiuixIcons
|
||||
import top.yukonga.miuix.kmp.icon.extended.Close
|
||||
@@ -93,7 +57,7 @@ import top.yukonga.miuix.kmp.theme.MiuixTheme
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme.textStyles
|
||||
|
||||
class LockscreenPasswordActivity : FragmentActivity() {
|
||||
class LockscreenPasswordActivity: FragmentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
@@ -154,6 +118,8 @@ private fun LockscreenPasswordScreen(
|
||||
val taskScope = remember { CoroutineScope(SupervisorJob() + Dispatchers.IO) }
|
||||
val scrollBehavior = MiuixScrollBehavior()
|
||||
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
val asBundleShared by appSettings.bundleState.collectAsState()
|
||||
val asBundleSharedLatest by rememberUpdatedState(asBundleShared)
|
||||
var asBundle by rememberSaveable(asBundleShared) { mutableStateOf(asBundleShared) }
|
||||
@@ -232,10 +198,15 @@ private fun LockscreenPasswordScreen(
|
||||
if (blurBackdrop != null) Color.Transparent
|
||||
else colorScheme.surface,
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { activity.onBackPressedDispatcher.onBackPressed() }) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
activity.onBackPressedDispatcher.onBackPressed()
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
contentDescription = stringResource(R.string.cd_back)
|
||||
contentDescription = stringResource(R.string.cd_back),
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -248,9 +219,9 @@ private fun LockscreenPasswordScreen(
|
||||
text = textCreateNew,
|
||||
onClick = {
|
||||
pendingCreate = true
|
||||
}
|
||||
)
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
) {
|
||||
Icon(
|
||||
@@ -300,80 +271,86 @@ private fun LockscreenPasswordScreen(
|
||||
},
|
||||
)
|
||||
|
||||
if (showRiskDialog) {
|
||||
OverlayDialog(
|
||||
show = true,
|
||||
title = stringResource(R.string.password_no_lock_screen),
|
||||
summary = stringResource(R.string.password_no_lock_screen_warn),
|
||||
defaultWindowInsetsPadding = false,
|
||||
onDismissRequest = activity::finish,
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(UiSpacing.ContentHorizontal)) {
|
||||
TextButton(
|
||||
text = stringResource(R.string.button_cancel),
|
||||
onClick = activity::finish,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
TextButton(
|
||||
text = stringResource(R.string.password_agree),
|
||||
onClick = { showRiskDialog = false },
|
||||
modifier = Modifier.weight(1f),
|
||||
colors = ButtonDefaults.textButtonColorsPrimary(),
|
||||
)
|
||||
}
|
||||
OverlayDialog(
|
||||
show = showRiskDialog,
|
||||
title = stringResource(R.string.password_no_lock_screen),
|
||||
summary = stringResource(R.string.password_no_lock_screen_warn),
|
||||
defaultWindowInsetsPadding = false,
|
||||
onDismissRequest = activity::finish,
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(UiSpacing.ContentHorizontal)) {
|
||||
TextButton(
|
||||
text = stringResource(R.string.button_cancel),
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
activity.finish()
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
TextButton(
|
||||
text = stringResource(R.string.password_agree),
|
||||
onClick = {
|
||||
haptic.confirm()
|
||||
showRiskDialog = false
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
colors = ButtonDefaults.textButtonColorsPrimary(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (showDisableDialog) {
|
||||
OverlayDialog(
|
||||
show = true,
|
||||
title = stringResource(R.string.password_auth_lost_warn),
|
||||
summary = stringResource(R.string.password_auth_lost_detail),
|
||||
defaultWindowInsetsPadding = false,
|
||||
onDismissRequest = { showDisableDialog = false },
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(UiSpacing.ContentHorizontal)) {
|
||||
TextButton(
|
||||
text = stringResource(R.string.button_cancel),
|
||||
onClick = { showDisableDialog = false },
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
val textAuthToDisable = stringResource(R.string.password_auth_to_disable)
|
||||
val textAuthSubtitle = stringResource(R.string.password_auth_subtitle)
|
||||
TextButton(
|
||||
text = stringResource(R.string.password_continue_disable),
|
||||
onClick = {
|
||||
scope.launch {
|
||||
if (entries.any { it.cipherText != null }) {
|
||||
val ok = BiometricGate.authenticate(
|
||||
activity = activity,
|
||||
title = textAuthToDisable,
|
||||
subtitle = textAuthSubtitle,
|
||||
)
|
||||
if (!ok) {
|
||||
AppRuntime.snackbar(R.string.password_auth_failed)
|
||||
showDisableDialog = false
|
||||
return@launch
|
||||
}
|
||||
OverlayDialog(
|
||||
show = showDisableDialog,
|
||||
title = stringResource(R.string.password_auth_lost_warn),
|
||||
summary = stringResource(R.string.password_auth_lost_detail),
|
||||
defaultWindowInsetsPadding = false,
|
||||
onDismissRequest = { showDisableDialog = false },
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(UiSpacing.ContentHorizontal)) {
|
||||
TextButton(
|
||||
text = stringResource(R.string.button_cancel),
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
showDisableDialog = false
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
val textAuthToDisable = stringResource(R.string.password_auth_to_disable)
|
||||
val textAuthSubtitle = stringResource(R.string.password_auth_subtitle)
|
||||
TextButton(
|
||||
text = stringResource(R.string.password_continue_disable),
|
||||
onClick = {
|
||||
haptic.confirm()
|
||||
scope.launch {
|
||||
if (entries.any { it.cipherText != null }) {
|
||||
val ok = BiometricGate.authenticate(
|
||||
activity = activity,
|
||||
title = textAuthToDisable,
|
||||
subtitle = textAuthSubtitle,
|
||||
)
|
||||
if (!ok) {
|
||||
AppRuntime.snackbar(R.string.password_auth_failed)
|
||||
showDisableDialog = false
|
||||
return@launch
|
||||
}
|
||||
asBundle = asBundle.copy(passwordRequireAuth = false)
|
||||
entries.forEach { entry ->
|
||||
PasswordRepository.update(
|
||||
entry.copy(
|
||||
cipherText = entry.cipherText?.copyOf(),
|
||||
createdWithAuth = entry.createdWithAuth
|
||||
.takeIf { it != PasswordCreatedState.AuthenticatedCreated }
|
||||
?: PasswordCreatedState.AuthenticatedCreatedModified
|
||||
)
|
||||
)
|
||||
}
|
||||
showDisableDialog = false
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
colors = ButtonDefaults.textButtonColorsPrimary(),
|
||||
)
|
||||
}
|
||||
asBundle = asBundle.copy(passwordRequireAuth = false)
|
||||
entries.forEach { entry ->
|
||||
PasswordRepository.update(
|
||||
entry.copy(
|
||||
cipherText = entry.cipherText?.copyOf(),
|
||||
createdWithAuth = entry.createdWithAuth
|
||||
.takeIf { it != PasswordCreatedState.AuthenticatedCreated }
|
||||
?: PasswordCreatedState.AuthenticatedCreatedModified,
|
||||
),
|
||||
)
|
||||
}
|
||||
showDisableDialog = false
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
colors = ButtonDefaults.textButtonColorsPrimary(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,12 +367,16 @@ private fun LockscreenPasswordScreen(
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(UiSpacing.ContentHorizontal)) {
|
||||
TextButton(
|
||||
text = stringResource(R.string.button_cancel),
|
||||
onClick = { pendingDeleteId = null },
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
pendingDeleteId = null
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
TextButton(
|
||||
text = stringResource(R.string.button_delete),
|
||||
onClick = {
|
||||
haptic.confirm()
|
||||
PasswordRepository.delete(target.id)
|
||||
pendingDeleteId = null
|
||||
},
|
||||
@@ -467,6 +448,8 @@ private fun LockscreenPasswordPage(
|
||||
onDelete: (PasswordEntry) -> Unit,
|
||||
onMove: (Int, Int) -> Unit,
|
||||
) {
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
LazyColumn(
|
||||
contentPadding = contentPadding,
|
||||
scrollBehavior = scrollBehavior,
|
||||
@@ -478,7 +461,7 @@ private fun LockscreenPasswordPage(
|
||||
title = stringResource(R.string.password_require_auth),
|
||||
summary = stringResource(
|
||||
if (canAuthenticate) R.string.password_require_auth_detail
|
||||
else R.string.password_no_auth_capability
|
||||
else R.string.password_no_auth_capability,
|
||||
),
|
||||
checked = requireAuth,
|
||||
enabled = canAuthenticate || requireAuth,
|
||||
@@ -494,7 +477,10 @@ private fun LockscreenPasswordPage(
|
||||
ArrowPreference(
|
||||
title = stringResource(R.string.password_create_new),
|
||||
summary = stringResource(R.string.password_or_menu_hint),
|
||||
onClick = onCreate,
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
onCreate()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -521,17 +507,26 @@ private fun LockscreenPasswordPage(
|
||||
PasswordCreatedState.UnauthenticatedCreated -> textUnauthenticated
|
||||
PasswordCreatedState.AuthenticatedCreatedModified -> textBurned
|
||||
},
|
||||
onClick = { if (entry.cipherText != null) onRename(entry) },
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
if (entry.cipherText != null) onRename(entry)
|
||||
},
|
||||
endActions = listOf(
|
||||
ReorderableList.EndAction.Icon(
|
||||
icon = Icons.Rounded.Edit,
|
||||
contentDescription = textEdit,
|
||||
onClick = { if (entry.cipherText != null) onRename(entry) },
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
if (entry.cipherText != null) onRename(entry)
|
||||
},
|
||||
),
|
||||
ReorderableList.EndAction.Icon(
|
||||
icon = Icons.Rounded.DeleteOutline,
|
||||
contentDescription = textConfirm,
|
||||
onClick = { onDelete(entry) },
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
onDelete(entry)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -563,6 +558,8 @@ private fun PasswordEditorSheet(
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: (String, String) -> Unit,
|
||||
) {
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
val focusManager = LocalFocusManager.current
|
||||
var nameBuffer by rememberSaveable(mode, show, initialName) { mutableStateOf(initialName) }
|
||||
var passwordBuffer by rememberSaveable(mode, show) { mutableStateOf("") }
|
||||
@@ -571,12 +568,17 @@ private fun PasswordEditorSheet(
|
||||
show = show,
|
||||
title = stringResource(
|
||||
if (mode == PasswordDialogMode.Create) R.string.password_create_new
|
||||
else R.string.password_rename
|
||||
else R.string.password_rename,
|
||||
),
|
||||
defaultWindowInsetsPadding = false,
|
||||
onDismissRequest = onDismissRequest,
|
||||
startAction = {
|
||||
IconButton(onClick = onDismissRequest) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
onDismissRequest()
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = MiuixIcons.Close,
|
||||
contentDescription = stringResource(R.string.cd_close),
|
||||
@@ -584,7 +586,12 @@ private fun PasswordEditorSheet(
|
||||
}
|
||||
},
|
||||
endAction = {
|
||||
IconButton(onClick = { onConfirm(nameBuffer, passwordBuffer) }) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
onConfirm(nameBuffer, passwordBuffer)
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = MiuixIcons.Ok,
|
||||
contentDescription = stringResource(R.string.cd_save),
|
||||
|
||||
@@ -124,6 +124,7 @@ private fun AboutContent(
|
||||
onLogoHeightChanged: (Int) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val updateState by AppUpdateChecker.state.collectAsState()
|
||||
val backdrop = rememberLayerBackdrop()
|
||||
var isOs3Effect by remember { mutableStateOf(true) }
|
||||
@@ -363,6 +364,7 @@ private fun AboutContent(
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
context.startActivity(
|
||||
Intent(Intent.ACTION_VIEW, AppUpdateChecker.REPO_URL.toUri()),
|
||||
)
|
||||
@@ -380,6 +382,7 @@ private fun AboutContent(
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
context.startActivity(
|
||||
Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
@@ -400,6 +403,7 @@ private fun AboutContent(
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
context.startActivity(
|
||||
Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
@@ -424,6 +428,7 @@ private fun AboutContent(
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
context.startActivity(
|
||||
Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
|
||||
@@ -1,36 +1,15 @@
|
||||
package io.github.miuzarte.scrcpyforandroid.pages
|
||||
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.SwapHoriz
|
||||
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
|
||||
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
||||
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -52,39 +31,16 @@ import io.github.miuzarte.scrcpyforandroid.password.PasswordPickerPopupContent
|
||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.LazyColumn
|
||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.SectionSmallTitle
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.ClientOptions
|
||||
import io.github.miuzarte.scrcpyforandroid.services.AppRuntime
|
||||
import io.github.miuzarte.scrcpyforandroid.services.ConnectionController
|
||||
import io.github.miuzarte.scrcpyforandroid.services.ConnectionStateStore
|
||||
import io.github.miuzarte.scrcpyforandroid.services.DeviceAdbAutoReconnectManager
|
||||
import io.github.miuzarte.scrcpyforandroid.services.DeviceAdbConnectionCoordinator
|
||||
import io.github.miuzarte.scrcpyforandroid.services.EventLogger
|
||||
import io.github.miuzarte.scrcpyforandroid.services.*
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.BlurredBar
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.LocalEnableBlur
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.contextClick
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.rememberBlurBackdrop
|
||||
import io.github.miuzarte.scrcpyforandroid.widgets.AppListBottomSheet
|
||||
import io.github.miuzarte.scrcpyforandroid.widgets.AppListEntry
|
||||
import io.github.miuzarte.scrcpyforandroid.widgets.ConfigPanel
|
||||
import io.github.miuzarte.scrcpyforandroid.widgets.DeviceTileList
|
||||
import io.github.miuzarte.scrcpyforandroid.widgets.PairingCard
|
||||
import io.github.miuzarte.scrcpyforandroid.widgets.PreviewCard
|
||||
import io.github.miuzarte.scrcpyforandroid.widgets.QuickConnectCard
|
||||
import io.github.miuzarte.scrcpyforandroid.widgets.StatusCard
|
||||
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonAction
|
||||
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonCard
|
||||
import io.github.miuzarte.scrcpyforandroid.widgets.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import top.yukonga.miuix.kmp.basic.Card
|
||||
import top.yukonga.miuix.kmp.basic.DropdownEntry
|
||||
import top.yukonga.miuix.kmp.basic.DropdownItem
|
||||
import top.yukonga.miuix.kmp.basic.Icon
|
||||
import top.yukonga.miuix.kmp.basic.IconButton
|
||||
import top.yukonga.miuix.kmp.basic.Scaffold
|
||||
import top.yukonga.miuix.kmp.basic.ScrollBehavior
|
||||
import top.yukonga.miuix.kmp.basic.SmallTopAppBar
|
||||
import top.yukonga.miuix.kmp.basic.TextField
|
||||
import top.yukonga.miuix.kmp.basic.TopAppBar
|
||||
import top.yukonga.miuix.kmp.basic.*
|
||||
import top.yukonga.miuix.kmp.blur.layerBackdrop
|
||||
import top.yukonga.miuix.kmp.icon.MiuixIcons
|
||||
import top.yukonga.miuix.kmp.icon.extended.More
|
||||
@@ -114,6 +70,7 @@ internal fun DeviceTabScreen(
|
||||
val viewModel: DeviceTabViewModel = viewModel(factory = viewModelFactory)
|
||||
|
||||
val navigator = LocalRootNavigator.current
|
||||
val haptic = LocalHapticFeedback.current
|
||||
var useCompactTopAppBar by remember { mutableStateOf(false) }
|
||||
var showTwoPaneSideAction by remember { mutableStateOf(false) }
|
||||
var configPanelOnLeft by remember { mutableStateOf(true) }
|
||||
@@ -130,13 +87,16 @@ internal fun DeviceTabScreen(
|
||||
val topAppBarActions: @Composable RowScope.() -> Unit = {
|
||||
if (showTwoPaneSideAction) {
|
||||
IconButton(
|
||||
onClick = { twoPaneSideToggleRequest++ },
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
twoPaneSideToggleRequest++
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.SwapHoriz,
|
||||
contentDescription = stringResource(
|
||||
if (configPanelOnLeft) R.string.device_cd_config_right
|
||||
else R.string.device_cd_config_left
|
||||
else R.string.device_cd_config_left,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -162,9 +122,9 @@ internal fun DeviceTabScreen(
|
||||
onClick = {
|
||||
EventLogger.clearLogs()
|
||||
},
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = MiuixIcons.More,
|
||||
@@ -175,13 +135,13 @@ internal fun DeviceTabScreen(
|
||||
if (useCompactTopAppBar) SmallTopAppBar(
|
||||
title = stringResource(R.string.device_title),
|
||||
color = topAppBarColor,
|
||||
actions = topAppBarActions
|
||||
actions = topAppBarActions,
|
||||
)
|
||||
else TopAppBar(
|
||||
title = stringResource(R.string.device_title),
|
||||
color = topAppBarColor,
|
||||
actions = topAppBarActions,
|
||||
scrollBehavior = scrollBehavior
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -286,7 +246,7 @@ internal fun DeviceTabPage(
|
||||
|
||||
DisposableEffect(lifecycleOwner) {
|
||||
viewModel.setAppInForeground(
|
||||
lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)
|
||||
lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED),
|
||||
)
|
||||
val observer = LifecycleEventObserver { _, event ->
|
||||
when (event) {
|
||||
@@ -378,7 +338,7 @@ internal fun DeviceTabPage(
|
||||
} else {
|
||||
viewModel.setEditingDeviceId(
|
||||
if (editingDeviceId != device.id) device.id
|
||||
else null
|
||||
else null,
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -476,7 +436,6 @@ internal fun DeviceTabPage(
|
||||
}
|
||||
},
|
||||
onOpenAdvanced = { navigator.push(RootScreen.Advanced) },
|
||||
onStartStopHaptic = haptic::contextClick,
|
||||
onStart = viewModel::startScrcpy,
|
||||
onStop = viewModel::stopScrcpy,
|
||||
sessionInfo = sessionInfo,
|
||||
@@ -588,7 +547,6 @@ internal fun DeviceTabPage(
|
||||
}
|
||||
},
|
||||
onOpenAdvanced = { navigator.push(RootScreen.Advanced) },
|
||||
onStartStopHaptic = haptic::contextClick,
|
||||
onStart = viewModel::startScrcpy,
|
||||
onStop = viewModel::stopScrcpy,
|
||||
sessionInfo = sessionInfo,
|
||||
@@ -715,7 +673,7 @@ internal fun DeviceTabPage(
|
||||
.padding(contentPadding)
|
||||
.padding(
|
||||
horizontal = UiSpacing.PageHorizontal,
|
||||
vertical = UiSpacing.PageVertical
|
||||
vertical = UiSpacing.PageVertical,
|
||||
),
|
||||
horizontalArrangement = Arrangement.spacedBy(UiSpacing.PageItem),
|
||||
) {
|
||||
@@ -791,6 +749,7 @@ internal fun DeviceTabPage(
|
||||
summary = if (app?.label != null) task.packageName else null,
|
||||
system = app?.system,
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
viewModel.hideRecentTasks()
|
||||
if (sessionInfo == null) viewModel.startScrcpy(task.packageName)
|
||||
else viewModel.launchAppWithFallback(task.packageName)
|
||||
|
||||
@@ -3,42 +3,22 @@ package io.github.miuzarte.scrcpyforandroid.pages
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.rounded.InsertDriveFile
|
||||
import androidx.compose.material.icons.rounded.Download
|
||||
import androidx.compose.material.icons.rounded.Folder
|
||||
import androidx.compose.material.icons.rounded.Image
|
||||
import androidx.compose.material.icons.rounded.Link
|
||||
import androidx.compose.material.icons.rounded.RawOff
|
||||
import androidx.compose.material.icons.rounded.RawOn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.material.icons.rounded.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
@@ -50,28 +30,9 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import io.github.miuzarte.scrcpyforandroid.R
|
||||
import io.github.miuzarte.scrcpyforandroid.constants.UiSpacing
|
||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.LazyColumn
|
||||
import io.github.miuzarte.scrcpyforandroid.services.AppRuntime
|
||||
import io.github.miuzarte.scrcpyforandroid.services.DirectoryDownloadSnapshot
|
||||
import io.github.miuzarte.scrcpyforandroid.services.FileManagerService
|
||||
import io.github.miuzarte.scrcpyforandroid.services.RemoteFileEntry
|
||||
import io.github.miuzarte.scrcpyforandroid.services.RemoteFileKind
|
||||
import io.github.miuzarte.scrcpyforandroid.services.RemoteFileStat
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.BlurredBar
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.LocalEnableBlur
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.rememberBlurBackdrop
|
||||
import top.yukonga.miuix.kmp.basic.Card
|
||||
import top.yukonga.miuix.kmp.basic.DropdownEntry
|
||||
import top.yukonga.miuix.kmp.basic.DropdownItem
|
||||
import top.yukonga.miuix.kmp.basic.Icon
|
||||
import top.yukonga.miuix.kmp.basic.IconButton
|
||||
import top.yukonga.miuix.kmp.basic.PullToRefresh
|
||||
import top.yukonga.miuix.kmp.basic.PullToRefreshState
|
||||
import top.yukonga.miuix.kmp.basic.Scaffold
|
||||
import top.yukonga.miuix.kmp.basic.SmallTopAppBar
|
||||
import top.yukonga.miuix.kmp.basic.Text
|
||||
import top.yukonga.miuix.kmp.basic.TextButton
|
||||
import top.yukonga.miuix.kmp.basic.TextField
|
||||
import top.yukonga.miuix.kmp.basic.rememberPullToRefreshState
|
||||
import io.github.miuzarte.scrcpyforandroid.services.*
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.*
|
||||
import top.yukonga.miuix.kmp.basic.*
|
||||
import top.yukonga.miuix.kmp.blur.layerBackdrop
|
||||
import top.yukonga.miuix.kmp.icon.MiuixIcons
|
||||
import top.yukonga.miuix.kmp.icon.extended.More
|
||||
@@ -90,6 +51,7 @@ fun FileManagerScreen(
|
||||
onNavigateUpActionChange: (((() -> Boolean)?) -> Unit)? = null,
|
||||
) {
|
||||
val viewModel: FileManagerViewModel = viewModel()
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val context = LocalContext.current
|
||||
val blurBackdrop = rememberBlurBackdrop(LocalEnableBlur.current)
|
||||
val blurActive = blurBackdrop != null
|
||||
@@ -178,7 +140,10 @@ fun FileManagerScreen(
|
||||
else colorScheme.surface,
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = viewModel::navigateUp,
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
viewModel.navigateUp()
|
||||
},
|
||||
enabled = pathStack.size > 1,
|
||||
) {
|
||||
Icon(
|
||||
@@ -194,10 +159,12 @@ fun FileManagerScreen(
|
||||
.fillMaxWidth()
|
||||
.combinedClickable(
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
pathInput = currentPath
|
||||
showPathDialog = true
|
||||
},
|
||||
onLongClick = {
|
||||
// 自带 haptic
|
||||
pathInput = currentPath
|
||||
showPathDialog = true
|
||||
},
|
||||
@@ -244,11 +211,11 @@ fun FileManagerScreen(
|
||||
2 -> FileManagerSortField.TIME
|
||||
3 -> FileManagerSortField.EXTENSION
|
||||
else -> FileManagerSortField.NAME
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
DropdownEntry(
|
||||
items = dirOptions.mapIndexed { i, option ->
|
||||
@@ -257,13 +224,13 @@ fun FileManagerScreen(
|
||||
selected = i == dirIdx,
|
||||
onClick = {
|
||||
viewModel.updateSort(
|
||||
descending = i == 1
|
||||
descending = i == 1,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = MiuixIcons.Tune,
|
||||
@@ -286,9 +253,9 @@ fun FileManagerScreen(
|
||||
onClick = {
|
||||
uploadLauncher.launch(arrayOf("*/*"))
|
||||
},
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = MiuixIcons.More,
|
||||
@@ -303,7 +270,7 @@ fun FileManagerScreen(
|
||||
Box(
|
||||
modifier =
|
||||
if (blurActive) Modifier.layerBackdrop(blurBackdrop)
|
||||
else Modifier
|
||||
else Modifier,
|
||||
) {
|
||||
FileManagerPage(
|
||||
contentPadding = pagePadding,
|
||||
@@ -320,7 +287,7 @@ fun FileManagerScreen(
|
||||
viewModel.saveScrollPosition(
|
||||
currentPath,
|
||||
listState.firstVisibleItemIndex,
|
||||
listState.firstVisibleItemScrollOffset
|
||||
listState.firstVisibleItemScrollOffset,
|
||||
)
|
||||
viewModel.openEntry(entry)
|
||||
},
|
||||
@@ -356,30 +323,28 @@ fun FileManagerScreen(
|
||||
&& (!entry.isDirectory || selectedSnapshot != null),
|
||||
)
|
||||
}
|
||||
if (showPathDialog) {
|
||||
PathJumpDialog(
|
||||
show = showPathDialog,
|
||||
path = pathInput,
|
||||
onPathChange = { pathInput = it },
|
||||
onDismissRequest = { showPathDialog = false },
|
||||
onConfirm = {
|
||||
showPathDialog = false
|
||||
viewModel.jumpToPath(pathInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (showCreateFolderDialog) {
|
||||
CreateFolderDialog(
|
||||
show = showCreateFolderDialog,
|
||||
folderName = newFolderName,
|
||||
onFolderNameChange = { newFolderName = it },
|
||||
onDismissRequest = { showCreateFolderDialog = false },
|
||||
onConfirm = {
|
||||
showCreateFolderDialog = false
|
||||
viewModel.createFolder(newFolderName)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
PathJumpDialog(
|
||||
show = showPathDialog,
|
||||
path = pathInput,
|
||||
onPathChange = { pathInput = it },
|
||||
onDismissRequest = { showPathDialog = false },
|
||||
onConfirm = {
|
||||
showPathDialog = false
|
||||
viewModel.jumpToPath(pathInput)
|
||||
},
|
||||
)
|
||||
|
||||
CreateFolderDialog(
|
||||
show = showCreateFolderDialog,
|
||||
folderName = newFolderName,
|
||||
onFolderNameChange = { newFolderName = it },
|
||||
onDismissRequest = { showCreateFolderDialog = false },
|
||||
onConfirm = {
|
||||
showCreateFolderDialog = false
|
||||
viewModel.createFolder(newFolderName)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -430,17 +395,17 @@ private fun FileManagerPage(
|
||||
when {
|
||||
loading -> FileManagerStatusCard(
|
||||
message = stringResource(R.string.text_loading),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
errorText != null -> FileManagerStatusCard(
|
||||
message = stringResource(R.string.fm_load_failed, errorText),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
displayedEntries.isEmpty() -> FileManagerStatusCard(
|
||||
message = stringResource(R.string.fm_empty_dir),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -457,7 +422,7 @@ private fun FileManagerPage(
|
||||
items(fileRows) { rowEntries ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(UiSpacing.PageItem)
|
||||
horizontalArrangement = Arrangement.spacedBy(UiSpacing.PageItem),
|
||||
) {
|
||||
rowEntries.forEach { entry ->
|
||||
FileManagerItemCard(
|
||||
@@ -503,10 +468,15 @@ private fun FileManagerItemCard(
|
||||
onLongClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
Card(
|
||||
modifier = modifier
|
||||
.combinedClickable(
|
||||
onClick = onClick,
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
onClick()
|
||||
},
|
||||
onLongClick = onLongClick,
|
||||
),
|
||||
) {
|
||||
@@ -560,6 +530,8 @@ private fun FileDetailsBottomSheet(
|
||||
onDownload: () -> Unit,
|
||||
downloadEnabled: Boolean,
|
||||
) {
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
OverlayBottomSheet(
|
||||
show = show,
|
||||
title = stringResource(R.string.fm_file_details),
|
||||
@@ -567,7 +539,10 @@ private fun FileDetailsBottomSheet(
|
||||
onDismissFinished = onDismissFinished,
|
||||
startAction = {
|
||||
IconButton(
|
||||
onClick = onToggleRaw
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
onToggleRaw()
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
imageVector =
|
||||
@@ -575,14 +550,17 @@ private fun FileDetailsBottomSheet(
|
||||
else Icons.Rounded.RawOn,
|
||||
contentDescription = stringResource(
|
||||
if (!showingRaw) R.string.fm_show_raw
|
||||
else R.string.fm_show_parsed
|
||||
else R.string.fm_show_parsed,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
endAction = {
|
||||
IconButton(
|
||||
onClick = onDownload,
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
onDownload()
|
||||
},
|
||||
enabled = downloadEnabled,
|
||||
) {
|
||||
Icon(
|
||||
@@ -595,7 +573,7 @@ private fun FileDetailsBottomSheet(
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight(2f / 3f)
|
||||
.fillMaxHeight(2f / 3f),
|
||||
) {
|
||||
item {
|
||||
TextField(
|
||||
@@ -618,6 +596,8 @@ private fun PathJumpDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: () -> Unit,
|
||||
) {
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
OverlayDialog(
|
||||
show = show,
|
||||
title = stringResource(R.string.fm_goto_path),
|
||||
@@ -628,17 +608,26 @@ private fun PathJumpDialog(
|
||||
TextField(
|
||||
value = path,
|
||||
onValueChange = onPathChange,
|
||||
label = "/storage/emulated/0",
|
||||
useLabelAsPlaceholder = true,
|
||||
// label = "/storage/emulated/0",
|
||||
// useLabelAsPlaceholder = true,
|
||||
)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(UiSpacing.PageItem)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(UiSpacing.PageItem),
|
||||
) {
|
||||
TextButton(
|
||||
text = stringResource(R.string.button_cancel),
|
||||
onClick = onDismissRequest,
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
TextButton(
|
||||
text = stringResource(R.string.button_confirm),
|
||||
onClick = onConfirm,
|
||||
onClick = {
|
||||
haptic.confirm()
|
||||
onConfirm()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -653,6 +642,8 @@ private fun CreateFolderDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: () -> Unit,
|
||||
) {
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
OverlayDialog(
|
||||
show = show,
|
||||
title = stringResource(R.string.fm_title_create_folder),
|
||||
@@ -666,14 +657,23 @@ private fun CreateFolderDialog(
|
||||
label = stringResource(R.string.fm_label_new_folder),
|
||||
useLabelAsPlaceholder = true,
|
||||
)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(UiSpacing.PageItem)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(UiSpacing.PageItem),
|
||||
) {
|
||||
TextButton(
|
||||
text = stringResource(R.string.button_cancel),
|
||||
onClick = onDismissRequest,
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
TextButton(
|
||||
text = stringResource(R.string.fm_button_create),
|
||||
onClick = onConfirm,
|
||||
onClick = {
|
||||
haptic.confirm()
|
||||
onConfirm()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -695,13 +695,13 @@ private fun buildDetailsText(
|
||||
): String {
|
||||
val details = StringBuilder(
|
||||
if (showRaw) stat.rawOutput
|
||||
else FileManagerService.formatStatDetails(stat, directorySnapshot)
|
||||
else FileManagerService.formatStatDetails(stat, directorySnapshot),
|
||||
)
|
||||
if (targetStat != null) {
|
||||
details.append("\n\n${AppRuntime.stringResource(R.string.fm_stat_target_info)}\n")
|
||||
details.append(
|
||||
if (showRaw) targetStat.rawOutput
|
||||
else FileManagerService.formatStatDetails(targetStat)
|
||||
else FileManagerService.formatStatDetails(targetStat),
|
||||
)
|
||||
}
|
||||
return details.toString()
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
package io.github.miuzarte.scrcpyforandroid.pages
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
@@ -21,23 +11,13 @@ import androidx.compose.material.icons.rounded.Add
|
||||
import androidx.compose.material.icons.rounded.Android
|
||||
import androidx.compose.material.icons.rounded.DeleteOutline
|
||||
import androidx.compose.material.icons.rounded.Edit
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.platform.LocalResources
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -51,20 +31,10 @@ import io.github.miuzarte.scrcpyforandroid.miuix.SpinnerEntry
|
||||
import io.github.miuzarte.scrcpyforandroid.models.DeviceShortcuts
|
||||
import io.github.miuzarte.scrcpyforandroid.models.ScrcpyOptions.Crop
|
||||
import io.github.miuzarte.scrcpyforandroid.models.ScrcpyOptions.NewDisplay
|
||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.LazyColumn
|
||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.OverlaySpinnerWithFallback
|
||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.ReorderableList
|
||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.ArrowSlider
|
||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.SuperTextField
|
||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.*
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.ClientOptions
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.AudioSource
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.CameraFacing
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Codec
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.DisplayImePolicy
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.LogLevel
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Tick
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.VideoSource
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.*
|
||||
import io.github.miuzarte.scrcpyforandroid.services.AppRuntime
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.ScrcpyOptions
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.ScrcpyProfiles
|
||||
@@ -72,30 +42,10 @@ import io.github.miuzarte.scrcpyforandroid.storage.Settings
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Storage.quickDevices
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Storage.scrcpyOptions
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Storage.scrcpyProfiles
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.BlurredBar
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.LocalEnableBlur
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.rememberBlurBackdrop
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.*
|
||||
import io.github.miuzarte.scrcpyforandroid.widgets.RecordPreferences
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import top.yukonga.miuix.kmp.basic.ButtonDefaults
|
||||
import top.yukonga.miuix.kmp.basic.Card
|
||||
import top.yukonga.miuix.kmp.basic.DropdownEntry
|
||||
import top.yukonga.miuix.kmp.basic.DropdownItem
|
||||
import top.yukonga.miuix.kmp.basic.Icon
|
||||
import top.yukonga.miuix.kmp.basic.IconButton
|
||||
import top.yukonga.miuix.kmp.basic.Scaffold
|
||||
import top.yukonga.miuix.kmp.basic.ScrollBehavior
|
||||
import top.yukonga.miuix.kmp.basic.SnackbarHost
|
||||
import top.yukonga.miuix.kmp.basic.TabRow
|
||||
import top.yukonga.miuix.kmp.basic.Text
|
||||
import top.yukonga.miuix.kmp.basic.TextButton
|
||||
import top.yukonga.miuix.kmp.basic.TextField
|
||||
import top.yukonga.miuix.kmp.basic.TopAppBar
|
||||
import kotlinx.coroutines.*
|
||||
import top.yukonga.miuix.kmp.basic.*
|
||||
import top.yukonga.miuix.kmp.blur.layerBackdrop
|
||||
import top.yukonga.miuix.kmp.icon.MiuixIcons
|
||||
import top.yukonga.miuix.kmp.icon.extended.More
|
||||
@@ -142,7 +92,7 @@ internal fun ScrcpyAllOptionsScreen(
|
||||
soBundleShared
|
||||
else scrcpyProfilesState.profiles
|
||||
.firstOrNull { it.id == selectedProfileId }
|
||||
?.bundle ?: soBundleShared
|
||||
?.bundle ?: soBundleShared,
|
||||
)
|
||||
}
|
||||
val lastValidSoBundleState = rememberSaveable(selectedProfileId) {
|
||||
@@ -186,7 +136,7 @@ internal fun ScrcpyAllOptionsScreen(
|
||||
if (device.scrcpyProfileId == profileId)
|
||||
device.copy(scrcpyProfileId = ScrcpyOptions.GLOBAL_PROFILE_ID)
|
||||
else device
|
||||
}
|
||||
},
|
||||
)
|
||||
if (updated != shortcuts) {
|
||||
quickDevices.updateBundle { bundle ->
|
||||
@@ -235,9 +185,9 @@ internal fun ScrcpyAllOptionsScreen(
|
||||
onClick = {
|
||||
showManageProfilesSheet = true
|
||||
},
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = MiuixIcons.More,
|
||||
@@ -277,7 +227,7 @@ internal fun ScrcpyAllOptionsScreen(
|
||||
height = 48.dp,
|
||||
itemSpacing = UiSpacing.Medium,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -286,7 +236,7 @@ internal fun ScrcpyAllOptionsScreen(
|
||||
Box(
|
||||
modifier =
|
||||
if (blurActive) Modifier.layerBackdrop(blurBackdrop)
|
||||
else Modifier
|
||||
else Modifier,
|
||||
) {
|
||||
ScrcpyAllOptionsPage(
|
||||
contentPadding = contentPadding,
|
||||
@@ -606,7 +556,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
|
||||
val screenOffTimeoutPresetIndex = rememberSaveable(soBundle.screenOffTimeout) {
|
||||
ScrcpyPresets.ScreenOffTimeout.indexOfOrNearest(
|
||||
Tick(soBundle.screenOffTimeout).sec.toInt().coerceAtLeast(0)
|
||||
Tick(soBundle.screenOffTimeout).sec.toInt().coerceAtLeast(0),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -671,7 +621,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
SpinnerEntry(
|
||||
title = info.id,
|
||||
summary = info.type.s,
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -702,7 +652,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
SpinnerEntry(
|
||||
title = info.id,
|
||||
summary = info.type.s,
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -760,7 +710,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
},
|
||||
title = app.label?.takeIf { it.isNotBlank() } ?: app.packageName,
|
||||
summary = app.packageName,
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -893,13 +843,13 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = soBundle.turnScreenOff,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
turnScreenOff = it
|
||||
turnScreenOff = it,
|
||||
)
|
||||
if (it) AppRuntime.snackbar(
|
||||
// github.com/Genymobile/scrcpy/issues/3376
|
||||
// github.com/Genymobile/scrcpy/issues/4587
|
||||
// github.com/Genymobile/scrcpy/issues/5676
|
||||
R.string.scrcpyopt_turn_screen_off_note
|
||||
R.string.scrcpyopt_turn_screen_off_note,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -909,7 +859,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = !soBundle.control,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
control = !it
|
||||
control = !it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -919,7 +869,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = !soBundle.video,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
video = !it
|
||||
video = !it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -929,7 +879,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = !soBundle.videoPlayback,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
videoPlayback = !it
|
||||
videoPlayback = !it,
|
||||
)
|
||||
},
|
||||
enabled = soBundle.video,
|
||||
@@ -940,7 +890,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = !soBundle.audio,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
audio = !it
|
||||
audio = !it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -950,7 +900,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = !soBundle.audioPlayback,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
audioPlayback = !it
|
||||
audioPlayback = !it,
|
||||
)
|
||||
},
|
||||
enabled = soBundle.audio,
|
||||
@@ -968,7 +918,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
?.toLong()
|
||||
?.let(Tick::fromSec)
|
||||
?.value
|
||||
?: -1
|
||||
?: -1,
|
||||
)
|
||||
},
|
||||
valueRange = 0f..ScrcpyPresets.ScreenOffTimeout.lastIndex.toFloat(),
|
||||
@@ -989,7 +939,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
?.takeIf { value -> value > 0 }
|
||||
?.let(Tick::fromSec)
|
||||
?.value
|
||||
?: -1
|
||||
?: -1,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -999,7 +949,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = !soBundle.powerOn,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
powerOn = !it
|
||||
powerOn = !it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1009,7 +959,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = soBundle.powerOffOnClose,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
powerOffOnClose = it
|
||||
powerOffOnClose = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1019,7 +969,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = soBundle.stayAwake,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
stayAwake = it
|
||||
stayAwake = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1029,7 +979,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = soBundle.keepActive,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
keepActive = it
|
||||
keepActive = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1039,7 +989,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = soBundle.showTouches,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
showTouches = it
|
||||
showTouches = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1049,7 +999,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = soBundle.fullscreen,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
fullscreen = it
|
||||
fullscreen = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1059,10 +1009,10 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = soBundle.disableScreensaver,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
disableScreensaver = it
|
||||
disableScreensaver = it,
|
||||
)
|
||||
if (it) AppRuntime.snackbar(
|
||||
R.string.scrcpyopt_disable_screensaver_note
|
||||
R.string.scrcpyopt_disable_screensaver_note,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1081,7 +1031,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = soBundle.killAdbOnClose,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
killAdbOnClose = it
|
||||
killAdbOnClose = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1097,7 +1047,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
selectedIndex = audioCodecIndex,
|
||||
onSelectedIndexChange = {
|
||||
soBundle = soBundle.copy(
|
||||
audioCodec = Codec.AUDIO[it].string
|
||||
audioCodec = Codec.AUDIO[it].string,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1111,7 +1061,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
val idx = value.roundToInt()
|
||||
.coerceIn(0, ScrcpyPresets.AudioBitRate.lastIndex)
|
||||
soBundle = soBundle.copy(
|
||||
audioBitRate = ScrcpyPresets.AudioBitRate[idx] * 1000
|
||||
audioBitRate = ScrcpyPresets.AudioBitRate[idx] * 1000,
|
||||
)
|
||||
},
|
||||
valueRange = 0f..ScrcpyPresets.AudioBitRate.lastIndex.toFloat(),
|
||||
@@ -1129,7 +1079,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
?.takeIf { it >= 0 }
|
||||
?.let {
|
||||
soBundle = soBundle.copy(
|
||||
audioBitRate = it * 1000
|
||||
audioBitRate = it * 1000,
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -1142,7 +1092,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
selectedIndex = videoCodecIndex,
|
||||
onSelectedIndexChange = {
|
||||
soBundle = soBundle.copy(
|
||||
videoCodec = Codec.VIDEO[it].string
|
||||
videoCodec = Codec.VIDEO[it].string,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1152,7 +1102,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
value = soBundle.videoBitRate / 1_000_000f,
|
||||
onValueChange = { mbps ->
|
||||
soBundle = soBundle.copy(
|
||||
videoBitRate = (mbps * 10).roundToInt() * (1_000_000 / 10)
|
||||
videoBitRate = (mbps * 10).roundToInt() * (1_000_000 / 10),
|
||||
)
|
||||
},
|
||||
valueRange = 0f..40f,
|
||||
@@ -1182,7 +1132,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
raw.toFloatOrNull()?.let { parsed ->
|
||||
if (parsed >= 0f) {
|
||||
soBundle = soBundle.copy(
|
||||
videoBitRate = (parsed * 1_000_000f).roundToInt()
|
||||
videoBitRate = (parsed * 1_000_000f).roundToInt(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1200,7 +1150,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
selectedIndex = videoSourceIndex,
|
||||
onSelectedIndexChange = {
|
||||
soBundle = soBundle.copy(
|
||||
videoSource = VideoSource.entries[it].string
|
||||
videoSource = VideoSource.entries[it].string,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1236,7 +1186,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
soBundle = soBundle.copy(
|
||||
displayId =
|
||||
if (it == 0) -1
|
||||
else displays[it - 1].id
|
||||
else displays[it - 1].id,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1248,7 +1198,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
val idx = it.roundToInt()
|
||||
.coerceIn(0, ScrcpyPresets.MinSizeAlignment.lastIndex)
|
||||
soBundle = soBundle.copy(
|
||||
minSizeAlignment = ScrcpyPresets.MinSizeAlignment[idx]
|
||||
minSizeAlignment = ScrcpyPresets.MinSizeAlignment[idx],
|
||||
)
|
||||
},
|
||||
valueRange = 0f..ScrcpyPresets.MinSizeAlignment.lastIndex.toFloat(),
|
||||
@@ -1269,7 +1219,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
return@ArrowSlider
|
||||
}
|
||||
soBundle = soBundle.copy(
|
||||
minSizeAlignment = parsed
|
||||
minSizeAlignment = parsed,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1281,7 +1231,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
val idx = it.roundToInt()
|
||||
.coerceIn(0, ScrcpyPresets.MaxSize.lastIndex)
|
||||
soBundle = soBundle.copy(
|
||||
maxSize = ScrcpyPresets.MaxSize[idx]
|
||||
maxSize = ScrcpyPresets.MaxSize[idx],
|
||||
)
|
||||
},
|
||||
valueRange = 0f..ScrcpyPresets.MaxSize.lastIndex.toFloat(),
|
||||
@@ -1300,7 +1250,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
inputValueRange = 0f..UInt.MAX_VALUE.toFloat(),
|
||||
onInputConfirm = {
|
||||
soBundle = soBundle.copy(
|
||||
maxSize = it.toIntOrNull() ?: run { 0 }
|
||||
maxSize = it.toIntOrNull() ?: run { 0 },
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1314,7 +1264,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
soBundle = soBundle.copy(
|
||||
maxFps =
|
||||
if (idx == 0) ""
|
||||
else ScrcpyPresets.MaxFPS[idx].toString()
|
||||
else ScrcpyPresets.MaxFPS[idx].toString(),
|
||||
)
|
||||
},
|
||||
valueRange = 0f..ScrcpyPresets.MaxFPS.lastIndex.toFloat(),
|
||||
@@ -1330,7 +1280,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
inputValueRange = 0f..UShort.MAX_VALUE.toFloat(),
|
||||
onInputConfirm = {
|
||||
soBundle = soBundle.copy(
|
||||
maxFps = it
|
||||
maxFps = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1368,7 +1318,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
soBundle = soBundle.copy(
|
||||
cameraId =
|
||||
if (it == 0) ""
|
||||
else cameras[it - 1].id
|
||||
else cameras[it - 1].id,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1381,7 +1331,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
soBundle = soBundle.copy(
|
||||
cameraFacing =
|
||||
if (it == 0) ""
|
||||
else CameraFacing.entries[it].string
|
||||
else CameraFacing.entries[it].string,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1425,7 +1375,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
1 -> {
|
||||
// "自定义" - 进入自定义输入模式
|
||||
soBundle = soBundle.copy(
|
||||
cameraSizeUseCustom = true
|
||||
cameraSizeUseCustom = true,
|
||||
)
|
||||
cameraSizeCustomInput = ""
|
||||
}
|
||||
@@ -1452,11 +1402,11 @@ internal fun ScrcpyAllOptionsPage(
|
||||
// 输入的值存在于列表中, 取消自定义输入
|
||||
soBundle.copy(
|
||||
cameraSize = cameraSizeCustomInput,
|
||||
cameraSizeUseCustom = false
|
||||
cameraSizeUseCustom = false,
|
||||
)
|
||||
} else {
|
||||
soBundle.copy(
|
||||
cameraSizeCustom = cameraSizeCustomInput
|
||||
cameraSizeCustom = cameraSizeCustomInput,
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -1473,7 +1423,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
onValueChange = { cameraArInput = it },
|
||||
onFocusLost = {
|
||||
soBundle = soBundle.copy(
|
||||
cameraAr = cameraArInput
|
||||
cameraAr = cameraArInput,
|
||||
)
|
||||
},
|
||||
label = "--camera-ar",
|
||||
@@ -1487,7 +1437,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
onValueChange = { cameraZoomInput = it },
|
||||
onFocusLost = {
|
||||
soBundle = soBundle.copy(
|
||||
cameraZoom = cameraZoomInput
|
||||
cameraZoom = cameraZoomInput,
|
||||
)
|
||||
},
|
||||
label = "--camera-zoom",
|
||||
@@ -1504,7 +1454,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
val idx = value.roundToInt()
|
||||
.coerceIn(0, ScrcpyPresets.CameraFps.lastIndex)
|
||||
soBundle = soBundle.copy(
|
||||
cameraFps = ScrcpyPresets.CameraFps[idx]
|
||||
cameraFps = ScrcpyPresets.CameraFps[idx],
|
||||
)
|
||||
},
|
||||
valueRange = 0f..ScrcpyPresets.CameraFps.lastIndex.toFloat(),
|
||||
@@ -1521,7 +1471,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
inputValueRange = 0f..UShort.MAX_VALUE.toFloat(),
|
||||
onInputConfirm = {
|
||||
soBundle = soBundle.copy(
|
||||
cameraFps = it.toIntOrNull() ?: run { 0 }
|
||||
cameraFps = it.toIntOrNull() ?: run { 0 },
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1531,7 +1481,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = soBundle.cameraHighSpeed,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
cameraHighSpeed = it
|
||||
cameraHighSpeed = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1541,7 +1491,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = soBundle.cameraTorch,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
cameraTorch = it
|
||||
cameraTorch = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1560,7 +1510,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
selectedIndex = audioSourceIndex,
|
||||
onSelectedIndexChange = {
|
||||
soBundle = soBundle.copy(
|
||||
audioSource = AudioSource.entries[it].string
|
||||
audioSource = AudioSource.entries[it].string,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1570,7 +1520,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = soBundle.audioDup,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
audioDup = it
|
||||
audioDup = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1580,7 +1530,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = soBundle.requireAudio,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
requireAudio = it
|
||||
requireAudio = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1619,7 +1569,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
soBundle = soBundle.copy(
|
||||
videoEncoder =
|
||||
if (it == 0) ""
|
||||
else videoEncoders[it - 1].id
|
||||
else videoEncoders[it - 1].id,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1628,7 +1578,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
onValueChange = { videoCodecOptionsInput = it },
|
||||
onFocusLost = {
|
||||
soBundle = soBundle.copy(
|
||||
videoCodecOptions = videoCodecOptionsInput
|
||||
videoCodecOptions = videoCodecOptionsInput,
|
||||
)
|
||||
},
|
||||
label = "--video-codec-options",
|
||||
@@ -1667,7 +1617,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
soBundle = soBundle.copy(
|
||||
audioEncoder =
|
||||
if (it == 0) ""
|
||||
else audioEncoders[it - 1].id
|
||||
else audioEncoders[it - 1].id,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1676,7 +1626,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
onValueChange = { audioCodecOptionsInput = it },
|
||||
onFocusLost = {
|
||||
soBundle = soBundle.copy(
|
||||
audioCodecOptions = audioCodecOptionsInput
|
||||
audioCodecOptions = audioCodecOptionsInput,
|
||||
)
|
||||
},
|
||||
label = "--audio-codec-options",
|
||||
@@ -1699,7 +1649,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
soBundle = soBundle.copy(
|
||||
displayImePolicy =
|
||||
if (it == 0) ""
|
||||
else DisplayImePolicy.entries[it].string
|
||||
else DisplayImePolicy.entries[it].string,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1709,7 +1659,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = !soBundle.vdDestroyContent,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
vdDestroyContent = !it
|
||||
vdDestroyContent = !it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1719,7 +1669,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = !soBundle.vdSystemDecorations,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
vdSystemDecorations = !it
|
||||
vdSystemDecorations = !it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1729,10 +1679,10 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = !soBundle.downsizeOnError,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
downsizeOnError = !it
|
||||
downsizeOnError = !it,
|
||||
)
|
||||
if (it) AppRuntime.snackbar(
|
||||
R.string.scrcpyopt_no_downsize_on_error_desc
|
||||
R.string.scrcpyopt_no_downsize_on_error_desc,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1742,7 +1692,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = soBundle.legacyPaste,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
legacyPaste = it
|
||||
legacyPaste = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1761,7 +1711,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
1 -> ClientOptions.KeyInjectMode.PREFER_TEXT.string
|
||||
2 -> ClientOptions.KeyInjectMode.RAW.string
|
||||
else -> ClientOptions.KeyInjectMode.MIXED.string
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1771,7 +1721,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = !soBundle.forwardKeyRepeat,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
forwardKeyRepeat = !it
|
||||
forwardKeyRepeat = !it,
|
||||
)
|
||||
},
|
||||
enabled = soBundle.keyInjectMode != ClientOptions.KeyInjectMode.PREFER_TEXT.string,
|
||||
@@ -1782,7 +1732,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = !soBundle.clipboardAutosync,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
clipboardAutosync = !it
|
||||
clipboardAutosync = !it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1792,7 +1742,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = !soBundle.mouseHover,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
mouseHover = !it
|
||||
mouseHover = !it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1802,10 +1752,10 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = !soBundle.cleanup,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
cleanup = !it
|
||||
cleanup = !it,
|
||||
)
|
||||
if (it) AppRuntime.snackbar(
|
||||
R.string.scrcpyopt_no_cleanup_desc
|
||||
R.string.scrcpyopt_no_cleanup_desc,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1815,10 +1765,10 @@ internal fun ScrcpyAllOptionsPage(
|
||||
checked = soBundle.flexDisplay,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
flexDisplay = it
|
||||
flexDisplay = it,
|
||||
)
|
||||
if (it) AppRuntime.snackbar(
|
||||
"untested"
|
||||
"untested",
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1865,7 +1815,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
|
||||
1 -> {
|
||||
soBundle = soBundle.copy(
|
||||
startAppUseCustom = true
|
||||
startAppUseCustom = true,
|
||||
)
|
||||
startAppCustomInput = ""
|
||||
}
|
||||
@@ -1893,7 +1843,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
)
|
||||
} else {
|
||||
soBundle.copy(
|
||||
startAppCustom = startAppCustomInput
|
||||
startAppCustom = startAppCustomInput,
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -1935,8 +1885,8 @@ internal fun ScrcpyAllOptionsPage(
|
||||
newDisplay = NewDisplay.parseFrom(
|
||||
newDisplayWidthInput,
|
||||
newDisplayHeightInput,
|
||||
newDisplayDpiInput
|
||||
).toString()
|
||||
newDisplayDpiInput,
|
||||
).toString(),
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
@@ -1958,8 +1908,8 @@ internal fun ScrcpyAllOptionsPage(
|
||||
newDisplay = NewDisplay.parseFrom(
|
||||
newDisplayWidthInput,
|
||||
newDisplayHeightInput,
|
||||
newDisplayDpiInput
|
||||
).toString()
|
||||
newDisplayDpiInput,
|
||||
).toString(),
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
@@ -1981,8 +1931,8 @@ internal fun ScrcpyAllOptionsPage(
|
||||
newDisplay = NewDisplay.parseFrom(
|
||||
newDisplayWidthInput,
|
||||
newDisplayHeightInput,
|
||||
newDisplayDpiInput
|
||||
).toString()
|
||||
newDisplayDpiInput,
|
||||
).toString(),
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
@@ -2023,7 +1973,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
TextButton(
|
||||
text = stringResource(
|
||||
if (newDisplayWidthBlank || newDisplayHeightBlank) R.string.scrcpyopt_native
|
||||
else R.string.button_swap
|
||||
else R.string.button_swap,
|
||||
),
|
||||
onClick = {
|
||||
if (newDisplayWidthBlank || newDisplayHeightBlank) {
|
||||
@@ -2044,8 +1994,8 @@ internal fun ScrcpyAllOptionsPage(
|
||||
newDisplay = NewDisplay.parseFrom(
|
||||
newDisplayWidthInput,
|
||||
newDisplayHeightInput,
|
||||
newDisplayDpiInput
|
||||
).toString()
|
||||
newDisplayDpiInput,
|
||||
).toString(),
|
||||
)
|
||||
},
|
||||
modifier = Modifier.width(trailingButtonWidth),
|
||||
@@ -2086,8 +2036,8 @@ internal fun ScrcpyAllOptionsPage(
|
||||
cropWidthInput,
|
||||
cropHeightInput,
|
||||
cropXInput,
|
||||
cropYInput
|
||||
).toString()
|
||||
cropYInput,
|
||||
).toString(),
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
@@ -2110,8 +2060,8 @@ internal fun ScrcpyAllOptionsPage(
|
||||
cropWidthInput,
|
||||
cropHeightInput,
|
||||
cropXInput,
|
||||
cropYInput
|
||||
).toString()
|
||||
cropYInput,
|
||||
).toString(),
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
@@ -2139,8 +2089,8 @@ internal fun ScrcpyAllOptionsPage(
|
||||
cropWidthInput,
|
||||
cropHeightInput,
|
||||
cropXInput,
|
||||
cropYInput
|
||||
).toString()
|
||||
cropYInput,
|
||||
).toString(),
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
@@ -2163,8 +2113,8 @@ internal fun ScrcpyAllOptionsPage(
|
||||
cropWidthInput,
|
||||
cropHeightInput,
|
||||
cropXInput,
|
||||
cropYInput
|
||||
).toString()
|
||||
cropYInput,
|
||||
).toString(),
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
@@ -2193,7 +2143,7 @@ internal fun ScrcpyAllOptionsPage(
|
||||
selectedIndex = logLevelIndex,
|
||||
onSelectedIndexChange = {
|
||||
soBundle = soBundle.copy(
|
||||
logLevel = LogLevel.entries[it].string
|
||||
logLevel = LogLevel.entries[it].string,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -2218,6 +2168,8 @@ private fun ProfileNameDialog(
|
||||
onConfirm: (String, String?) -> Unit,
|
||||
) {
|
||||
if (mode == null) return
|
||||
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
val textDefault = stringResource(R.string.text_default)
|
||||
@@ -2282,12 +2234,18 @@ private fun ProfileNameDialog(
|
||||
) {
|
||||
TextButton(
|
||||
text = stringResource(R.string.button_cancel),
|
||||
onClick = onDismissRequest,
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
onDismissRequest()
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
TextButton(
|
||||
text = stringResource(R.string.button_confirm),
|
||||
onClick = { onConfirm(input, copySourceProfileId) },
|
||||
onClick = {
|
||||
haptic.confirm()
|
||||
onConfirm(input, copySourceProfileId)
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
colors = ButtonDefaults.textButtonColorsPrimary(),
|
||||
)
|
||||
@@ -2345,14 +2303,14 @@ private fun ManageProfilesSheet(
|
||||
icon = Icons.Rounded.Edit,
|
||||
contentDescription = textRename,
|
||||
onClick = { onRenameProfile(profile.id) },
|
||||
)
|
||||
),
|
||||
)
|
||||
add(
|
||||
ReorderableList.EndAction.Icon(
|
||||
icon = Icons.Rounded.DeleteOutline,
|
||||
contentDescription = textDelete,
|
||||
onClick = { onDeleteProfile(profile.id) },
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -2372,9 +2330,10 @@ private fun DeleteProfileDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: () -> Unit,
|
||||
) {
|
||||
if (!show) return
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
OverlayDialog(
|
||||
show = true,
|
||||
show = show,
|
||||
title = stringResource(R.string.scrcpyopt_delete_profile),
|
||||
summary = stringResource(R.string.scrcpyopt_delete_confirm, profileName),
|
||||
defaultWindowInsetsPadding = false,
|
||||
@@ -2385,12 +2344,18 @@ private fun DeleteProfileDialog(
|
||||
) {
|
||||
TextButton(
|
||||
text = stringResource(R.string.button_cancel),
|
||||
onClick = onDismissRequest,
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
onDismissRequest()
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
TextButton(
|
||||
text = stringResource(R.string.button_delete),
|
||||
onClick = onConfirm,
|
||||
onClick = {
|
||||
haptic.confirm()
|
||||
onConfirm()
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
colors = ButtonDefaults.textButtonColorsPrimary(),
|
||||
)
|
||||
|
||||
@@ -9,32 +9,18 @@ import androidx.activity.compose.LocalActivity
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Clear
|
||||
import androidx.compose.material.icons.rounded.FileOpen
|
||||
import androidx.compose.material.icons.rounded.Refresh
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.Dp
|
||||
@@ -45,9 +31,9 @@ import io.github.miuzarte.scrcpyforandroid.MainActivity
|
||||
import io.github.miuzarte.scrcpyforandroid.R
|
||||
import io.github.miuzarte.scrcpyforandroid.constants.UiSpacing
|
||||
import io.github.miuzarte.scrcpyforandroid.nativecore.DirectAdbTransport
|
||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.ArrowSlider
|
||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.LazyColumn
|
||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.SectionSmallTitle
|
||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.ArrowSlider
|
||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.SuperTextField
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy
|
||||
import io.github.miuzarte.scrcpyforandroid.services.AppRuntime
|
||||
@@ -57,26 +43,9 @@ import io.github.miuzarte.scrcpyforandroid.storage.AppSettings.FullscreenVirtual
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Settings
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Storage.adbClientData
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Storage.appSettings
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.BlurredBar
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.LocalEnableBlur
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.MonetKeyColorOptions
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.rememberBlurBackdrop
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import top.yukonga.miuix.kmp.basic.Card
|
||||
import top.yukonga.miuix.kmp.basic.DropdownEntry
|
||||
import top.yukonga.miuix.kmp.basic.DropdownItem
|
||||
import top.yukonga.miuix.kmp.basic.Icon
|
||||
import top.yukonga.miuix.kmp.basic.IconButton
|
||||
import top.yukonga.miuix.kmp.basic.Scaffold
|
||||
import top.yukonga.miuix.kmp.basic.ScrollBehavior
|
||||
import top.yukonga.miuix.kmp.basic.Text
|
||||
import top.yukonga.miuix.kmp.basic.TextField
|
||||
import top.yukonga.miuix.kmp.basic.TopAppBar
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.*
|
||||
import kotlinx.coroutines.*
|
||||
import top.yukonga.miuix.kmp.basic.*
|
||||
import top.yukonga.miuix.kmp.blur.layerBackdrop
|
||||
import top.yukonga.miuix.kmp.preference.ArrowPreference
|
||||
import top.yukonga.miuix.kmp.preference.OverlayDropdownPreference
|
||||
@@ -172,6 +141,7 @@ fun SettingsPage(
|
||||
val taskScope = remember { CoroutineScope(SupervisorJob() + Dispatchers.IO) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val navigator = LocalRootNavigator.current
|
||||
val serverPicker = LocalServerPicker.current
|
||||
val terminalFontPicker = LocalTerminalFontPicker.current
|
||||
@@ -215,19 +185,19 @@ fun SettingsPage(
|
||||
var serverRemotePathInput by rememberSaveable(asBundle.serverRemotePath) {
|
||||
mutableStateOf(
|
||||
if (asBundle.serverRemotePath == AppSettings.SERVER_REMOTE_PATH.defaultValue) ""
|
||||
else asBundle.serverRemotePath
|
||||
else asBundle.serverRemotePath,
|
||||
)
|
||||
}
|
||||
|
||||
var adbKeyNameInput by rememberSaveable(asBundle.adbKeyName) {
|
||||
mutableStateOf(
|
||||
if (asBundle.adbKeyName == AppSettings.ADB_KEY_NAME.defaultValue) ""
|
||||
else asBundle.adbKeyName
|
||||
else asBundle.adbKeyName,
|
||||
)
|
||||
}
|
||||
|
||||
val adbPrivateKeyPicker = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.OpenDocument()
|
||||
ActivityResultContracts.OpenDocument(),
|
||||
) { uri: Uri? ->
|
||||
if (uri == null) return@rememberLauncherForActivityResult
|
||||
scope.launch {
|
||||
@@ -247,7 +217,7 @@ fun SettingsPage(
|
||||
}
|
||||
}
|
||||
val adbPublicKeyPicker = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.OpenDocument()
|
||||
ActivityResultContracts.OpenDocument(),
|
||||
) { uri: Uri? ->
|
||||
if (uri == null) return@rememberLauncherForActivityResult
|
||||
scope.launch {
|
||||
@@ -310,7 +280,7 @@ fun SettingsPage(
|
||||
selected = lang.second == asBundle.languageTag,
|
||||
onClick = {
|
||||
asBundle = asBundle.copy(
|
||||
languageTag = lang.second
|
||||
languageTag = lang.second,
|
||||
)
|
||||
MainActivity.setAppLanguageTag(context, lang.second)
|
||||
activity?.recreate()
|
||||
@@ -328,7 +298,7 @@ fun SettingsPage(
|
||||
.coerceIn(0, AppSettings.ThemeModes.baseOptions.lastIndex),
|
||||
onSelectedIndexChange = {
|
||||
asBundle = asBundle.copy(
|
||||
themeBaseIndex = it
|
||||
themeBaseIndex = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -338,7 +308,7 @@ fun SettingsPage(
|
||||
checked = asBundle.monet,
|
||||
onCheckedChange = {
|
||||
asBundle = asBundle.copy(
|
||||
monet = it
|
||||
monet = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -352,7 +322,7 @@ fun SettingsPage(
|
||||
.coerceIn(0, MonetKeyColorOptions.lastIndex),
|
||||
onSelectedIndexChange = {
|
||||
asBundle = asBundle.copy(
|
||||
monetSeedIndex = it
|
||||
monetSeedIndex = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -368,7 +338,7 @@ fun SettingsPage(
|
||||
.coerceIn(0, monetPaletteStyleOptions.lastIndex),
|
||||
onSelectedIndexChange = {
|
||||
asBundle = asBundle.copy(
|
||||
monetPaletteStyle = it
|
||||
monetPaletteStyle = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -380,7 +350,7 @@ fun SettingsPage(
|
||||
.coerceIn(0, monetColorSpecOptions.lastIndex),
|
||||
onSelectedIndexChange = {
|
||||
asBundle = asBundle.copy(
|
||||
monetColorSpec = it
|
||||
monetColorSpec = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -392,9 +362,9 @@ fun SettingsPage(
|
||||
checked = asBundle.blur,
|
||||
onCheckedChange = {
|
||||
asBundle = asBundle.copy(
|
||||
blur = it
|
||||
blur = it,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.pref_title_floating_bottom_bar),
|
||||
@@ -402,9 +372,9 @@ fun SettingsPage(
|
||||
checked = asBundle.floatingBottomBar,
|
||||
onCheckedChange = {
|
||||
asBundle = asBundle.copy(
|
||||
floatingBottomBar = it
|
||||
floatingBottomBar = it,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
AnimatedVisibility(asBundle.floatingBottomBar && asBundle.blur) {
|
||||
Column {
|
||||
@@ -415,9 +385,9 @@ fun SettingsPage(
|
||||
&& asBundle.floatingBottomBarBlur,
|
||||
onCheckedChange = {
|
||||
asBundle = asBundle.copy(
|
||||
floatingBottomBarBlur = it
|
||||
floatingBottomBarBlur = it,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -435,7 +405,7 @@ fun SettingsPage(
|
||||
onCheckedChange = {
|
||||
if (!isScrcpyStreaming)
|
||||
asBundle = asBundle.copy(
|
||||
lowLatency = it
|
||||
lowLatency = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -445,7 +415,7 @@ fun SettingsPage(
|
||||
checked = asBundle.fullscreenDebugInfo,
|
||||
onCheckedChange = {
|
||||
asBundle = asBundle.copy(
|
||||
fullscreenDebugInfo = it
|
||||
fullscreenDebugInfo = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -455,7 +425,7 @@ fun SettingsPage(
|
||||
checked = asBundle.hideSimpleConfigItems,
|
||||
onCheckedChange = {
|
||||
asBundle = asBundle.copy(
|
||||
hideSimpleConfigItems = it
|
||||
hideSimpleConfigItems = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -466,7 +436,7 @@ fun SettingsPage(
|
||||
onValueChange = {
|
||||
asBundle = asBundle.copy(
|
||||
devicePreviewCardHeightDp =
|
||||
it.roundToInt().coerceAtLeast(120)
|
||||
it.roundToInt().coerceAtLeast(120),
|
||||
)
|
||||
},
|
||||
valueRange = 160f..600f,
|
||||
@@ -479,7 +449,7 @@ fun SettingsPage(
|
||||
onInputConfirm = { input ->
|
||||
input.toIntOrNull()?.let {
|
||||
asBundle = asBundle.copy(
|
||||
devicePreviewCardHeightDp = it.coerceAtLeast(120)
|
||||
devicePreviewCardHeightDp = it.coerceAtLeast(120),
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -487,17 +457,24 @@ fun SettingsPage(
|
||||
ArrowPreference(
|
||||
title = stringResource(R.string.pref_title_quick_device_sort),
|
||||
summary = stringResource(R.string.pref_summary_quick_device_sort),
|
||||
onClick = onOpenReorderDevices,
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
onOpenReorderDevices()
|
||||
},
|
||||
)
|
||||
ArrowPreference(
|
||||
title = stringResource(R.string.pref_title_virtual_button_sort),
|
||||
summary = stringResource(R.string.pref_summary_virtual_button_sort),
|
||||
onClick = { navigator.push(RootScreen.VirtualButtonOrder) },
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
navigator.push(RootScreen.VirtualButtonOrder)
|
||||
},
|
||||
)
|
||||
ArrowPreference(
|
||||
title = stringResource(R.string.pref_title_password_autofill),
|
||||
summary = stringResource(R.string.pref_summary_password_autofill),
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
context.startActivity(LockscreenPasswordActivity.createIntent(context))
|
||||
},
|
||||
)
|
||||
@@ -507,7 +484,7 @@ fun SettingsPage(
|
||||
checked = asBundle.realtimeClipboardSyncToDevice,
|
||||
onCheckedChange = {
|
||||
asBundle = asBundle.copy(
|
||||
realtimeClipboardSyncToDevice = it
|
||||
realtimeClipboardSyncToDevice = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -523,7 +500,7 @@ fun SettingsPage(
|
||||
checked = asBundle.fullscreenControlIgnoreSystemRotationLock,
|
||||
onCheckedChange = {
|
||||
asBundle = asBundle.copy(
|
||||
fullscreenControlIgnoreSystemRotationLock = it
|
||||
fullscreenControlIgnoreSystemRotationLock = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -533,7 +510,7 @@ fun SettingsPage(
|
||||
checked = asBundle.fullscreenControlBackToDevice,
|
||||
onCheckedChange = {
|
||||
asBundle = asBundle.copy(
|
||||
fullscreenControlBackToDevice = it
|
||||
fullscreenControlBackToDevice = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -543,7 +520,7 @@ fun SettingsPage(
|
||||
checked = asBundle.showFullscreenVirtualButtons,
|
||||
onCheckedChange = {
|
||||
asBundle = asBundle.copy(
|
||||
showFullscreenVirtualButtons = it
|
||||
showFullscreenVirtualButtons = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -564,7 +541,7 @@ fun SettingsPage(
|
||||
modeIndex = index,
|
||||
directionIndex = fullscreenVirtualButtonDock.directionIndex,
|
||||
)
|
||||
.toStoredValue()
|
||||
.toStoredValue(),
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -583,7 +560,7 @@ fun SettingsPage(
|
||||
modeIndex = fullscreenVirtualButtonDock.modeIndex,
|
||||
directionIndex = index,
|
||||
)
|
||||
.toStoredValue()
|
||||
.toStoredValue(),
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -593,7 +570,7 @@ fun SettingsPage(
|
||||
title = stringResource(R.string.pref_title_virtual_button_direction),
|
||||
summary = stringResource(
|
||||
if (fullscreenVirtualButtonDock.isFixed) R.string.dock_fixed
|
||||
else R.string.dock_follow
|
||||
else R.string.dock_follow,
|
||||
) +
|
||||
stringResource(R.string.dock_display_on) +
|
||||
stringResource(fullscreenVirtualButtonDock.directionLabelResId),
|
||||
@@ -604,7 +581,7 @@ fun SettingsPage(
|
||||
onValueChange = {
|
||||
asBundle = asBundle.copy(
|
||||
fullscreenVirtualButtonHeightDp =
|
||||
it.roundToInt().coerceIn(16, 80)
|
||||
it.roundToInt().coerceIn(16, 80),
|
||||
)
|
||||
},
|
||||
valueRange = 16f..80f,
|
||||
@@ -618,7 +595,7 @@ fun SettingsPage(
|
||||
input.toIntOrNull()?.let {
|
||||
asBundle = asBundle.copy(
|
||||
fullscreenVirtualButtonHeightDp =
|
||||
it.coerceIn(1, 160)
|
||||
it.coerceIn(1, 160),
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -631,7 +608,7 @@ fun SettingsPage(
|
||||
checked = asBundle.showFullscreenFloatingButton,
|
||||
onCheckedChange = {
|
||||
asBundle = asBundle.copy(
|
||||
showFullscreenFloatingButton = it
|
||||
showFullscreenFloatingButton = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -643,7 +620,7 @@ fun SettingsPage(
|
||||
onValueChange = {
|
||||
asBundle = asBundle.copy(
|
||||
fullscreenFloatingButtonSizeDp =
|
||||
it.roundToInt().coerceIn(32, 64)
|
||||
it.roundToInt().coerceIn(32, 64),
|
||||
)
|
||||
},
|
||||
valueRange = 32f..64f,
|
||||
@@ -657,7 +634,7 @@ fun SettingsPage(
|
||||
input.toIntOrNull()?.let {
|
||||
asBundle = asBundle.copy(
|
||||
fullscreenFloatingButtonSizeDp =
|
||||
it.coerceIn(16, 96)
|
||||
it.coerceIn(16, 96),
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -668,7 +645,7 @@ fun SettingsPage(
|
||||
onValueChange = {
|
||||
asBundle = asBundle.copy(
|
||||
fullscreenFloatingButtonBackgroundAlphaPercent =
|
||||
it.roundToInt().coerceIn(10, 100)
|
||||
it.roundToInt().coerceIn(10, 100),
|
||||
)
|
||||
},
|
||||
valueRange = 10f..100f,
|
||||
@@ -682,7 +659,7 @@ fun SettingsPage(
|
||||
input.toIntOrNull()?.let {
|
||||
asBundle = asBundle.copy(
|
||||
fullscreenFloatingButtonBackgroundAlphaPercent =
|
||||
it.coerceIn(10, 100)
|
||||
it.coerceIn(10, 100),
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -693,7 +670,7 @@ fun SettingsPage(
|
||||
onValueChange = {
|
||||
asBundle = asBundle.copy(
|
||||
fullscreenFloatingButtonRingAlphaPercent =
|
||||
it.roundToInt().coerceIn(0, 100)
|
||||
it.roundToInt().coerceIn(0, 100),
|
||||
)
|
||||
},
|
||||
valueRange = 0f..100f,
|
||||
@@ -707,7 +684,7 @@ fun SettingsPage(
|
||||
input.toIntOrNull()?.let {
|
||||
asBundle = asBundle.copy(
|
||||
fullscreenFloatingButtonRingAlphaPercent =
|
||||
it.coerceIn(0, 100)
|
||||
it.coerceIn(0, 100),
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -720,7 +697,7 @@ fun SettingsPage(
|
||||
checked = asBundle.fullscreenCompatibilityMode,
|
||||
onCheckedChange = {
|
||||
asBundle = asBundle.copy(
|
||||
fullscreenCompatibilityMode = it
|
||||
fullscreenCompatibilityMode = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -752,11 +729,12 @@ fun SettingsPage(
|
||||
trailingIcon = {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(end = UiSpacing.Medium)
|
||||
.padding(end = UiSpacing.Medium),
|
||||
) {
|
||||
if (asBundle.customServerUri.isNotBlank())
|
||||
IconButton(
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
asBundle = asBundle.copy(
|
||||
customServerUri = "",
|
||||
customServerVersion = "",
|
||||
@@ -768,7 +746,12 @@ fun SettingsPage(
|
||||
contentDescription = stringResource(R.string.cd_clear),
|
||||
)
|
||||
}
|
||||
IconButton(onClick = serverPicker.pick) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
serverPicker.pick()
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.FileOpen,
|
||||
contentDescription = stringResource(R.string.cd_select_file),
|
||||
@@ -795,7 +778,7 @@ fun SettingsPage(
|
||||
customServerVersionInput = ""
|
||||
asBundle = asBundle.copy(
|
||||
customServerVersion = customServerVersionInput
|
||||
.ifBlank { AppSettings.CUSTOM_SERVER_VERSION.defaultValue }
|
||||
.ifBlank { AppSettings.CUSTOM_SERVER_VERSION.defaultValue },
|
||||
)
|
||||
},
|
||||
label = Scrcpy.DEFAULT_SERVER_VERSION,
|
||||
@@ -821,7 +804,7 @@ fun SettingsPage(
|
||||
serverRemotePathInput = ""
|
||||
asBundle = asBundle.copy(
|
||||
serverRemotePath = serverRemotePathInput
|
||||
.ifBlank { AppSettings.SERVER_REMOTE_PATH.defaultValue }
|
||||
.ifBlank { AppSettings.SERVER_REMOTE_PATH.defaultValue },
|
||||
)
|
||||
},
|
||||
label = Scrcpy.DEFAULT_REMOTE_PATH,
|
||||
@@ -842,6 +825,7 @@ fun SettingsPage(
|
||||
title = stringResource(R.string.pref_title_battery_optimization),
|
||||
summary = stringResource(R.string.pref_summary_battery_optimization),
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
val appInfoArgs = Bundle().apply {
|
||||
putString("package", context.packageName)
|
||||
putInt("uid", context.applicationInfo.uid)
|
||||
@@ -866,10 +850,10 @@ fun SettingsPage(
|
||||
}
|
||||
val requestIntent = Intent(
|
||||
AndroidSettings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
|
||||
"package:${context.packageName}".toUri()
|
||||
"package:${context.packageName}".toUri(),
|
||||
)
|
||||
val fallbackIntent = Intent(
|
||||
AndroidSettings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS
|
||||
AndroidSettings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS,
|
||||
)
|
||||
runCatching { context.startActivity(appDetailsIntent) }
|
||||
.recoverCatching { context.startActivity(requestIntent) }
|
||||
@@ -897,7 +881,7 @@ fun SettingsPage(
|
||||
adbKeyNameInput = ""
|
||||
asBundle = asBundle.copy(
|
||||
adbKeyName = adbKeyNameInput
|
||||
.ifBlank { AppSettings.ADB_KEY_NAME.defaultValue }
|
||||
.ifBlank { AppSettings.ADB_KEY_NAME.defaultValue },
|
||||
)
|
||||
},
|
||||
label = AppSettings.ADB_KEY_NAME.defaultValue,
|
||||
@@ -931,7 +915,7 @@ fun SettingsPage(
|
||||
R.string.pref_adb_key_imported
|
||||
|
||||
else -> R.string.pref_adb_key_generated
|
||||
}
|
||||
},
|
||||
),
|
||||
useLabelAsPlaceholder = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -939,6 +923,7 @@ fun SettingsPage(
|
||||
Row(modifier = Modifier.padding(end = UiSpacing.Medium)) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
scope.launch {
|
||||
runCatching {
|
||||
withContext(Dispatchers.IO) {
|
||||
@@ -972,7 +957,10 @@ fun SettingsPage(
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { adbPrivateKeyPicker.launch(arrayOf("*/*")) },
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
adbPrivateKeyPicker.launch(arrayOf("*/*"))
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.FileOpen,
|
||||
@@ -997,7 +985,7 @@ fun SettingsPage(
|
||||
R.string.pref_adb_key_imported
|
||||
|
||||
else -> R.string.pref_adb_key_generated
|
||||
}
|
||||
},
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.pref_title_adb_public_key),
|
||||
@@ -1013,7 +1001,10 @@ fun SettingsPage(
|
||||
trailingIcon = {
|
||||
Row(modifier = Modifier.padding(end = UiSpacing.Medium)) {
|
||||
IconButton(
|
||||
onClick = { adbPublicKeyPicker.launch(arrayOf("*/*")) },
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
adbPublicKeyPicker.launch(arrayOf("*/*"))
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.FileOpen,
|
||||
@@ -1031,7 +1022,7 @@ fun SettingsPage(
|
||||
checked = asBundle.adbPairingAutoDiscoverOnDialogOpen,
|
||||
onCheckedChange = {
|
||||
asBundle = asBundle.copy(
|
||||
adbPairingAutoDiscoverOnDialogOpen = it
|
||||
adbPairingAutoDiscoverOnDialogOpen = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1041,7 +1032,7 @@ fun SettingsPage(
|
||||
checked = asBundle.adbAutoReconnectPairedDevice,
|
||||
onCheckedChange = {
|
||||
asBundle = asBundle.copy(
|
||||
adbAutoReconnectPairedDevice = it
|
||||
adbAutoReconnectPairedDevice = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1051,10 +1042,10 @@ fun SettingsPage(
|
||||
checked = asBundle.adbAutoLoadAppListOnConnect,
|
||||
onCheckedChange = {
|
||||
asBundle = asBundle.copy(
|
||||
adbAutoLoadAppListOnConnect = it
|
||||
adbAutoLoadAppListOnConnect = it,
|
||||
)
|
||||
if (it) AppRuntime.snackbar(
|
||||
R.string.pref_warning_list_apps
|
||||
R.string.pref_warning_list_apps,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1070,7 +1061,7 @@ fun SettingsPage(
|
||||
value = asBundle.terminalFontSizeSp,
|
||||
onValueChange = {
|
||||
asBundle = asBundle.copy(
|
||||
terminalFontSizeSp = it.roundToInt().toFloat()
|
||||
terminalFontSizeSp = it.roundToInt().toFloat(),
|
||||
)
|
||||
},
|
||||
valueRange = 1f..32f,
|
||||
@@ -1083,7 +1074,7 @@ fun SettingsPage(
|
||||
onInputConfirm = { input ->
|
||||
input.toIntOrNull()?.let {
|
||||
asBundle = asBundle.copy(
|
||||
terminalFontSizeSp = it.coerceIn(1, 32).toFloat()
|
||||
terminalFontSizeSp = it.coerceIn(1, 32).toFloat(),
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -1112,14 +1103,15 @@ fun SettingsPage(
|
||||
if (asBundle.terminalFontDisplayName.isNotBlank()) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
scope.launch {
|
||||
val cleared = clearTerminalFont(context)
|
||||
asBundle = asBundle.copy(
|
||||
terminalFontDisplayName = ""
|
||||
terminalFontDisplayName = "",
|
||||
)
|
||||
AppRuntime.snackbar(
|
||||
if (cleared) R.string.pref_font_restored
|
||||
else R.string.pref_no_custom_font
|
||||
else R.string.pref_no_custom_font,
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -1130,7 +1122,12 @@ fun SettingsPage(
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(onClick = terminalFontPicker.pick) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
terminalFontPicker.pick()
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.FileOpen,
|
||||
contentDescription = stringResource(R.string.cd_select_font),
|
||||
@@ -1153,7 +1150,7 @@ fun SettingsPage(
|
||||
checked = asBundle.clearLogsOnExit,
|
||||
onCheckedChange = {
|
||||
asBundle = asBundle.copy(
|
||||
clearLogsOnExit = it
|
||||
clearLogsOnExit = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1163,7 +1160,7 @@ fun SettingsPage(
|
||||
checked = asBundle.hideDeviceLogs,
|
||||
onCheckedChange = {
|
||||
asBundle = asBundle.copy(
|
||||
hideDeviceLogs = it
|
||||
hideDeviceLogs = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -1176,7 +1173,10 @@ fun SettingsPage(
|
||||
ArrowPreference(
|
||||
title = stringResource(R.string.about_title),
|
||||
summary = updateSummary,
|
||||
onClick = { navigator.push(RootScreen.About) },
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
navigator.push(RootScreen.About)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.github.miuzarte.scrcpyforandroid.R
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.confirm
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.contextClick
|
||||
import top.yukonga.miuix.kmp.basic.ButtonDefaults
|
||||
import top.yukonga.miuix.kmp.basic.Slider
|
||||
@@ -174,7 +175,7 @@ private fun SliderInputDialog(
|
||||
TextButton(
|
||||
text = stringResource(R.string.button_confirm),
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
haptic.confirm()
|
||||
val inputValue = text.toFloatOrNull() ?: 0f
|
||||
if (inputValue >= inputValueRange.start && inputValue <= inputValueRange.endInclusive) {
|
||||
onConfirm(text.trim())
|
||||
|
||||
@@ -10,44 +10,16 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
||||
import androidx.compose.foundation.relocation.bringIntoViewRequester
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.AddLink
|
||||
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.Wifi
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.material.icons.rounded.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
@@ -89,6 +61,7 @@ import io.github.miuzarte.scrcpyforandroid.storage.ScrcpyOptions
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Settings
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Storage
|
||||
import io.github.miuzarte.scrcpyforandroid.storage.Storage.scrcpyOptions
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.confirm
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.contextClick
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -98,21 +71,9 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import top.yukonga.miuix.kmp.basic.Button
|
||||
import top.yukonga.miuix.kmp.basic.ButtonDefaults
|
||||
import top.yukonga.miuix.kmp.basic.Card
|
||||
import top.yukonga.miuix.kmp.basic.CardDefaults
|
||||
import top.yukonga.miuix.kmp.basic.CircularProgressIndicator
|
||||
import top.yukonga.miuix.kmp.basic.Icon
|
||||
import top.yukonga.miuix.kmp.basic.Text
|
||||
import top.yukonga.miuix.kmp.basic.TextButton
|
||||
import top.yukonga.miuix.kmp.basic.TextField
|
||||
import top.yukonga.miuix.kmp.basic.*
|
||||
import top.yukonga.miuix.kmp.overlay.OverlayDialog
|
||||
import top.yukonga.miuix.kmp.preference.ArrowPreference
|
||||
import top.yukonga.miuix.kmp.preference.CheckboxLocation
|
||||
import top.yukonga.miuix.kmp.preference.CheckboxPreference
|
||||
import top.yukonga.miuix.kmp.preference.OverlayDropdownPreference
|
||||
import top.yukonga.miuix.kmp.preference.SwitchPreference
|
||||
import top.yukonga.miuix.kmp.preference.*
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme.isDynamicColor
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme.textStyles
|
||||
@@ -230,6 +191,8 @@ internal fun PairingCard(
|
||||
onDiscoverTarget: (suspend () -> Pair<String, Int>?)? = null,
|
||||
onPair: (host: String, port: String, code: String) -> Unit,
|
||||
) {
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
val showPairDialog = remember { mutableStateOf(false) }
|
||||
val holdDownState = remember { mutableStateOf(false) }
|
||||
|
||||
@@ -237,6 +200,7 @@ internal fun PairingCard(
|
||||
ArrowPreference(
|
||||
title = stringResource(R.string.device_pairing_title),
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
showPairDialog.value = true
|
||||
holdDownState.value = true
|
||||
},
|
||||
@@ -295,7 +259,7 @@ internal fun PreviewCard(
|
||||
var nextPointerLabel by rememberSaveable { mutableIntStateOf(1) }
|
||||
val alpha by animateFloatAsState(
|
||||
if (previewControlsVisible) 1f else 0f,
|
||||
label = "preview-controls"
|
||||
label = "preview-controls",
|
||||
)
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||
@@ -361,7 +325,7 @@ internal fun PreviewCard(
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.bringIntoViewRequester(bringIntoViewRequester)
|
||||
.then(modifier)
|
||||
.then(modifier),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -373,17 +337,20 @@ internal fun PreviewCard(
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> onTouchActiveChanged(true)
|
||||
MotionEvent.ACTION_UP,
|
||||
MotionEvent.ACTION_CANCEL -> onTouchActiveChanged(false)
|
||||
MotionEvent.ACTION_CANCEL,
|
||||
-> onTouchActiveChanged(false)
|
||||
}
|
||||
touchEventHandler.handleMotionEvent(event)
|
||||
}
|
||||
} else {
|
||||
Modifier.pointerInput(sessionInfo) {
|
||||
detectTapGestures(onTap = {
|
||||
previewControlsVisible = !previewControlsVisible
|
||||
})
|
||||
detectTapGestures(
|
||||
onTap = {
|
||||
previewControlsVisible = !previewControlsVisible
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.onSizeChanged { touchAreaSize = it },
|
||||
) {
|
||||
@@ -496,7 +463,6 @@ internal fun ConfigPanel(
|
||||
recentTasksEndActionText: String,
|
||||
onOpenRecentTasks: () -> Unit,
|
||||
onOpenAdvanced: () -> Unit,
|
||||
onStartStopHaptic: () -> Unit,
|
||||
onStart: () -> Unit,
|
||||
onStop: () -> Unit,
|
||||
sessionInfo: Scrcpy.Session.SessionInfo?,
|
||||
@@ -505,6 +471,7 @@ internal fun ConfigPanel(
|
||||
onOpenFullscreen: () -> Unit = {},
|
||||
reverseSideActions: Boolean = false,
|
||||
) {
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val taskScope = remember { CoroutineScope(SupervisorJob() + Dispatchers.IO) }
|
||||
|
||||
val sessionStarted = sessionInfo != null
|
||||
@@ -563,7 +530,7 @@ internal fun ConfigPanel(
|
||||
checked = soBundle.audio,
|
||||
onCheckedChange = {
|
||||
soBundle = soBundle.copy(
|
||||
audio = it
|
||||
audio = it,
|
||||
)
|
||||
},
|
||||
enabled = !sessionStarted
|
||||
@@ -578,7 +545,7 @@ internal fun ConfigPanel(
|
||||
onSelectedIndexChange = {
|
||||
val codec = Codec.AUDIO[it]
|
||||
soBundle = soBundle.copy(
|
||||
audioCodec = codec.string
|
||||
audioCodec = codec.string,
|
||||
)
|
||||
if (codec == Codec.FLAC)
|
||||
AppRuntime.snackbar(R.string.device_config_audio_codec_note)
|
||||
@@ -596,7 +563,7 @@ internal fun ConfigPanel(
|
||||
val idx = value.roundToInt()
|
||||
.coerceIn(0, ScrcpyPresets.AudioBitRate.lastIndex)
|
||||
soBundle = soBundle.copy(
|
||||
audioBitRate = ScrcpyPresets.AudioBitRate[idx] * 1000
|
||||
audioBitRate = ScrcpyPresets.AudioBitRate[idx] * 1000,
|
||||
)
|
||||
},
|
||||
valueRange = 0f..ScrcpyPresets.AudioBitRate.lastIndex.toFloat(),
|
||||
@@ -626,7 +593,7 @@ internal fun ConfigPanel(
|
||||
onSelectedIndexChange = {
|
||||
val codec = Codec.VIDEO[it]
|
||||
soBundle = soBundle.copy(
|
||||
videoCodec = codec.string
|
||||
videoCodec = codec.string,
|
||||
)
|
||||
if (codec == Codec.AV1)
|
||||
AppRuntime.snackbar(R.string.device_config_video_codec_note)
|
||||
@@ -639,7 +606,7 @@ internal fun ConfigPanel(
|
||||
value = soBundle.videoBitRate / 1_000_000f,
|
||||
onValueChange = { mbps ->
|
||||
soBundle = soBundle.copy(
|
||||
videoBitRate = (mbps * 10).roundToInt() * (1_000_000 / 10)
|
||||
videoBitRate = (mbps * 10).roundToInt() * (1_000_000 / 10),
|
||||
)
|
||||
},
|
||||
valueRange = 0f..40f,
|
||||
@@ -669,7 +636,7 @@ internal fun ConfigPanel(
|
||||
raw.toFloatOrNull()?.let { parsed ->
|
||||
if (parsed >= 0f) {
|
||||
soBundle = soBundle.copy(
|
||||
videoBitRate = (parsed * 1_000_000f).roundToInt()
|
||||
videoBitRate = (parsed * 1_000_000f).roundToInt(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -681,7 +648,7 @@ internal fun ConfigPanel(
|
||||
ArrowPreference(
|
||||
title = stringResource(
|
||||
if (!hideSimpleConfigItems) R.string.device_config_more_params
|
||||
else R.string.device_config_all_params
|
||||
else R.string.device_config_all_params,
|
||||
),
|
||||
summary = stringResource(R.string.device_config_all_scrcpy_params),
|
||||
endActions = {
|
||||
@@ -691,7 +658,10 @@ internal fun ConfigPanel(
|
||||
fontSize = textStyles.body2.fontSize,
|
||||
)
|
||||
},
|
||||
onClick = onOpenAdvanced,
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
onOpenAdvanced()
|
||||
},
|
||||
enabled = !sessionStarted,
|
||||
)
|
||||
|
||||
@@ -704,7 +674,10 @@ internal fun ConfigPanel(
|
||||
fontSize = textStyles.body2.fontSize,
|
||||
)
|
||||
},
|
||||
onClick = onOpenAllApps,
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
onOpenAllApps()
|
||||
},
|
||||
enabled = !busy && !adbConnecting,
|
||||
)
|
||||
ArrowPreference(
|
||||
@@ -716,7 +689,10 @@ internal fun ConfigPanel(
|
||||
fontSize = textStyles.body2.fontSize,
|
||||
)
|
||||
},
|
||||
onClick = onOpenRecentTasks,
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
onOpenRecentTasks()
|
||||
},
|
||||
enabled = !busy && !adbConnecting,
|
||||
)
|
||||
|
||||
@@ -738,7 +714,7 @@ internal fun ConfigPanel(
|
||||
if (isQuickConnected) TextButton(
|
||||
text = stringResource(R.string.button_disconnect),
|
||||
onClick = {
|
||||
onStartStopHaptic()
|
||||
haptic.contextClick()
|
||||
onDisconnect()
|
||||
},
|
||||
modifier = Modifier.weight(sideButtonWeight),
|
||||
@@ -751,7 +727,7 @@ internal fun ConfigPanel(
|
||||
if (!sessionStarted) TextButton(
|
||||
text = stringResource(R.string.button_start),
|
||||
onClick = {
|
||||
onStartStopHaptic()
|
||||
haptic.contextClick()
|
||||
onStart()
|
||||
},
|
||||
modifier = Modifier.weight(mainButtonWeight),
|
||||
@@ -761,7 +737,7 @@ internal fun ConfigPanel(
|
||||
if (sessionStarted) TextButton(
|
||||
text = stringResource(R.string.button_stop),
|
||||
onClick = {
|
||||
onStartStopHaptic()
|
||||
haptic.contextClick()
|
||||
onStop()
|
||||
},
|
||||
modifier = Modifier.weight(mainButtonWeight),
|
||||
@@ -774,7 +750,7 @@ internal fun ConfigPanel(
|
||||
if (showFullscreenAction) TextButton(
|
||||
text = stringResource(R.string.button_fullscreen),
|
||||
onClick = {
|
||||
onStartStopHaptic()
|
||||
haptic.contextClick()
|
||||
onOpenFullscreen()
|
||||
},
|
||||
modifier = Modifier.weight(sideButtonWeight),
|
||||
@@ -818,6 +794,8 @@ private fun PairingDialog(
|
||||
onDismissFinished: () -> Unit,
|
||||
onConfirm: (host: String, port: String, code: String) -> Unit,
|
||||
) {
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
var host by rememberSaveable(showDialog) { mutableStateOf("") }
|
||||
var port by rememberSaveable(showDialog) { mutableStateOf("") }
|
||||
var code by rememberSaveable(showDialog) { mutableStateOf("") }
|
||||
@@ -869,7 +847,7 @@ private fun PairingDialog(
|
||||
keyboardActions = KeyboardActions(
|
||||
onNext = { focusManager.moveFocus(FocusDirection.Next) },
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
TextField(
|
||||
value = port,
|
||||
@@ -883,7 +861,7 @@ private fun PairingDialog(
|
||||
keyboardActions = KeyboardActions(
|
||||
onNext = { focusManager.moveFocus(FocusDirection.Next) },
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
TextField(
|
||||
value = code,
|
||||
@@ -897,7 +875,7 @@ private fun PairingDialog(
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = { focusManager.clearFocus() },
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -909,9 +887,10 @@ private fun PairingDialog(
|
||||
TextButton(
|
||||
text = stringResource(
|
||||
if (!discoveringPort) R.string.button_auto_discover
|
||||
else R.string.button_discovering
|
||||
else R.string.button_discovering,
|
||||
),
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
if (enabled && onDiscoverTarget != null && !discoveringPort)
|
||||
scope.launch { doDiscover() }
|
||||
},
|
||||
@@ -924,6 +903,7 @@ private fun PairingDialog(
|
||||
TextButton(
|
||||
text = stringResource(R.string.button_cancel),
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
onDismissRequest()
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
@@ -931,6 +911,7 @@ private fun PairingDialog(
|
||||
TextButton(
|
||||
text = stringResource(R.string.button_pair),
|
||||
onClick = {
|
||||
haptic.confirm()
|
||||
onConfirm(host.trim(), port.trim(), code.trim())
|
||||
onDismissRequest()
|
||||
},
|
||||
@@ -1053,7 +1034,7 @@ fun ScrcpyVideoSurface(
|
||||
factory = { context ->
|
||||
ScrcpyInputSurfaceView(context).apply {
|
||||
currentSurfaceView = this
|
||||
inputCallbacks = object : ScrcpyInputSurfaceView.InputCallbacks {
|
||||
inputCallbacks = object: ScrcpyInputSurfaceView.InputCallbacks {
|
||||
override fun handleKeyEvent(event: KeyEvent): Boolean {
|
||||
val handler = onImeKeyEvent ?: return false
|
||||
taskScope.launch {
|
||||
@@ -1076,7 +1057,7 @@ fun ScrcpyVideoSurface(
|
||||
|
||||
override fun handleDeleteSurroundingText(
|
||||
beforeLength: Int,
|
||||
afterLength: Int
|
||||
afterLength: Int,
|
||||
): Boolean {
|
||||
val handler = onImeDeleteSurroundingText ?: return false
|
||||
taskScope.launch {
|
||||
@@ -1087,46 +1068,48 @@ fun ScrcpyVideoSurface(
|
||||
return true
|
||||
}
|
||||
}
|
||||
holder.addCallback(object : SurfaceHolder.Callback {
|
||||
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||
val newSurface = holder.surface
|
||||
if (!newSurface.isValid) return
|
||||
currentSurface = newSurface
|
||||
// Register immediately when surface becomes available
|
||||
if (latestSession != null) {
|
||||
scope.launch {
|
||||
NativeCoreFacade.attachVideoSurface(newSurface)
|
||||
holder.addCallback(
|
||||
object: SurfaceHolder.Callback {
|
||||
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||
val newSurface = holder.surface
|
||||
if (!newSurface.isValid) return
|
||||
currentSurface = newSurface
|
||||
// Register immediately when surface becomes available
|
||||
if (latestSession != null) {
|
||||
scope.launch {
|
||||
NativeCoreFacade.attachVideoSurface(newSurface)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun surfaceChanged(
|
||||
holder: SurfaceHolder,
|
||||
format: Int,
|
||||
width: Int,
|
||||
height: Int,
|
||||
) {
|
||||
if (width <= 0 || height <= 0) return
|
||||
if (!holder.surface.isValid) return
|
||||
val surface = holder.surface
|
||||
currentSurface = surface
|
||||
if (latestSession != null) {
|
||||
scope.launch {
|
||||
NativeCoreFacade.attachVideoSurface(surface)
|
||||
override fun surfaceChanged(
|
||||
holder: SurfaceHolder,
|
||||
format: Int,
|
||||
width: Int,
|
||||
height: Int,
|
||||
) {
|
||||
if (width <= 0 || height <= 0) return
|
||||
if (!holder.surface.isValid) return
|
||||
val surface = holder.surface
|
||||
currentSurface = surface
|
||||
if (latestSession != null) {
|
||||
scope.launch {
|
||||
NativeCoreFacade.attachVideoSurface(surface)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||
val surface = currentSurface
|
||||
if (surface != null) {
|
||||
taskScope.launch {
|
||||
NativeCoreFacade.detachVideoSurface(surface)
|
||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||
val surface = currentSurface
|
||||
if (surface != null) {
|
||||
taskScope.launch {
|
||||
NativeCoreFacade.detachVideoSurface(surface)
|
||||
}
|
||||
currentSurface = null
|
||||
}
|
||||
currentSurface = null
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
update = {},
|
||||
@@ -1201,7 +1184,6 @@ internal fun DeviceTile(
|
||||
else colorScheme.surfaceContainer.copy(alpha = 0.6f),
|
||||
),
|
||||
pressFeedbackType = if (!editing) PressFeedbackType.Sink else PressFeedbackType.None,
|
||||
onClick = haptic::contextClick,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -1209,10 +1191,13 @@ internal fun DeviceTile(
|
||||
.then(
|
||||
if (!isConnected)
|
||||
Modifier.combinedClickable(
|
||||
onClick = onClick,
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
onClick()
|
||||
},
|
||||
onLongClick = onLongClick,
|
||||
)
|
||||
else Modifier
|
||||
else Modifier,
|
||||
)
|
||||
.padding(UiSpacing.PageItem),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@@ -1261,7 +1246,7 @@ internal fun DeviceTile(
|
||||
TextButton(
|
||||
text = stringResource(
|
||||
if (!isConnected) R.string.button_connect
|
||||
else R.string.button_disconnect
|
||||
else R.string.button_disconnect,
|
||||
),
|
||||
onClick = onAction,
|
||||
enabled = actionEnabled && !actionInProgress,
|
||||
@@ -1303,7 +1288,7 @@ internal fun DeviceTile(
|
||||
onValueChange = {
|
||||
draftPortText = it.filter(Char::isDigit)
|
||||
draft = currentDraft.copy(
|
||||
port = draftPortText?.toIntOrNull() ?: Defaults.ADB_PORT
|
||||
port = draftPortText?.toIntOrNull() ?: Defaults.ADB_PORT,
|
||||
)
|
||||
},
|
||||
label = stringResource(R.string.label_port),
|
||||
@@ -1473,13 +1458,19 @@ internal fun QuickConnectCard(
|
||||
) {
|
||||
TextButton(
|
||||
text = stringResource(R.string.button_add_device),
|
||||
onClick = onAddDevice,
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
onAddDevice()
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
enabled = enabled,
|
||||
)
|
||||
TextButton(
|
||||
text = stringResource(R.string.button_direct_connect),
|
||||
onClick = onConnect,
|
||||
onClick = {
|
||||
haptic.confirm()
|
||||
onConnect()
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
enabled = enabled,
|
||||
colors = ButtonDefaults.textButtonColorsPrimary(),
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.github.miuzarte.scrcpyforandroid.widgets
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import io.github.miuzarte.scrcpyforandroid.R
|
||||
@@ -9,6 +10,7 @@ import io.github.miuzarte.scrcpyforandroid.pages.LocalRootNavigator
|
||||
import io.github.miuzarte.scrcpyforandroid.pages.RootScreen
|
||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.ClientOptions.RecordFormat
|
||||
import io.github.miuzarte.scrcpyforandroid.services.NativeRecordingSupport
|
||||
import io.github.miuzarte.scrcpyforandroid.ui.contextClick
|
||||
import top.yukonga.miuix.kmp.basic.Text
|
||||
import top.yukonga.miuix.kmp.preference.ArrowPreference
|
||||
import top.yukonga.miuix.kmp.preference.OverlayDropdownPreference
|
||||
@@ -23,6 +25,8 @@ fun RecordPreferences(
|
||||
enabled: Boolean,
|
||||
onRecordFormatChange: (String) -> Unit,
|
||||
) {
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
val navigator = LocalRootNavigator.current
|
||||
val supportedFormats = remember { NativeRecordingSupport.supportedFormats }
|
||||
|
||||
@@ -43,6 +47,7 @@ fun RecordPreferences(
|
||||
summary = "--record",
|
||||
enabled = enabled,
|
||||
onClick = {
|
||||
haptic.contextClick()
|
||||
navigator.push(RootScreen.ScrcpyOptionRecord(profileId))
|
||||
},
|
||||
endActions = {
|
||||
|
||||
Reference in New Issue
Block a user