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