chore: format codes

This commit is contained in:
Miuzarte
2026-05-31 17:48:05 +08:00
parent 7f256882a4
commit 56c724d282
66 changed files with 422 additions and 821 deletions

View File

@@ -11,7 +11,6 @@ import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Codec
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@@ -89,7 +88,10 @@ object NativeCoreFacade {
}
}
if (session.width <= 0 || session.height <= 0) {
Log.i(TAG, "attachVideoSurface(): defer decoder, session size not yet known (${session.width}x${session.height})")
Log.i(
TAG,
"attachVideoSurface(): defer decoder, session size not yet known (${session.width}x${session.height})",
)
return
}
createOrReplaceDecoder(session)
@@ -112,13 +114,13 @@ object NativeCoreFacade {
if (requestId != null && currentId != null && requestId != currentId) {
Log.i(
TAG,
"detachVideoSurface(): skip stale request requestSurfaceId=$requestId currentSurfaceId=$currentId"
"detachVideoSurface(): skip stale request requestSurfaceId=$requestId currentSurfaceId=$currentId",
)
return
}
Log.i(
TAG,
"detachVideoSurface(): surfaceId=$requestId releaseDecoder=$releaseDecoder"
"detachVideoSurface(): surfaceId=$requestId releaseDecoder=$releaseDecoder",
)
activeSurfaceId = null
renderer.detachDisplaySurface(surface, releaseSurface = false)
@@ -197,7 +199,7 @@ object NativeCoreFacade {
if (packetCount == 1L || packetCount % 120L == 0L) {
Log.i(
TAG,
"videoFeed(): packets=$packetCount key=${packet.isKeyFrame} cfg=${packet.isConfig} decoder=${decoder != null}"
"videoFeed(): packets=$packetCount key=${packet.isKeyFrame} cfg=${packet.isConfig} decoder=${decoder != null}",
)
}
@@ -207,7 +209,7 @@ object NativeCoreFacade {
packet.data,
packet.ptsUs,
packet.isKeyFrame,
packet.isConfig
packet.isConfig,
)
}
}
@@ -233,7 +235,10 @@ object NativeCoreFacade {
(info.width != currentSessionInfo!!.width || info.height != currentSessionInfo!!.height)
) {
// Flex display: rebuild decoder on size change
Log.i(TAG, "onVideoSizeChanged(): rebuild decoder ${currentSessionInfo!!.width}x${currentSessionInfo!!.height}${info.width}x${info.height}")
Log.i(
TAG,
"onVideoSizeChanged(): rebuild decoder ${currentSessionInfo!!.width}x${currentSessionInfo!!.height}${info.width}x${info.height}",
)
currentSessionInfo = info
createOrReplaceDecoder(info)
}
@@ -306,7 +311,7 @@ object NativeCoreFacade {
"createOrReplaceDecoder(): " +
"codec=${session.codec?.string ?: "null"}, " +
"size=${session.width}x${session.height}, " +
"persistent=true"
"persistent=true",
)
val newDecoder = AnnexBDecoder(
width = session.width,
@@ -325,7 +330,7 @@ object NativeCoreFacade {
}
Log.i(
TAG,
"videoSizeChanged(): ${current.width}x${current.height} -> ${width}x${height}"
"videoSizeChanged(): ${current.width}x${current.height} -> ${width}x${height}",
)
currentSessionInfo = current.copy(width = width, height = height)
mainHandler.post {

View File

@@ -3,12 +3,7 @@ package io.github.miuzarte.scrcpyforandroid.miuix
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -18,15 +13,7 @@ import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import top.yukonga.miuix.kmp.basic.BasicComponent
import top.yukonga.miuix.kmp.basic.BasicComponentColors
import top.yukonga.miuix.kmp.basic.BasicComponentDefaults
import top.yukonga.miuix.kmp.basic.DropdownArrowEndAction
import top.yukonga.miuix.kmp.basic.DropdownColors
import top.yukonga.miuix.kmp.basic.DropdownDefaults
import top.yukonga.miuix.kmp.basic.ListPopupColumn
import top.yukonga.miuix.kmp.basic.PopupPositionProvider
import top.yukonga.miuix.kmp.basic.Text
import top.yukonga.miuix.kmp.basic.*
import top.yukonga.miuix.kmp.overlay.OverlayListPopup
import top.yukonga.miuix.kmp.theme.MiuixTheme

View File

@@ -2,15 +2,7 @@ package io.github.miuzarte.scrcpyforandroid.miuix
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState

View File

@@ -97,7 +97,7 @@ class DeviceShortcuts(val devices: List<DeviceShortcut>) : List<DeviceShortcut>
|| (newPort != null && newPort != old.port)
)
newList.distinctBy { it.id }
else newList
else newList,
)
}
@@ -176,7 +176,7 @@ data class DeviceShortcut(
if (openFullscreenOnStart) "1" else "0",
scrcpyProfileId.trim(),
).joinToString(
separator = separator
separator = separator,
)
companion object {

View File

@@ -2,9 +2,9 @@ package io.github.miuzarte.scrcpyforandroid.nativecore
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import android.os.Build
import android.util.Log
import java.io.IOException
import java.net.InetSocketAddress

View File

@@ -11,11 +11,7 @@ import java.math.BigInteger
import java.net.Socket
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.security.KeyFactory
import java.security.PrivateKey
import java.security.Provider
import java.security.SecureRandom
import java.security.Security
import java.security.*
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.security.interfaces.RSAPrivateKey

View File

@@ -94,14 +94,14 @@ class AnnexBDecoder(
if (inCount == 1L || inCount % 180L == 0L || isConfig) {
Log.i(
TAG,
"feed(): mime=$decoderMime in=$inCount size=${data.size} key=$isKeyFrame cfg=$isConfig pts=$ptsUs"
"feed(): mime=$decoderMime in=$inCount size=${data.size} key=$isKeyFrame cfg=$isConfig pts=$ptsUs",
)
}
} else {
if (isConfig || isKeyFrame) {
Log.w(
TAG,
"drop critical packet: mime=$decoderMime size=${data.size} key=$isKeyFrame cfg=$isConfig"
"drop critical packet: mime=$decoderMime size=${data.size} key=$isKeyFrame cfg=$isConfig",
)
}
}
@@ -110,7 +110,7 @@ class AnnexBDecoder(
Log.w(
TAG,
"feed failed: mime=$decoderMime size=${data.size} key=$isKeyFrame cfg=$isConfig",
it
it,
)
}
}

View File

@@ -7,24 +7,13 @@ import io.github.miuzarte.scrcpyforandroid.storage.AdbClientData
import io.github.miuzarte.scrcpyforandroid.storage.AppSettings
import io.github.miuzarte.scrcpyforandroid.storage.Storage
import kotlinx.coroutines.runBlocking
import java.io.BufferedInputStream
import java.io.ByteArrayOutputStream
import java.io.Closeable
import java.io.EOFException
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.io.*
import java.math.BigInteger
import java.net.InetSocketAddress
import java.net.Socket
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.security.KeyFactory
import java.security.KeyPairGenerator
import java.security.MessageDigest
import java.security.PrivateKey
import java.security.PublicKey
import java.security.Signature
import java.security.*
import java.security.interfaces.RSAPrivateCrtKey
import java.security.interfaces.RSAPublicKey
import java.security.spec.PKCS8EncodedKeySpec
@@ -72,7 +61,8 @@ internal object DirectAdbTransport {
port,
privateKey,
publicKeyX509,
keyName.ifBlank { AppSettings.ADB_KEY_NAME.defaultValue })
keyName.ifBlank { AppSettings.ADB_KEY_NAME.defaultValue },
)
conn.handshake()
Log.i(TAG, "connect(): handshake success for $host:$port")
return conn
@@ -186,7 +176,7 @@ internal object DirectAdbTransport {
importedPrivateKeyFileName = "",
importedPublicKeyX509 = "",
importedPublicKeyFileName = "",
)
),
)
loadOrCreate()
}.also { cachedKeys = it }
@@ -232,21 +222,21 @@ internal object DirectAdbTransport {
current.copy(
rsaPublicKeyX509 = encoded,
)
}
},
)
}
Log.i(
TAG,
"loadOrCreate(): loaded persisted RSA key pair from DataStore, " +
"fp=${fingerprint(pub)}"
"fp=${fingerprint(pub)}",
)
return Pair(priv, pub)
} catch (e: Exception) {
Log.w(
TAG,
"loadOrCreate(): failed to load persisted key from DataStore, regenerating",
e
e,
)
}
}
@@ -264,12 +254,12 @@ internal object DirectAdbTransport {
importedPrivateKeyFileName = "",
importedPublicKeyX509 = "",
importedPublicKeyFileName = "",
)
),
)
Log.i(
TAG,
"loadOrCreate(): generated new RSA key pair, fp=${fingerprint(kp.public.encoded)}"
"loadOrCreate(): generated new RSA key pair, fp=${fingerprint(kp.public.encoded)}",
)
return Pair(kp.private, kp.public.encoded)
}
@@ -427,7 +417,7 @@ internal object DirectAdbTransport {
val exponent = buf.int.toLong() and 0xffffffffL
val modulus = BigInteger(1, modulusLE.reversedArray())
return KeyFactory.getInstance("RSA").generatePublic(
RSAPublicKeySpec(modulus, BigInteger.valueOf(exponent))
RSAPublicKeySpec(modulus, BigInteger.valueOf(exponent)),
)
}
@@ -623,9 +613,9 @@ internal class DirectAdbConnection(
else -> throw IOException(
"ADB: unexpected message 0x${
afterSign.command.toString(
16
16,
)
} after AUTH_SIGNATURE"
} after AUTH_SIGNATURE",
)
}
}
@@ -837,7 +827,7 @@ internal class DirectAdbConnection(
command: Int,
arg0: Int = 0,
arg1: Int = 0,
data: ByteArray = ByteArray(0)
data: ByteArray = ByteArray(0),
) {
val crc = data.fold(0L) { acc, b -> acc + (b.toLong() and 0xFF) }.toInt()
val header = ByteBuffer.allocate(24).order(ByteOrder.LITTLE_ENDIAN)

View File

@@ -190,7 +190,7 @@ object NativeAdbService {
.map(String::trim)
.lastOrNull { '/' in it }
?: throw IllegalStateException(
"Cannot resolve launch activity for $normalizedPackageName"
"Cannot resolve launch activity for $normalizedPackageName",
)
val displayArg = displayId

View File

@@ -1,15 +1,7 @@
package io.github.miuzarte.scrcpyforandroid.nativecore
import android.graphics.SurfaceTexture
import android.opengl.EGL14
import android.opengl.EGLConfig
import android.opengl.EGLContext
import android.opengl.EGLDisplay
import android.opengl.EGLExt
import android.opengl.EGLSurface
import android.opengl.GLES11Ext
import android.opengl.GLES20
import android.opengl.Matrix
import android.opengl.*
import android.os.Handler
import android.os.HandlerThread
import android.util.Log
@@ -85,7 +77,7 @@ class PersistentVideoRenderer {
eglConfig,
surface,
intArrayOf(EGL14.EGL_NONE),
0
0,
)
Log.i(tag, "attachDisplaySurface(): attached surfaceId=$newId")
drawFrame()
@@ -96,7 +88,7 @@ class PersistentVideoRenderer {
val requestId = surface?.let { System.identityHashCode(it) }
Log.i(
tag,
"detachDisplaySurface(): request surfaceId=$requestId releaseSurface=$releaseSurface current=${displaySurfaceId}"
"detachDisplaySurface(): request surfaceId=$requestId releaseSurface=$releaseSurface current=${displaySurfaceId}",
)
handler.post {
if (released) return@post
@@ -137,7 +129,7 @@ class PersistentVideoRenderer {
eglConfig,
surface,
intArrayOf(EGL14.EGL_NONE),
0
0,
)
Log.i(tag, "attachRecordSurface(): attached surfaceId=$newId")
drawFrame()
@@ -148,7 +140,7 @@ class PersistentVideoRenderer {
val requestId = surface?.let { System.identityHashCode(it) }
Log.i(
tag,
"detachRecordSurface(): request surfaceId=$requestId releaseSurface=$releaseSurface current=$recordSurfaceId"
"detachRecordSurface(): request surfaceId=$requestId releaseSurface=$releaseSurface current=$recordSurfaceId",
)
handler.post {
if (released) return@post
@@ -183,7 +175,7 @@ class PersistentVideoRenderer {
eglDisplay,
EGL14.EGL_NO_SURFACE,
EGL14.EGL_NO_SURFACE,
EGL14.EGL_NO_CONTEXT
EGL14.EGL_NO_CONTEXT,
)
if (eglPbufferSurface != EGL14.EGL_NO_SURFACE) {
EGL14.eglDestroySurface(eglDisplay, eglPbufferSurface)
@@ -227,7 +219,7 @@ class PersistentVideoRenderer {
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
EGL14.EGL_NONE
EGL14.EGL_NONE,
)
check(EGL14.eglChooseConfig(eglDisplay, attribs, 0, configs, 0, 1, numConfigs, 0))
eglConfig = configs[0]
@@ -236,30 +228,33 @@ class PersistentVideoRenderer {
eglConfig,
EGL14.EGL_NO_CONTEXT,
intArrayOf(0x3098, 2, EGL14.EGL_NONE),
0
0,
)
check(eglContext != EGL14.EGL_NO_CONTEXT)
eglPbufferSurface = EGL14.eglCreatePbufferSurface(
eglDisplay,
eglConfig,
intArrayOf(EGL14.EGL_WIDTH, 1, EGL14.EGL_HEIGHT, 1, EGL14.EGL_NONE),
0
0,
)
check(eglPbufferSurface != EGL14.EGL_NO_SURFACE)
check(EGL14.eglMakeCurrent(eglDisplay, eglPbufferSurface, eglPbufferSurface, eglContext))
oesTextureId = createExternalTexture()
decoderSurfaceTexture = SurfaceTexture(oesTextureId).apply {
setOnFrameAvailableListener({
setOnFrameAvailableListener(
{
val n = frameAvailableCount.incrementAndGet()
if (n == 1L || n % 120L == 0L) {
Log.i(
tag,
"onFrameAvailable(): available=$n consumed=${frameConsumedCount.get()} rendered=${frameRenderedCount.get()} display=${displaySurfaceId != null}"
"onFrameAvailable(): available=$n consumed=${frameConsumedCount.get()} rendered=${frameRenderedCount.get()} display=${displaySurfaceId != null}",
)
}
drawFrame()
}, handler)
},
handler,
)
}
decoderSurface = Surface(decoderSurfaceTexture)
Log.i(tag, "initializeLocked(): decoder surface created")
@@ -290,7 +285,7 @@ class PersistentVideoRenderer {
if (consumed == 1L || consumed % 120L == 0L) {
Log.i(
tag,
"drawFrame(): consumed=$consumed available=${frameAvailableCount.get()} rendered=${frameRenderedCount.get()} display=${displaySurfaceId != null}"
"drawFrame(): consumed=$consumed available=${frameAvailableCount.get()} rendered=${frameRenderedCount.get()} display=${displaySurfaceId != null}",
)
}
}
@@ -322,7 +317,7 @@ class PersistentVideoRenderer {
if (rendered == 1L || rendered % 120L == 0L) {
Log.i(
tag,
"drawFrame(): rendered=$rendered consumed=${frameConsumedCount.get()} available=${frameAvailableCount.get()} viewport=${width[0]}x${height[0]}"
"drawFrame(): rendered=$rendered consumed=${frameConsumedCount.get()} available=${frameAvailableCount.get()} viewport=${width[0]}x${height[0]}",
)
}
}
@@ -391,22 +386,22 @@ class PersistentVideoRenderer {
GLES20.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR
GLES20.GL_LINEAR,
)
GLES20.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR
GLES20.GL_LINEAR,
)
GLES20.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_CLAMP_TO_EDGE
GLES20.GL_CLAMP_TO_EDGE,
)
GLES20.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_CLAMP_TO_EDGE
GLES20.GL_CLAMP_TO_EDGE,
)
return textures[0]
}
@@ -439,7 +434,7 @@ class PersistentVideoRenderer {
1f, -1f, 1f, 1f,
-1f, 1f, 0f, 0f,
1f, 1f, 1f, 0f,
)
),
)
position(0)
}

View File

@@ -3,12 +3,7 @@ package io.github.miuzarte.scrcpyforandroid.nativecore
// Go reader note: Audio output helper for scrcpy stream: decodes/plays PCM or codec audio frames.
import android.content.Context
import android.media.AudioAttributes
import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioTrack
import android.media.MediaCodec
import android.media.MediaFormat
import android.media.*
import android.os.Build
import android.util.Log
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Codec
@@ -54,7 +49,7 @@ class ScrcpyAudioPlayer(
TAG,
"feedPacket(): config packet size=${data.size} codec=0x${
codecId.toUInt().toString(16)
}"
}",
)
when (codecId) {
Codec.OPUS.id -> prepareOpus(data)
@@ -101,7 +96,7 @@ class ScrcpyAudioPlayer(
val format = MediaFormat.createAudioFormat(
MediaFormat.MIMETYPE_AUDIO_OPUS,
SAMPLE_RATE,
CHANNELS
CHANNELS,
)
format.setByteBuffer("csd-0", ByteBuffer.wrap(opusHead))
// pre-skip field: 2 bytes LE at offset 10 of the OpusHead
@@ -132,7 +127,7 @@ class ScrcpyAudioPlayer(
val format = MediaFormat.createAudioFormat(
MediaFormat.MIMETYPE_AUDIO_FLAC,
SAMPLE_RATE,
CHANNELS
CHANNELS,
)
if (flacConfig.isNotEmpty()) {
format.setByteBuffer("csd-0", ByteBuffer.wrap(flacConfig))
@@ -196,11 +191,11 @@ class ScrcpyAudioPlayer(
val attributesBuilder = AudioAttributes.Builder()
.setUsage(
if (lowLatency) AudioAttributes.USAGE_GAME
else AudioAttributes.USAGE_MEDIA
else AudioAttributes.USAGE_MEDIA,
)
.setContentType(
if (lowLatency) AudioAttributes.CONTENT_TYPE_SONIFICATION
else AudioAttributes.CONTENT_TYPE_MOVIE
else AudioAttributes.CONTENT_TYPE_MOVIE,
)
if (lowLatency) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
@@ -215,7 +210,7 @@ class ScrcpyAudioPlayer(
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(SAMPLE_RATE)
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
.build()
.build(),
)
.setBufferSizeInBytes(bufferSize)
.setTransferMode(AudioTrack.MODE_STREAM)
@@ -229,7 +224,7 @@ class ScrcpyAudioPlayer(
Log.i(
TAG,
"low-latency audio requested: nativeSampleRate=$nativeSampleRate streamSampleRate=$SAMPLE_RATE " +
"framesPerBurst=$framesPerBurst bufferSize=$bufferSize performanceMode=${track.performanceMode}"
"framesPerBurst=$framesPerBurst bufferSize=$bufferSize performanceMode=${track.performanceMode}",
)
}
return track

View File

@@ -11,13 +11,8 @@ import io.github.miuzarte.scrcpyforandroid.models.ConnectionTarget
import io.github.miuzarte.scrcpyforandroid.models.DeviceShortcut
import io.github.miuzarte.scrcpyforandroid.models.DeviceShortcuts
import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy
import io.github.miuzarte.scrcpyforandroid.services.AppRuntime
import io.github.miuzarte.scrcpyforandroid.services.AppScreenOn
import io.github.miuzarte.scrcpyforandroid.services.DisconnectCause
import io.github.miuzarte.scrcpyforandroid.services.EventLogMessage
import io.github.miuzarte.scrcpyforandroid.services.*
import io.github.miuzarte.scrcpyforandroid.services.EventLogger.logEvent
import io.github.miuzarte.scrcpyforandroid.services.LocalInputService
import io.github.miuzarte.scrcpyforandroid.services.render
import io.github.miuzarte.scrcpyforandroid.storage.AppSettings
import io.github.miuzarte.scrcpyforandroid.storage.ScrcpyOptions
import io.github.miuzarte.scrcpyforandroid.storage.Settings
@@ -27,25 +22,9 @@ import io.github.miuzarte.scrcpyforandroid.storage.Storage.scrcpyOptions
import io.github.miuzarte.scrcpyforandroid.storage.Storage.scrcpyProfiles
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonAction
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonActions
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlinx.coroutines.flow.*
private const val ADB_CONNECT_TIMEOUT_MS = 12_000L
private const val ADB_KEEPALIVE_INTERVAL_MS = 3_000L
@@ -107,7 +86,7 @@ internal class DeviceTabViewModel(
val quickConnectInput: StateFlow<String> = _quickConnectInput.asStateFlow()
private val _savedShortcuts = MutableStateFlow(
DeviceShortcuts.unmarshalFrom(_qdBundle.value.quickDevicesList)
DeviceShortcuts.unmarshalFrom(_qdBundle.value.quickDevicesList),
)
val savedShortcuts: StateFlow<DeviceShortcuts> = _savedShortcuts.asStateFlow()
@@ -223,13 +202,13 @@ internal class DeviceTabViewModel(
val virtualButtonLayout: StateFlow<Pair<List<VirtualButtonAction>, List<VirtualButtonAction>>> =
_asBundle.map {
VirtualButtonActions.splitLayout(
VirtualButtonActions.parseStoredLayout(it.virtualButtonsLayout)
VirtualButtonActions.parseStoredLayout(it.virtualButtonsLayout),
)
}.stateIn(
viewModelScope,
SharingStarted.Eagerly,
VirtualButtonActions.splitLayout(
VirtualButtonActions.parseStoredLayout(_asBundle.value.virtualButtonsLayout)
VirtualButtonActions.parseStoredLayout(_asBundle.value.virtualButtonsLayout),
),
)
@@ -397,7 +376,7 @@ internal class DeviceTabViewModel(
EventLogMessage.Resource(
R.string.vm_send_action,
listOf(EventLogMessage.Resource(action.titleResId)),
)
),
) {
scrcpy.injectKeycode(0, keycode)
scrcpy.injectKeycode(1, keycode)
@@ -432,7 +411,7 @@ internal class DeviceTabViewModel(
private fun runBusy(
label: EventLogMessage,
onFinished: (() -> Unit)? = null,
block: suspend () -> Unit
block: suspend () -> Unit,
) {
if (_busy.value) return
viewModelScope.launch {
@@ -492,7 +471,7 @@ internal class DeviceTabViewModel(
val result = connectionController.disconnectAdbConnection(
clearQuickOnlineForTarget,
cause,
statusLine
statusLine,
)
result.clearedTarget?.let { target ->
if (target.host.isNotBlank())
@@ -535,14 +514,14 @@ internal class DeviceTabViewModel(
host = host,
port = port,
name = fullLabel,
updateNameOnlyWhenEmpty = true
updateNameOnlyWhenEmpty = true,
)
}
logEvent(
"ADB connected: model=${info.model}, serial=${info.serial.ifBlank { "unknown" }}, " +
"manufacturer=${info.manufacturer.ifBlank { "unknown" }}, brand=${info.brand.ifBlank { "unknown" }}, " +
"device=${info.device.ifBlank { "unknown" }}, android=${info.androidRelease.ifBlank { "unknown" }}, sdk=${info.sdkInt}"
"device=${info.device.ifBlank { "unknown" }}, android=${info.androidRelease.ifBlank { "unknown" }}, sdk=${info.sdkInt}",
)
AppRuntime.snackbar(R.string.vm_adb_connected)
@@ -607,7 +586,8 @@ internal class DeviceTabViewModel(
if (!resolvedOptions.video) "off"
else {
val codec = session.codec?.string ?: "null"
val sizeHint = if (session.width > 0 && session.height > 0) " ${session.width}x${session.height}" else ""
val sizeHint =
if (session.width > 0 && session.height > 0) " ${session.width}x${session.height}" else ""
val bitrateSuffix = if (activeBundle.videoBitRate <= 0) " @default"
else " @%.1fMbps".format(activeBundle.videoBitRate / 1_000_000f)
"$codec$sizeHint$bitrateSuffix"
@@ -623,11 +603,11 @@ internal class DeviceTabViewModel(
logEvent(
"scrcpy 已启动: device=${session.deviceName}, video=$videoDetail, audio=$audioDetail, " +
"control=${resolvedOptions.control}, turnScreenOff=${resolvedOptions.turnScreenOff}, " +
"maxSize=${resolvedOptions.maxSize}, maxFps=${resolvedOptions.maxFps}"
"maxSize=${resolvedOptions.maxSize}, maxFps=${resolvedOptions.maxFps}",
)
AppRuntime.snackbar(
if (resolvedOptions.recordFilename.isNotBlank()) R.string.vm_scrcpy_started_recording
else R.string.vm_scrcpy_started
else R.string.vm_scrcpy_started,
)
}
@@ -699,13 +679,13 @@ internal class DeviceTabViewModel(
}
logEvent(
if (useLegacyPaste) R.string.vm_legacy_paste_injected
else R.string.vm_clipboard_synced_paste
else R.string.vm_clipboard_synced_paste,
)
}.onFailure { error ->
logEvent(R.string.vm_clipboard_paste_failed, level = Log.WARN, error = error)
AppRuntime.snackbar(
if (useLegacyPaste) R.string.fullscreen_legacy_paste_failed
else R.string.fullscreen_clipboard_sync_failed
else R.string.fullscreen_clipboard_sync_failed,
)
}
}
@@ -723,7 +703,7 @@ internal class DeviceTabViewModel(
)
AppRuntime.snackbar(
if (useClipboardPaste) R.string.fullscreen_paste_non_ascii
else R.string.fullscreen_text_input_failed
else R.string.fullscreen_text_input_failed,
)
}
}
@@ -747,7 +727,7 @@ internal class DeviceTabViewModel(
port = device.port,
autoStartScrcpy = device.startScrcpyOnConnect,
autoEnterFullScreen = device.startScrcpyOnConnect && device.openFullscreenOnStart,
scrcpyProfileId = device.scrcpyProfileId
scrcpyProfileId = device.scrcpyProfileId,
)
connectionController.updateQuickConnected(false)
} catch (error: Exception) {
@@ -818,7 +798,7 @@ internal class DeviceTabViewModel(
)
AppRuntime.snackbar(
if (ok) R.string.vm_pairing_succeeded
else R.string.vm_pairing_failed
else R.string.vm_pairing_failed,
)
}
}
@@ -850,7 +830,7 @@ internal class DeviceTabViewModel(
viewModelScope.launch {
disconnectAdbConnection(
cause = DisconnectCause.KeepAliveFailed,
statusLine = "ADB disconnected"
statusLine = "ADB disconnected",
)
}
logEvent(R.string.vm_auto_reconnect_failed, level = Log.ERROR, error = error)
@@ -979,7 +959,7 @@ internal class DeviceTabViewModel(
screenHeight,
pressure,
actionButton,
buttons
buttons,
)
}

View File

@@ -6,24 +6,11 @@ import android.net.Uri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import io.github.miuzarte.scrcpyforandroid.R
import io.github.miuzarte.scrcpyforandroid.services.AppRuntime
import io.github.miuzarte.scrcpyforandroid.services.DirectoryDownloadSnapshot
import io.github.miuzarte.scrcpyforandroid.services.DirectorySnapshotSession
import io.github.miuzarte.scrcpyforandroid.services.FileManagerService
import io.github.miuzarte.scrcpyforandroid.services.RemoteFileEntry
import io.github.miuzarte.scrcpyforandroid.services.RemoteFileKind
import io.github.miuzarte.scrcpyforandroid.services.RemoteFileStat
import io.github.miuzarte.scrcpyforandroid.services.*
import io.github.miuzarte.scrcpyforandroid.storage.BundleSyncDelegate
import io.github.miuzarte.scrcpyforandroid.storage.Storage.appSettings
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
@@ -82,7 +69,7 @@ internal class FileManagerViewModel : ViewModel() {
val sortField: StateFlow<FileManagerSortField> = asBundle
.map {
runCatching { FileManagerSortField.valueOf(it.fileManagerSortBy) }.getOrDefault(
FileManagerSortField.NAME
FileManagerSortField.NAME,
)
}
.stateIn(
@@ -473,7 +460,7 @@ internal fun sortEntries(
}
return entries.sortedWith(
compareByDescending<RemoteFileEntry> { it.isDirectory }
.then(if (descending) comparator.reversed() else comparator)
.then(if (descending) comparator.reversed() else comparator),
)
}

View File

@@ -10,29 +10,9 @@ import android.view.Surface
import androidx.activity.compose.BackHandler
import androidx.activity.compose.LocalActivity
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
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.padding
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.mutableFloatStateOf
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.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
@@ -65,18 +45,8 @@ import io.github.miuzarte.scrcpyforandroid.storage.AppSettings
import io.github.miuzarte.scrcpyforandroid.storage.Settings
import io.github.miuzarte.scrcpyforandroid.storage.Storage.appSettings
import io.github.miuzarte.scrcpyforandroid.util.Debouncer
import io.github.miuzarte.scrcpyforandroid.widgets.AppListBottomSheet
import io.github.miuzarte.scrcpyforandroid.widgets.AppListEntry
import io.github.miuzarte.scrcpyforandroid.widgets.ScrcpyVideoSurface
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonAction
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonActions
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonBar
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 io.github.miuzarte.scrcpyforandroid.widgets.*
import kotlinx.coroutines.*
import top.yukonga.miuix.kmp.basic.Scaffold
import top.yukonga.miuix.kmp.basic.SnackbarHost
import top.yukonga.miuix.kmp.basic.Text
@@ -126,7 +96,7 @@ fun FullscreenControlScreen(
val buttonItems = remember(asBundle.virtualButtonsLayout) {
VirtualButtonActions.splitLayout(
VirtualButtonActions.parseStoredLayout(asBundle.virtualButtonsLayout)
VirtualButtonActions.parseStoredLayout(asBundle.virtualButtonsLayout),
)
}
val floatingActions = remember(buttonItems) {
@@ -137,7 +107,7 @@ fun FullscreenControlScreen(
val fullscreenVirtualButtonHeight = asBundle.fullscreenVirtualButtonHeightDp.dp
val fullscreenVirtualButtonDockSetting = remember(asBundle.fullscreenVirtualButtonDock) {
AppSettings.FullscreenVirtualButtonDock.fromStoredValue(
asBundle.fullscreenVirtualButtonDock
asBundle.fullscreenVirtualButtonDock,
)
}
var displayRotation by remember(activity) {
@@ -171,7 +141,8 @@ fun FullscreenControlScreen(
AppSettings.FullscreenVirtualButtonDock.FOLLOW_TOP,
AppSettings.FullscreenVirtualButtonDock.FOLLOW_BOTTOM,
AppSettings.FullscreenVirtualButtonDock.FOLLOW_LEFT,
AppSettings.FullscreenVirtualButtonDock.FOLLOW_RIGHT -> null
AppSettings.FullscreenVirtualButtonDock.FOLLOW_RIGHT,
-> null
AppSettings.FullscreenVirtualButtonDock.FIXED_TOP -> VirtualButtonBar.FullscreenDock.TOP
AppSettings.FullscreenVirtualButtonDock.FIXED_BOTTOM -> VirtualButtonBar.FullscreenDock.BOTTOM
@@ -250,7 +221,7 @@ fun FullscreenControlScreen(
val restoreWindow = activity?.window
if (restoreWindow != null) {
WindowInsetsControllerCompat(restoreWindow, restoreWindow.decorView).show(
WindowInsetsCompat.Type.systemBars()
WindowInsetsCompat.Type.systemBars(),
)
WindowCompat.setDecorFitsSystemWindows(restoreWindow, true)
}
@@ -320,7 +291,7 @@ fun FullscreenControlScreen(
Log.w("FullscreenControlPage", "commitImeText failed", error)
AppRuntime.snackbar(
if (useClipboardPaste) R.string.fullscreen_paste_non_ascii
else R.string.fullscreen_text_input_failed
else R.string.fullscreen_text_input_failed,
)
}
}
@@ -367,7 +338,7 @@ fun FullscreenControlScreen(
Log.w("FullscreenControl", "pasteLocalClipboard failed", error)
AppRuntime.snackbar(
if (useLegacyPaste) R.string.fullscreen_legacy_paste_failed
else R.string.fullscreen_clipboard_sync_failed
else R.string.fullscreen_clipboard_sync_failed,
)
}
}
@@ -383,7 +354,7 @@ fun FullscreenControlScreen(
Log.w(
"FullscreenControlPage",
"sendKeycode failed for keycode=$it",
e
e,
)
}
}
@@ -452,7 +423,7 @@ fun FullscreenControlScreen(
VirtualButtonBar.FullscreenDock.BOTTOM -> Alignment.BottomCenter
VirtualButtonBar.FullscreenDock.LEFT -> Alignment.CenterStart
VirtualButtonBar.FullscreenDock.RIGHT -> Alignment.CenterEnd
}
},
),
dock = fullscreenVirtualButtonDock,
reverseOrder = fullscreenVirtualButtonReverseOrder,
@@ -582,10 +553,12 @@ private fun isFixedDockOrderReversed(
)
return when (visualDock) {
VirtualButtonBar.FullscreenDock.TOP,
VirtualButtonBar.FullscreenDock.BOTTOM -> visualDirection == DockDirection.RIGHT_TO_LEFT
VirtualButtonBar.FullscreenDock.BOTTOM,
-> visualDirection == DockDirection.RIGHT_TO_LEFT
VirtualButtonBar.FullscreenDock.LEFT,
VirtualButtonBar.FullscreenDock.RIGHT -> visualDirection == DockDirection.BOTTOM_TO_TOP
VirtualButtonBar.FullscreenDock.RIGHT,
-> visualDirection == DockDirection.BOTTOM_TO_TOP
}
}
@@ -707,7 +680,7 @@ fun FullscreenControlPage(
Modifier.pointerInteropFilter { event ->
touchEventHandler.handleMotionEvent(event)
}
else Modifier
else Modifier,
)
.onSizeChanged { size ->
touchAreaSize = size
@@ -736,7 +709,7 @@ fun FullscreenControlPage(
else
Modifier
.fillMaxHeight()
.aspectRatio(sessionAspect)
.aspectRatio(sessionAspect),
),
) {
ScrcpyVideoSurface(
@@ -750,7 +723,7 @@ fun FullscreenControlPage(
bounds.top.toInt(),
bounds.right.toInt(),
bounds.bottom.toInt(),
)
),
)
},
session = session,
@@ -779,7 +752,7 @@ fun FullscreenControlPage(
.align(Alignment.TopStart)
.padding(start = UiSpacing.ContentVertical, top = UiSpacing.ContentVertical)
.background(Color.Black.copy(alpha = 0.5f))
.padding(horizontal = UiSpacing.ContentVertical, vertical = UiSpacing.Medium)
.padding(horizontal = UiSpacing.ContentVertical, vertical = UiSpacing.Medium),
) {
Column(verticalArrangement = Arrangement.spacedBy(UiSpacing.Tiny)) {
Text(

View File

@@ -1,28 +1,14 @@
package io.github.miuzarte.scrcpyforandroid.pages
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
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.rememberUpdatedState
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
@@ -50,22 +36,8 @@ import io.github.miuzarte.scrcpyforandroid.ui.BlurredBar
import io.github.miuzarte.scrcpyforandroid.ui.LocalEnableBlur
import io.github.miuzarte.scrcpyforandroid.ui.contextClick
import io.github.miuzarte.scrcpyforandroid.ui.rememberBlurBackdrop
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import top.yukonga.miuix.kmp.basic.Card
import top.yukonga.miuix.kmp.basic.HorizontalDivider
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.Text
import top.yukonga.miuix.kmp.basic.TextButton
import top.yukonga.miuix.kmp.basic.TextField
import top.yukonga.miuix.kmp.basic.TopAppBar
import kotlinx.coroutines.*
import top.yukonga.miuix.kmp.basic.*
import top.yukonga.miuix.kmp.blur.layerBackdrop
import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme
import top.yukonga.miuix.kmp.theme.MiuixTheme.textStyles
@@ -168,7 +140,7 @@ private fun RecordPreferencesPage(
TextFieldValue(
text = soBundle.recordFilename,
selection = TextRange(soBundle.recordFilename.length),
)
),
)
}
@@ -225,7 +197,7 @@ private fun RecordPreferencesPage(
RecordFilenameTemplate.resolve(
template = draftTemplate.text,
sessionInfo = previewSessionInfo,
)
),
)
}
val listState = rememberSaveable(
@@ -293,7 +265,7 @@ private fun RecordPreferencesPage(
if (index > 0) HorizontalDivider()
Column(
modifier = Modifier.padding(horizontal = UiSpacing.Large),
verticalArrangement = Arrangement.spacedBy(UiSpacing.Small)
verticalArrangement = Arrangement.spacedBy(UiSpacing.Small),
) {
Text(
text = entry.value,
@@ -302,7 +274,7 @@ private fun RecordPreferencesPage(
)
Text(
text = stringResource(
entry.descriptionResId ?: R.string.record_plain_text
entry.descriptionResId ?: R.string.record_plain_text,
),
color = colorScheme.onSurfaceVariantSummary,
fontSize = textStyles.body2.fontSize,

View File

@@ -2,16 +2,8 @@ package io.github.miuzarte.scrcpyforandroid.pages
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
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.rememberUpdatedState
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import io.github.miuzarte.scrcpyforandroid.R
@@ -20,11 +12,7 @@ import io.github.miuzarte.scrcpyforandroid.models.DeviceShortcuts
import io.github.miuzarte.scrcpyforandroid.scaffolds.ReorderableList
import io.github.miuzarte.scrcpyforandroid.storage.Settings
import io.github.miuzarte.scrcpyforandroid.storage.Storage.quickDevices
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import top.yukonga.miuix.kmp.overlay.OverlayBottomSheet
@Composable

View File

@@ -4,16 +4,7 @@ import android.content.pm.ActivityInfo
import android.graphics.Rect
import android.util.Rational
import androidx.activity.compose.LocalActivity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.*
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner

View File

@@ -7,30 +7,12 @@ import android.view.KeyEvent
import android.view.MotionEvent
import android.view.ViewConfiguration
import androidx.compose.foundation.clickable
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.WindowInsets
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.ime
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ContentCopy
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.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -56,14 +38,7 @@ import io.github.miuzarte.scrcpyforandroid.ui.BlurredBar
import io.github.miuzarte.scrcpyforandroid.ui.LocalEnableBlur
import io.github.miuzarte.scrcpyforandroid.ui.contextClick
import io.github.miuzarte.scrcpyforandroid.ui.rememberBlurBackdrop
import 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.SmallTopAppBar
import top.yukonga.miuix.kmp.basic.Text
import top.yukonga.miuix.kmp.basic.TextField
import top.yukonga.miuix.kmp.basic.*
import top.yukonga.miuix.kmp.blur.layerBackdrop
import top.yukonga.miuix.kmp.icon.MiuixIcons
import top.yukonga.miuix.kmp.icon.extended.More
@@ -123,9 +98,9 @@ fun TerminalScreen(
onClick = {
output = ""
},
)
)
)
),
),
),
) {
Icon(
imageVector = MiuixIcons.More,
@@ -140,7 +115,7 @@ fun TerminalScreen(
Box(
modifier =
if (blurActive) Modifier.layerBackdrop(blurBackdrop)
else Modifier
else Modifier,
) {
TerminalPage(
viewModel = viewModel,
@@ -360,7 +335,7 @@ private fun TerminalPage(
terminalView?.setTerminalCursorBlinkerRate(500)
terminalView?.setTerminalCursorBlinkerState(
start = true,
startOnlyIfCursorEnabled = true
startOnlyIfCursorEnabled = true,
)
viewModel.syncOutput { onOutputChange(it) }
}
@@ -419,7 +394,7 @@ private fun TerminalPage(
// theme changes
LaunchedEffect(
colorScheme.surface,
colorScheme.onSurface
colorScheme.onSurface,
) {
applyTheme()
terminalView?.onScreenUpdated()
@@ -546,7 +521,7 @@ private fun TerminalPage(
TerminalExtraKeyButton(
"CTRL",
Modifier.weight(1f),
active = viewModel.ctrlLatched
active = viewModel.ctrlLatched,
) {
haptic.contextClick()
viewModel.ctrlLatched = !viewModel.ctrlLatched
@@ -554,7 +529,7 @@ private fun TerminalPage(
TerminalExtraKeyButton(
"ALT",
Modifier.weight(1f),
active = viewModel.altLatched
active = viewModel.altLatched,
) {
haptic.contextClick()
viewModel.altLatched = !viewModel.altLatched
@@ -639,7 +614,7 @@ private fun TerminalOutputBottomSheet(
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(2f / 3f)
.fillMaxHeight(2f / 3f),
) {
item {
TextField(

View File

@@ -16,15 +16,11 @@ import io.github.miuzarte.scrcpyforandroid.services.AppRuntime
import io.github.miuzarte.scrcpyforandroid.services.LocalInputService
import io.github.miuzarte.scrcpyforandroid.storage.BundleSyncDelegate
import io.github.miuzarte.scrcpyforandroid.storage.Storage.appSettings
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import top.yukonga.miuix.kmp.basic.SnackbarResult
import java.nio.charset.StandardCharsets
import kotlin.math.roundToInt

View File

@@ -20,7 +20,7 @@ private val OS2_PHONE_LIGHT = BgEffectConfig.Config(
0.95f,
0.14f,
0.27f,
0.8f
0.8f,
),
colors1 = OS2_PHONE_LIGHT_COLORS,
colors2 = OS2_PHONE_LIGHT_COLORS,
@@ -51,7 +51,7 @@ private val OS2_PHONE_DARK = BgEffectConfig.Config(
0.81f,
0.14f,
0.24f,
0.72f
0.72f,
),
colors1 = OS2_PHONE_DARK_COLORS,
colors2 = OS2_PHONE_DARK_COLORS,
@@ -82,7 +82,7 @@ private val OS2_PAD_LIGHT = BgEffectConfig.Config(
0.68f,
0.28f,
0.26f,
0.62f
0.62f,
),
colors1 = OS2_PAD_LIGHT_COLORS,
colors2 = OS2_PAD_LIGHT_COLORS,
@@ -113,7 +113,7 @@ private val OS2_PAD_DARK = BgEffectConfig.Config(
0.71f,
0.43f,
0.09f,
0.75f
0.75f,
),
colors1 = OS2_PAD_DARK_COLORS,
colors2 = OS2_PAD_DARK_COLORS,

View File

@@ -18,7 +18,7 @@ private val OS3_PHONE_LIGHT = BgEffectConfig.Config(
0.64f,
0.65f,
0.98f,
1.0f
1.0f,
),
colors2 = floatArrayOf(
0.58f,
@@ -36,7 +36,7 @@ private val OS3_PHONE_LIGHT = BgEffectConfig.Config(
0.97f,
0.77f,
0.84f,
1.0f
1.0f,
),
colors3 = floatArrayOf(
0.98f,
@@ -54,7 +54,7 @@ private val OS3_PHONE_LIGHT = BgEffectConfig.Config(
0.56f,
0.69f,
1.0f,
1.0f
1.0f,
),
colorInterpPeriod = 5.0f,
lightOffset = 0.1f,
@@ -80,7 +80,7 @@ private val OS3_PHONE_DARK = BgEffectConfig.Config(
0.11f,
0.16f,
0.83f,
0.4f
0.4f,
),
colors2 = floatArrayOf(
0.07f,
@@ -98,7 +98,7 @@ private val OS3_PHONE_DARK = BgEffectConfig.Config(
0.0f,
0.2f,
0.78f,
0.5f
0.5f,
),
colors3 = floatArrayOf(
0.58f,
@@ -116,7 +116,7 @@ private val OS3_PHONE_DARK = BgEffectConfig.Config(
0.12f,
0.16f,
0.7f,
0.6f
0.6f,
),
colorInterpPeriod = 8.0f,
lightOffset = 0.0f,
@@ -142,7 +142,7 @@ private val OS3_PAD_LIGHT = BgEffectConfig.Config(
0.98f,
0.76f,
0.8f,
1.0f
1.0f,
),
colors2 = floatArrayOf(
0.66f,
@@ -160,7 +160,7 @@ private val OS3_PAD_LIGHT = BgEffectConfig.Config(
0.97f,
0.77f,
0.84f,
1.0f
1.0f,
),
colors3 = floatArrayOf(
0.97f,
@@ -178,7 +178,7 @@ private val OS3_PAD_LIGHT = BgEffectConfig.Config(
0.72f,
0.73f,
0.98f,
1.0f
1.0f,
),
colorInterpPeriod = 7.0f,
lightOffset = 0.1f,
@@ -204,7 +204,7 @@ private val OS3_PAD_DARK = BgEffectConfig.Config(
0.14f,
0.18f,
0.55f,
0.5f
0.5f,
),
colors2 = floatArrayOf(
0.07f,
@@ -222,7 +222,7 @@ private val OS3_PAD_DARK = BgEffectConfig.Config(
0.66f,
0.26f,
0.62f,
0.5f
0.5f,
),
colors3 = floatArrayOf(
0.58f,
@@ -240,7 +240,7 @@ private val OS3_PAD_DARK = BgEffectConfig.Config(
0.27f,
0.18f,
0.6f,
0.6f
0.6f,
),
colorInterpPeriod = 7.0f,
lightOffset = 0.0f,

View File

@@ -1,12 +1,6 @@
package io.github.miuzarte.scrcpyforandroid.pages.effect
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.withFrameNanos
import androidx.compose.runtime.*
@Composable
internal fun rememberFrameTimeSeconds(

View File

@@ -71,7 +71,7 @@ object BiometricGate {
}
override fun onAuthenticationFailed() = Unit
}
},
)
prompt.authenticate(
@@ -79,7 +79,7 @@ object BiometricGate {
.setAllowedAuthenticators(ALLOWED_AUTHENTICATORS)
.setTitle(title)
.setSubtitle(subtitle)
.build()
.build(),
)
continuation.invokeOnCancellation {

View File

@@ -6,11 +6,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Block
import androidx.compose.material.icons.rounded.Password
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
@@ -20,12 +16,7 @@ import io.github.miuzarte.scrcpyforandroid.constants.UiSpacing
import io.github.miuzarte.scrcpyforandroid.services.AppRuntime
import io.github.miuzarte.scrcpyforandroid.storage.Storage.appSettings
import kotlinx.coroutines.launch
import top.yukonga.miuix.kmp.basic.DropdownDefaults
import top.yukonga.miuix.kmp.basic.DropdownItem
import top.yukonga.miuix.kmp.basic.Icon
import top.yukonga.miuix.kmp.basic.ListPopupColumn
import top.yukonga.miuix.kmp.basic.SpinnerItemImpl
import top.yukonga.miuix.kmp.basic.Text
import top.yukonga.miuix.kmp.basic.*
import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme
@Composable

View File

@@ -191,7 +191,8 @@ object PasswordRepository {
is PasswordStorageCorruptedException -> throwable.cause ?: throwable
is KeyStoreException,
is GeneralSecurityException,
is KeyPermanentlyInvalidatedException -> throwable
is KeyPermanentlyInvalidatedException,
-> throwable
else -> throwable
}

View File

@@ -27,7 +27,7 @@ class PasswordUseCase {
val password = entry.cipherText
?: return Result.failure(
IllegalStateException(AppRuntime.stringResource(R.string.password_expired))
IllegalStateException(AppRuntime.stringResource(R.string.password_expired)),
)
return Result.success(password.copyOf())
}

View File

@@ -1,17 +1,9 @@
package io.github.miuzarte.scrcpyforandroid.scaffolds
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource

View File

@@ -1,15 +1,7 @@
package io.github.miuzarte.scrcpyforandroid.scaffolds
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyListState
@@ -60,7 +52,7 @@ fun LazyColumn(
Modifier.pointerInput(Unit) {
detectTapGestures(onTap = { focusManager.clearFocus() })
}
else Modifier
else Modifier,
),
) {
val contentWidthModifier =
@@ -78,7 +70,7 @@ fun LazyColumn(
.then(
if (scrollBehavior != null)
Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
else Modifier
else Modifier,
),
state = state,
contentPadding = mergedContentPadding,

View File

@@ -9,11 +9,7 @@ import androidx.compose.ui.unit.Dp
import io.github.miuzarte.scrcpyforandroid.R
import io.github.miuzarte.scrcpyforandroid.miuix.OverlaySpinnerPreference
import io.github.miuzarte.scrcpyforandroid.miuix.SpinnerEntry
import top.yukonga.miuix.kmp.basic.BasicComponentColors
import top.yukonga.miuix.kmp.basic.BasicComponentDefaults
import top.yukonga.miuix.kmp.basic.DropdownColors
import top.yukonga.miuix.kmp.basic.DropdownDefaults
import top.yukonga.miuix.kmp.basic.InfiniteProgressIndicator
import top.yukonga.miuix.kmp.basic.*
/**
* [OverlaySpinnerPreference] wrapper with fallback value display and loading state.
@@ -57,7 +53,7 @@ fun OverlaySpinnerWithFallback(
SpinnerEntry(
title = overrideEndActionValue,
enabled = true, // 保持启用以显示蓝色而非灰色
)
),
)
}
if (dataLoading) {
@@ -66,7 +62,7 @@ fun OverlaySpinnerWithFallback(
icon = { mod -> InfiniteProgressIndicator(mod) },
title = textLoading,
enabled = false,
)
),
)
}
}

View File

@@ -1,15 +1,7 @@
package io.github.miuzarte.scrcpyforandroid.scaffolds
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.DragIndicator
import androidx.compose.material3.IconButton
@@ -94,7 +86,7 @@ class ReorderableList(
.height(IntrinsicSize.Min)
.padding(
horizontal = UiSpacing.CardTitle,
vertical = UiSpacing.FieldLabelBottom
vertical = UiSpacing.FieldLabelBottom,
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
@@ -108,14 +100,14 @@ class ReorderableList(
Modifier.clickable(onClick = item.onClick)
} else {
Modifier
}
},
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(UiSpacing.Small)
horizontalArrangement = Arrangement.spacedBy(UiSpacing.Small),
) {
if (item.icon != null) Icon(
item.icon,
contentDescription = item.title
contentDescription = item.title,
)
Column {
Text(
@@ -132,7 +124,7 @@ class ReorderableList(
}
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(UiSpacing.Small)
horizontalArrangement = Arrangement.spacedBy(UiSpacing.Small),
) {
item.endActions.forEach { action ->
EndActionView(
@@ -181,7 +173,7 @@ class ReorderableList(
modifier = Modifier
.padding(
horizontal = UiSpacing.CardTitle,
vertical = UiSpacing.FieldLabelBottom
vertical = UiSpacing.FieldLabelBottom,
),
horizontalAlignment = Alignment.CenterHorizontally,
) {
@@ -191,7 +183,7 @@ class ReorderableList(
Modifier.clickable(onClick = item.onClick)
} else {
Modifier
}
},
),
verticalAlignment = Alignment.CenterVertically,
) {

View File

@@ -3,18 +3,12 @@ package io.github.miuzarte.scrcpyforandroid.scaffolds
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.VisualTransformation

View File

@@ -1,15 +1,6 @@
package io.github.miuzarte.scrcpyforandroid.scrcpy
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.AudioSource
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.CameraFacing
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Codec
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.DisplayImePolicy
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.ListOptions
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.LogLevel
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Orientation
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.OrientationLock
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Tick
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.VideoSource
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.*
import io.github.miuzarte.scrcpyforandroid.services.NativeRecordingSupport
data class ClientOptions(
@@ -304,7 +295,7 @@ data class ClientOptions(
if (!video && !audio && !control) {
throw IllegalArgumentException(
"nothing to do"
"nothing to do",
)
}
@@ -315,13 +306,13 @@ data class ClientOptions(
if (newDisplay.isNotBlank()) {
if (videoSource != VideoSource.DISPLAY) {
throw IllegalArgumentException(
"--new-display is only available with --video-source=display"
"--new-display is only available with --video-source=display",
)
}
if (!video) {
throw IllegalArgumentException(
"--new-display is incompatible with --no-video"
"--new-display is incompatible with --no-video",
)
}
}
@@ -329,39 +320,39 @@ data class ClientOptions(
if (videoSource == VideoSource.CAMERA) {
if (displayId > 0) {
throw IllegalArgumentException(
"--display-id is only available with --video-source=display"
"--display-id is only available with --video-source=display",
)
}
if (displayImePolicy != DisplayImePolicy.UNDEFINED) {
throw IllegalArgumentException(
"--display-ime-policy is only available with --video-source=display"
"--display-ime-policy is only available with --video-source=display",
)
}
if (cameraId.isNotBlank() && cameraFacing != CameraFacing.ANY) {
throw IllegalArgumentException(
"Cannot specify both --camera-id and --camera-facing"
"Cannot specify both --camera-id and --camera-facing",
)
}
if (cameraSize.isNotBlank()) {
if (maxSize > 0u) {
throw IllegalArgumentException(
"Cannot specify both --camera-size and -m/--max-size"
"Cannot specify both --camera-size and -m/--max-size",
)
}
if (cameraAr.isNotBlank()) {
throw IllegalArgumentException(
"--camera-high-speed requires an explicit --camera-fps value"
"--camera-high-speed requires an explicit --camera-fps value",
)
}
}
if (cameraHighSpeed && cameraFps <= 0u) {
throw IllegalArgumentException(
"--camera-high-speed requires an explicit --camera-fps value"
"--camera-high-speed requires an explicit --camera-fps value",
)
}
@@ -376,13 +367,13 @@ data class ClientOptions(
|| cameraSize.isNotBlank()
) {
throw IllegalArgumentException(
"Camera options are only available with --video-source=camera"
"Camera options are only available with --video-source=camera",
)
}
if (displayId > 0 && newDisplay.isNotBlank()) {
throw IllegalArgumentException(
"Cannot specify both --display-id and --new-display"
"Cannot specify both --display-id and --new-display",
)
}
@@ -390,19 +381,19 @@ data class ClientOptions(
if (videoSource != VideoSource.DISPLAY || newDisplay.isBlank()) {
throw IllegalArgumentException(
"-x/--flex-display can only be applied to displays created " +
"with --new-display"
"with --new-display",
)
}
if (!control) {
throw IllegalArgumentException(
"-n/--no-control is not compatible with -x/--flex-display"
"-n/--no-control is not compatible with -x/--flex-display",
)
}
if (crop.isNotBlank()) {
throw IllegalArgumentException(
"--crop is not compatible with -x/--flex-display"
"--crop is not compatible with -x/--flex-display",
)
}
}
@@ -411,7 +402,7 @@ data class ClientOptions(
&& displayId == 0 && newDisplay.isBlank()
) {
throw IllegalArgumentException(
"--display-ime-policy is only supported on a secondary display"
"--display-ime-policy is only supported on a secondary display",
)
}
@@ -432,27 +423,27 @@ data class ClientOptions(
if (audioDup) {
if (!audio) {
throw IllegalArgumentException(
"--audio-dup not supported if audio is disabled"
"--audio-dup not supported if audio is disabled",
)
}
if (audioSource != AudioSource.PLAYBACK) {
throw IllegalArgumentException(
"--audio-dup is specific to --audio-source=playback"
"--audio-dup is specific to --audio-source=playback",
)
}
}
if (recordFormat != RecordFormat.AUTO && recordFilename.isBlank()) {
throw IllegalArgumentException(
"Record format specified without recording"
"Record format specified without recording",
)
}
if (recordFilename.isNotBlank()) {
if (!video && !audio) {
throw IllegalArgumentException(
"Video and audio disabled, nothing to record"
"Video and audio disabled, nothing to record",
)
}
@@ -461,14 +452,14 @@ data class ClientOptions(
if (recordFormat == RecordFormat.AUTO) {
throw IllegalArgumentException(
"No format specified for recording file " +
"(try with --record-format=mp4)"
"(try with --record-format=mp4)",
)
}
}
if (!NativeRecordingSupport.isSupported(recordFormat)) {
throw IllegalArgumentException(
"Android native recording currently supports only MP4/M4A"
"Android native recording currently supports only MP4/M4A",
)
}
@@ -476,14 +467,14 @@ data class ClientOptions(
if (recordOrientation.isMirror()) {
throw IllegalArgumentException(
"Record orientation only supports rotation, " +
"not flipping: ${recordOrientation.string}"
"not flipping: ${recordOrientation.string}",
)
}
}
if (video && recordFormat.isAudioOnly()) {
throw IllegalArgumentException(
"Audio container does not support video stream"
"Audio container does not support video stream",
)
}
@@ -554,32 +545,32 @@ data class ClientOptions(
if (!control) {
if (turnScreenOff) {
throw IllegalArgumentException(
"Cannot request to turn screen off if control is disabled"
"Cannot request to turn screen off if control is disabled",
)
}
if (stayAwake) {
throw IllegalArgumentException(
"Cannot request to stay awake if control is disabled"
"Cannot request to stay awake if control is disabled",
)
}
if (showTouches) {
throw IllegalArgumentException(
"Cannot request to show touches if control is disabled"
"Cannot request to show touches if control is disabled",
)
}
if (powerOffOnClose) {
throw IllegalArgumentException(
"Cannot request power off on close if control is disabled"
"Cannot request power off on close if control is disabled",
)
}
if (startApp.isNotBlank()) {
throw IllegalArgumentException(
"Cannot start an Android app if control is disabled"
"Cannot start an Android app if control is disabled",
)
}
if (keepActive) {
throw IllegalArgumentException(
"Cannot keep device active if control is disabled"
"Cannot keep device active if control is disabled",
)
}
}

View File

@@ -12,36 +12,18 @@ import io.github.miuzarte.scrcpyforandroid.constants.Defaults
import io.github.miuzarte.scrcpyforandroid.nativecore.AdbSocketStream
import io.github.miuzarte.scrcpyforandroid.nativecore.NativeAdbService
import io.github.miuzarte.scrcpyforandroid.nativecore.ScrcpyAudioPlayer
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.CameraFacing
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Codec
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.EncoderType
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.ListOptions
import io.github.miuzarte.scrcpyforandroid.services.AppRuntime
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.*
import io.github.miuzarte.scrcpyforandroid.services.*
import io.github.miuzarte.scrcpyforandroid.services.EventLogger.logEvent
import io.github.miuzarte.scrcpyforandroid.services.LocalInputService
import io.github.miuzarte.scrcpyforandroid.services.NativeAacRecorder
import io.github.miuzarte.scrcpyforandroid.services.NativeMp4Recorder
import io.github.miuzarte.scrcpyforandroid.services.NativeWavRecorder
import io.github.miuzarte.scrcpyforandroid.services.RecordingFileResolver
import io.github.miuzarte.scrcpyforandroid.storage.Storage.appSettings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.io.BufferedInputStream
import java.io.BufferedReader
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.EOFException
import java.io.File
import java.io.InputStreamReader
import java.io.*
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.ArrayDeque
@@ -228,7 +210,7 @@ class Scrcpy(
TAG,
"start(): create audio player codecId=0x${
info.audioCodecId.toUInt().toString(16)
}"
}",
)
val player = ScrcpyAudioPlayer(appContext, info.audioCodecId, lowLatency)
audioPlayer = player
@@ -243,7 +225,8 @@ class Scrcpy(
val recordFile = RecordingFileResolver.resolve(options, info)
when (options.recordFormat) {
ClientOptions.RecordFormat.MP4,
ClientOptions.RecordFormat.M4A -> {
ClientOptions.RecordFormat.M4A,
-> {
val recorder = NativeMp4Recorder(
outputFile = recordFile,
includeVideo = options.video,
@@ -296,13 +279,16 @@ class Scrcpy(
}
Log.i(
TAG, "start(): Session started successfully - device=${info.deviceName}, " +
"video=${if (options.video) buildString {
TAG,
"start(): Session started successfully - device=${info.deviceName}, " +
"video=${
if (options.video) buildString {
append(info.codec?.string ?: "null")
if (info.width > 0 && info.height > 0) append(" ${info.width}x${info.height}")
} else "off"}, " +
} else "off"
}, " +
"audio=${if (options.audio) options.audioCodec.string else "off"}, " +
"control=${options.control}"
"control=${options.control}",
)
return@withContext info
@@ -637,9 +623,11 @@ class Scrcpy(
runTrackedFetch {
val output = executeList(ListOptions.CAMERA_SIZES)
val parsed = parseCameraSizes(output)
.sortedWith(compareByDescending { size ->
.sortedWith(
compareByDescending { size ->
size.substringBefore('x').toIntOrNull() ?: 0
})
},
)
cachedCameraSizes = parsed
logListPreview(
list = ListOptions.CAMERA_SIZES,
@@ -789,7 +777,7 @@ class Scrcpy(
id = match.groupValues[1].toInt(),
width = match.groupValues[2].toInt(),
height = match.groupValues[3].toInt(),
)
),
)
}
return displays.toList()
@@ -810,7 +798,7 @@ class Scrcpy(
facing = CameraFacing.fromString(facing),
activeSize = activeSize,
fps = fpsValues.map(Int::toUShort),
)
),
)
}
return cameras.toList()
@@ -832,7 +820,7 @@ class Scrcpy(
system = match.groupValues[1] == "*",
label = match.groupValues[2].trim(),
packageName = match.groupValues[3].trim(),
)
),
)
}
return apps.toList().sortedBy { appSortKey(it) }
@@ -1079,7 +1067,7 @@ class Scrcpy(
Log.w(TAG, "audio disabled by server")
if (options.requireAudio) {
throw IllegalStateException(
"Audio is required but was disabled by the server"
"Audio is required but was disabled by the server",
)
}
0
@@ -1089,7 +1077,7 @@ class Scrcpy(
Log.e(TAG, "audio stream configuration error from server")
if (options.requireAudio) {
throw IllegalStateException(
"Audio is required but the server failed to configure audio capture"
"Audio is required but the server failed to configure audio capture",
)
}
0
@@ -1098,7 +1086,7 @@ class Scrcpy(
else -> {
Log.i(
TAG,
"audio stream codec=0x${streamCodecId.toUInt().toString(16)}"
"audio stream codec=0x${streamCodecId.toUInt().toString(16)}",
)
streamCodecId
}
@@ -1191,7 +1179,7 @@ class Scrcpy(
(headerBuf[11].toInt() and 0xFF)
Log.i(
TAG,
"video session packet: ${sw}x${sh} clientResized=$clientResized"
"video session packet: ${sw}x${sh} clientResized=$clientResized",
)
onVideoSessionSize(sw, sh)
continue
@@ -1261,7 +1249,7 @@ class Scrcpy(
val packet = AudioPacket(
data = payload,
ptsUs = ptsUs,
isConfig = isConfig
isConfig = isConfig,
)
audioConsumers.forEach { it(packet) }
} catch (_: EOFException) {
@@ -1349,7 +1337,7 @@ class Scrcpy(
screenHeight,
pressure,
actionButton,
buttons
buttons,
)
} catch (e: IllegalStateException) {
Log.w(TAG, "injectTouch(): control channel not available", e)
@@ -1373,7 +1361,7 @@ class Scrcpy(
screenHeight,
hScroll,
vScroll,
buttons
buttons,
)
} catch (e: IllegalStateException) {
Log.w(TAG, "injectScroll(): control channel not available", e)
@@ -1496,15 +1484,15 @@ class Scrcpy(
private fun startServerLogThread(
serverStream: AdbSocketStream,
socketName: String
socketName: String,
): Thread {
return thread(start = true, name = "scrcpy-server-log") {
try {
BufferedReader(
InputStreamReader(
serverStream.inputStream,
Charsets.UTF_8
)
Charsets.UTF_8,
),
).use { reader ->
while (true) {
val line = reader.readLine() ?: break
@@ -1539,7 +1527,7 @@ class Scrcpy(
private suspend fun openAbstractSocketWithRetry(
socketName: String,
expectDummyByte: Boolean
expectDummyByte: Boolean,
): AdbSocketStream {
var lastEx: Exception? = null
repeat(CONNECT_RETRY_COUNT) { attempt ->
@@ -1713,7 +1701,7 @@ class Scrcpy(
screenHeight: Int,
hScroll: Float,
vScroll: Float,
buttons: Int
buttons: Int,
) {
output.writeByte(TYPE_INJECT_SCROLL_EVENT)
writePosition(x, y, screenWidth, screenHeight)

View File

@@ -1,15 +1,6 @@
package io.github.miuzarte.scrcpyforandroid.scrcpy
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.AudioSource
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.CameraFacing
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Codec
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.DisplayImePolicy
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.ListOptions
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.LogLevel
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Orientation
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.OrientationLock
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Tick
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.VideoSource
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.*
// 启动 scrcpy 前直接继承自 [ClientOptions],
// 不需要默认值

View File

@@ -186,7 +186,8 @@ class TouchEventHandler(
val isHoverMotion = when (event.actionMasked) {
MotionEvent.ACTION_HOVER_ENTER,
MotionEvent.ACTION_HOVER_MOVE,
MotionEvent.ACTION_HOVER_EXIT -> true
MotionEvent.ACTION_HOVER_EXIT,
-> true
MotionEvent.ACTION_MOVE -> buttons == 0
else -> false
@@ -201,7 +202,8 @@ class TouchEventHandler(
) {
when (event.actionMasked) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_BUTTON_PRESS -> {
MotionEvent.ACTION_BUTTON_PRESS,
-> {
coroutineScope.launch {
runCatching {
onBackOrScreenOn(0)
@@ -212,7 +214,8 @@ class TouchEventHandler(
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_BUTTON_RELEASE -> {
MotionEvent.ACTION_BUTTON_RELEASE,
-> {
coroutineScope.launch {
runCatching {
onBackOrScreenOn(1)
@@ -234,7 +237,8 @@ class TouchEventHandler(
MotionEvent.ACTION_MOVE -> MotionEvent.ACTION_MOVE
MotionEvent.ACTION_BUTTON_PRESS,
MotionEvent.ACTION_BUTTON_RELEASE -> return true
MotionEvent.ACTION_BUTTON_RELEASE,
-> return true
else -> return true
}
@@ -343,7 +347,7 @@ class TouchEventHandler(
Log.w(
FULLSCREEN_TOUCH_LOG_TAG,
"handlePointerDown failed for pointerId=$pointerId",
e
e,
)
}
}
@@ -373,7 +377,7 @@ class TouchEventHandler(
Log.w(
FULLSCREEN_TOUCH_LOG_TAG,
"handlePointerMove failed for pointerId=$pointerId",
e
e,
)
}
}

View File

@@ -2,7 +2,6 @@ package io.github.miuzarte.scrcpyforandroid.services
import android.content.Context
import androidx.annotation.StringRes
import io.github.miuzarte.scrcpyforandroid.R
import io.github.miuzarte.scrcpyforandroid.models.ConnectionTarget
import io.github.miuzarte.scrcpyforandroid.nativecore.AdbMdnsDiscoverer
import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy

View File

@@ -1,11 +1,7 @@
package io.github.miuzarte.scrcpyforandroid.services
import io.github.miuzarte.scrcpyforandroid.models.DeviceShortcut
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExecutorCoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import kotlinx.coroutines.*
import java.io.Closeable
import java.util.concurrent.Executors

View File

@@ -25,7 +25,7 @@ data class ConnectedDeviceInfo(
internal suspend fun fetchConnectedDeviceInfo(
adbService: NativeAdbService,
host: String,
port: Int
port: Int,
): ConnectedDeviceInfo = withContext(Dispatchers.IO) {
val values = runCatching {
adbService.shellBatch {

View File

@@ -86,7 +86,7 @@ object FileManagerService {
private val listTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
private val displayTimeFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")
private val listLineRegex = Regex(
"""^\s*(\d+)\s+([\-bcdlps][rwxstST-]{9})\s+(\d+)\s+(\S+)\s+(\S+)\s+(\d+)\s+(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})\s+(.+)$"""
"""^\s*(\d+)\s+([\-bcdlps][rwxstST-]{9})\s+(\d+)\s+(\S+)\s+(\S+)\s+(\d+)\s+(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})\s+(.+)$""",
)
private val sizeFormatter = DecimalFormat("0.00")
@@ -101,7 +101,7 @@ object FileManagerService {
.filterNot { it.name == "." || it.name == ".." }
.sortedWith(
compareByDescending<RemoteFileEntry> { it.isDirectory }
.thenBy { it.name.lowercase() }
.thenBy { it.name.lowercase() },
)
.toList()
}
@@ -173,7 +173,7 @@ object FileManagerService {
targetFile.outputStream().use { output ->
NativeAdbService.pull(
joinRemotePath(snapshot.remoteRootPath, relativePath),
output
output,
)
}
}
@@ -189,7 +189,7 @@ object FileManagerService {
NativeAdbService.ensureConnectionResponsive()
val rootDocument = DocumentsContract.buildDocumentUriUsingTree(
treeUri,
DocumentsContract.getTreeDocumentId(treeUri)
DocumentsContract.getTreeDocumentId(treeUri),
)
val target = createUniqueDocument(context, rootDocument, fileName, guessMimeType(fileName))
context.contentResolver.openOutputStream(target, "w").use { output ->
@@ -206,7 +206,7 @@ object FileManagerService {
NativeAdbService.ensureConnectionResponsive()
val rootDocument = DocumentsContract.buildDocumentUriUsingTree(
treeUri,
DocumentsContract.getTreeDocumentId(treeUri)
DocumentsContract.getTreeDocumentId(treeUri),
)
val folderName = snapshot.remoteRootPath.substringAfterLast('/').ifBlank { "Scrcpy" }
val folderRoot = createUniqueDirectoryDocument(context, rootDocument, folderName)
@@ -327,7 +327,7 @@ object FileManagerService {
val dateTime = runCatching {
LocalDateTime.parse(
"${match.groupValues[7]} ${match.groupValues[8]}",
listTimeFormatter
listTimeFormatter,
)
}.getOrNull()
val nameField = match.groupValues[9]
@@ -542,7 +542,7 @@ object FileManagerService {
private fun createUniqueDirectoryDocument(
context: Context,
parentDocument: Uri,
baseName: String
baseName: String,
): Uri {
var name = baseName
var index = 1
@@ -551,7 +551,7 @@ object FileManagerService {
context,
parentDocument,
name,
DocumentsContract.Document.MIME_TYPE_DIR
DocumentsContract.Document.MIME_TYPE_DIR,
) != null
) {
name = "$baseName ($index)"
@@ -592,7 +592,7 @@ object FileManagerService {
private fun ensureTreeDirectory(
context: Context,
rootDocument: Uri,
relativePath: String
relativePath: String,
): Uri {
var current = rootDocument
if (relativePath.isBlank()) return current
@@ -603,14 +603,15 @@ object FileManagerService {
context,
current,
segment,
DocumentsContract.Document.MIME_TYPE_DIR
DocumentsContract.Document.MIME_TYPE_DIR,
)
current = existing ?: DocumentsContract.createDocument(
context.contentResolver,
current,
DocumentsContract.Document.MIME_TYPE_DIR,
segment,
) ?: throw IOException("${AppRuntime.stringResource(R.string.fm_exception_cannot_create_dir)}: $segment")
)
?: throw IOException("${AppRuntime.stringResource(R.string.fm_exception_cannot_create_dir)}: $segment")
}
return current
}
@@ -657,11 +658,11 @@ class DirectorySnapshotSession private constructor(
suspend fun load(path: String): DirectoryDownloadSnapshot {
val normalizedPath = path.trimEnd('/').ifBlank { "/" }
val totalBytes = parseDuBytes(
shellSession.execute("du -sb ${FileManagerService.quoteShellArg(normalizedPath)}")
shellSession.execute("du -sb ${FileManagerService.quoteShellArg(normalizedPath)}"),
)
val directories = shellSession.execute(
"find ${FileManagerService.quoteShellArg(normalizedPath)} -type d"
"find ${FileManagerService.quoteShellArg(normalizedPath)} -type d",
)
.lineSequence()
.map(String::trim)
@@ -671,7 +672,7 @@ class DirectorySnapshotSession private constructor(
.toList()
val files = shellSession.execute(
"find ${FileManagerService.quoteShellArg(normalizedPath)} -type f"
"find ${FileManagerService.quoteShellArg(normalizedPath)} -type f",
)
.lineSequence()
.map(String::trim)

View File

@@ -9,14 +9,14 @@ object PublicDirs {
fun transferDirectory(): File {
return File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
ROOT_DIRECTORY
ROOT_DIRECTORY,
)
}
fun recordDirectory(): File {
return File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
ROOT_DIRECTORY
ROOT_DIRECTORY,
)
}
}

View File

@@ -3,12 +3,7 @@ package io.github.miuzarte.scrcpyforandroid.storage
import android.content.Context
import android.os.Parcelable
import androidx.annotation.StringRes
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.floatPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.core.*
import io.github.miuzarte.scrcpyforandroid.R
import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy
import kotlinx.coroutines.flow.StateFlow
@@ -92,6 +87,7 @@ class AppSettings(context: Context) : Settings(context, "AppSettings") {
stringPreferencesKey("language_tag"),
"",
)
// Theme
val THEME_BASE_INDEX = Pair(
intPreferencesKey("theme_base_index"),

View File

@@ -1,29 +1,9 @@
package io.github.miuzarte.scrcpyforandroid.storage
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
/**
* Manages a local copy of a bundle from persistent storage with debounced save.

View File

@@ -2,25 +2,12 @@ package io.github.miuzarte.scrcpyforandroid.storage
import android.content.Context
import android.os.Parcelable
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.core.*
import io.github.miuzarte.scrcpyforandroid.R
import io.github.miuzarte.scrcpyforandroid.scrcpy.ClientOptions
import io.github.miuzarte.scrcpyforandroid.scrcpy.ClientOptions.KeyInjectMode
import io.github.miuzarte.scrcpyforandroid.scrcpy.ClientOptions.RecordFormat
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.AudioSource
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.CameraFacing
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Codec
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.DisplayImePolicy
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.ListOptions
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.LogLevel
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Orientation
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.OrientationLock
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Tick
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.VideoSource
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.*
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.runBlocking
import kotlinx.parcelize.Parcelize

View File

@@ -36,7 +36,7 @@ class ScrcpyProfiles(context: Context) : Settings(context, "ScrcpyProfiles") {
private fun stateFromPreferences(preferences: androidx.datastore.preferences.core.Preferences): State {
val raw = preferences.read(PROFILES_JSON)
return normalizeState(
runCatching { decodeState(raw) }.getOrNull() ?: State(emptyList())
runCatching { decodeState(raw) }.getOrNull() ?: State(emptyList()),
)
}
@@ -107,8 +107,8 @@ class ScrcpyProfiles(context: Context) : Settings(context, "ScrcpyProfiles") {
current.copy(
profiles = current.profiles.map {
if (it.id == id) updated else it
}
)
},
),
)
saveState(next)
return next.profiles.firstOrNull { it.id == id }
@@ -120,8 +120,8 @@ class ScrcpyProfiles(context: Context) : Settings(context, "ScrcpyProfiles") {
if (current.profiles.none { it.id == id }) return false
val next = normalizeState(
current.copy(
profiles = current.profiles.filterNot { it.id == id }
)
profiles = current.profiles.filterNot { it.id == id },
),
)
saveState(next)
return true
@@ -213,7 +213,7 @@ class ScrcpyProfiles(context: Context) : Settings(context, "ScrcpyProfiles") {
JSONObject()
.put("id", profile.id)
.put("name", profile.name)
.put("bundle", encodeBundleToJson(profile.bundle))
.put("bundle", encodeBundleToJson(profile.bundle)),
)
}
return array.toString()
@@ -236,7 +236,7 @@ class ScrcpyProfiles(context: Context) : Settings(context, "ScrcpyProfiles") {
name = name.ifBlank { textNewProfile },
bundle = decodeBundleFromJson(item.optJSONObject("bundle")),
isBuiltinGlobal = id == ScrcpyOptions.GLOBAL_PROFILE_ID,
)
),
)
}
}

View File

@@ -2,11 +2,7 @@ package io.github.miuzarte.scrcpyforandroid.storage
import android.content.Context
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.datastore.core.DataStore
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
@@ -14,17 +10,8 @@ import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlin.reflect.KProperty
import kotlin.time.Duration.Companion.milliseconds
@@ -37,7 +24,7 @@ abstract class Settings(
ReplaceFileCorruptionHandler {
Log.e(TAG, "Preferences corrupted, resetting.", it)
emptyPreferences()
}
},
) {
private val settingsScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
@@ -70,7 +57,7 @@ abstract class Settings(
* 设置项委托类,自动提供 get/set/observe/asState/asMutableState 方法
*/
inner class SettingProperty<T>(
val pair: Pair<T>
val pair: Pair<T>,
) {
// 创建时注册自身
init {
@@ -107,7 +94,7 @@ abstract class Settings(
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = this.name,
corruptionHandler = corruptionHandler,
scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
scope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
)
// 对外暴露的 DataStore 实例
@@ -121,7 +108,7 @@ abstract class Settings(
.stateIn(
scope = settingsScope,
started = SharingStarted.Eagerly,
initialValue = runBlocking { loadBundle(reader) }
initialValue = runBlocking { loadBundle(reader) },
)
protected suspend fun <B> loadBundle(reader: (Preferences) -> B): B =

View File

@@ -6,11 +6,7 @@ import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.platform.LocalDensity
import top.yukonga.miuix.kmp.blur.BlendColorEntry
import top.yukonga.miuix.kmp.blur.BlurColors
import top.yukonga.miuix.kmp.blur.LayerBackdrop
import top.yukonga.miuix.kmp.blur.rememberLayerBackdrop
import top.yukonga.miuix.kmp.blur.textureBlur
import top.yukonga.miuix.kmp.blur.*
import top.yukonga.miuix.kmp.shader.isRenderEffectSupported
import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme

View File

@@ -5,30 +5,9 @@ import androidx.compose.animation.core.EaseOut
import androidx.compose.animation.core.spring
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
@@ -84,7 +63,7 @@ fun RowScope.FloatingBottomBarItem(
interactionSource = null,
indication = null,
role = Role.Tab,
onClick = onClick
onClick = onClick,
)
.fillMaxHeight()
.weight(1f)
@@ -95,7 +74,7 @@ fun RowScope.FloatingBottomBarItem(
},
verticalArrangement = Arrangement.spacedBy(1.dp, Alignment.CenterVertically),
horizontalAlignment = Alignment.CenterHorizontally,
content = content
content = content,
)
}
@@ -184,13 +163,13 @@ fun FloatingBottomBar(
if (tabWidthPx > 0) {
updateValue(
(targetValue + dragAmount.x / tabWidthPx * if (isLtr) 1f else -1f)
.fastCoerceIn(0f, (tabsCount - 1).toFloat())
.fastCoerceIn(0f, (tabsCount - 1).toFloat()),
)
animationScope.launch {
offsetAnimation.snapTo(offsetAnimation.value + dragAmount.x)
}
}
}
},
).also { holder.instance = it }
}
@@ -214,15 +193,15 @@ fun FloatingBottomBar(
} else {
size.width - (dampedDragAnimation.value + 0.5f) * tabWidthPx + panelOffset
},
size.height / 2f
size.height / 2f,
)
}
},
)
}
Box(
modifier = modifier.width(IntrinsicSize.Min),
contentAlignment = Alignment.CenterStart
contentAlignment = Alignment.CenterStart,
) {
Row(
Modifier
@@ -235,7 +214,7 @@ fun FloatingBottomBar(
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = {}
onClick = {},
)
.drawBackdrop(
backdrop = backdrop,
@@ -263,20 +242,20 @@ fun FloatingBottomBar(
scaleY = scale
}
},
onDrawSurface = { drawRect(containerColor) }
onDrawSurface = { drawRect(containerColor) },
)
.then(if (isBlurEnabled) interactiveHighlight.modifier else Modifier)
.height(64.dp)
.padding(4.dp),
verticalAlignment = Alignment.CenterVertically,
content = content
content = content,
)
CompositionLocalProvider(
LocalFloatingBottomBarTabScale provides {
if (isBlurEnabled) lerp(1f, 1.2f, dampedDragAnimation.pressProgress)
else 1f
}
},
) {
Row(
Modifier
@@ -298,14 +277,14 @@ fun FloatingBottomBar(
highlight = {
Highlight.Default.copy(alpha = if (isBlurEnabled) dampedDragAnimation.pressProgress else 0f)
},
onDrawSurface = { drawRect(containerColor) }
onDrawSurface = { drawRect(containerColor) },
)
.then(if (isBlurEnabled) interactiveHighlight.modifier else Modifier)
.height(56.dp)
.padding(horizontal = 4.dp)
.graphicsLayer(colorFilter = ColorFilter.tint(accentColor)),
verticalAlignment = Alignment.CenterVertically,
content = content
content = content,
)
}
@@ -343,7 +322,7 @@ fun FloatingBottomBar(
innerShadow = {
InnerShadow(
radius = 8f.dp * dampedDragAnimation.pressProgress,
alpha = if (isBlurEnabled) dampedDragAnimation.pressProgress else 0f
alpha = if (isBlurEnabled) dampedDragAnimation.pressProgress else 0f,
)
},
layerBlock = {
@@ -364,15 +343,15 @@ fun FloatingBottomBar(
} else {
Color.White.copy(0.1f)
},
alpha = 1f - progress
alpha = 1f - progress,
)
drawRect(
Color.Black.copy(alpha = 0.03f * progress)
Color.Black.copy(alpha = 0.03f * progress),
)
}
},
)
.height(56.dp)
.width(with(density) { ((totalWidthPx - 8.dp.toPx()) / tabsCount).toDp() })
.width(with(density) { ((totalWidthPx - 8.dp.toPx()) / tabsCount).toDp() }),
)
}
}

View File

@@ -65,7 +65,7 @@ class DampedDragAnimation(
onDragCancel = {
onDragStopped()
release()
}
},
) { change, dragAmount ->
val position = change.position
val previousPosition = change.previousPosition
@@ -109,7 +109,7 @@ class DampedDragAnimation(
launch {
valueAnimation.animateTo(
targetValue,
valueAnimationSpec
valueAnimationSpec,
) { updateVelocity() }
}
}
@@ -132,7 +132,7 @@ class DampedDragAnimation(
private fun updateVelocity() {
velocityTracker.addPosition(
System.currentTimeMillis(),
Offset(value, 0f)
Offset(value, 0f),
)
val targetVelocity =
velocityTracker.calculateVelocity().x / (valueRange.endInclusive - valueRange.start)

View File

@@ -49,7 +49,7 @@ class InteractiveHighlight(
float dist = distance(coord, position);
float intensity = smoothstep(radius, radius * 0.5, dist);
return color * intensity;
}"""
}""",
)
val modifier: Modifier = Modifier.drawWithContent {
@@ -57,7 +57,7 @@ class InteractiveHighlight(
if (progress > 0f) {
drawRect(
Color.White.copy(0.06f * progress),
blendMode = BlendMode.Plus
blendMode = BlendMode.Plus,
)
shader.apply {
val shaderPosition = position(size, positionAnimation.value)
@@ -70,7 +70,7 @@ class InteractiveHighlight(
}
drawRect(
ShaderBrush(shader),
blendMode = BlendMode.Plus
blendMode = BlendMode.Plus,
)
}
@@ -97,7 +97,7 @@ class InteractiveHighlight(
launch { pressProgressAnimation.animateTo(0f, pressProgressAnimationSpec) }
launch { positionAnimation.animateTo(startPosition, positionAnimationSpec) }
}
}
},
) { change, _ ->
animationScope.launch { positionAnimation.snapTo(change.position) }
}

View File

@@ -3,13 +3,7 @@ package io.github.miuzarte.scrcpyforandroid.ui.component.miuix.modifier
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.AwaitPointerEventScope
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerId
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerInputScope
import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.input.pointer.*
import androidx.compose.ui.util.fastFirstOrNull
suspend fun PointerInputScope.inspectDragGestures(
@@ -26,7 +20,7 @@ suspend fun PointerInputScope.inspectDragGestures(
onDrag(initialDown, Offset.Zero)
val upEvent = drag(
pointerId = initialDown.id,
onDrag = { onDrag(it, it.positionChange()) }
onDrag = { onDrag(it, it.positionChange()) },
)
if (upEvent == null) {
onDragCancel()

View File

@@ -2,13 +2,7 @@ package io.github.miuzarte.scrcpyforandroid.widgets
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
@@ -22,11 +16,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import io.github.miuzarte.scrcpyforandroid.R
import io.github.miuzarte.scrcpyforandroid.constants.UiSpacing
import top.yukonga.miuix.kmp.basic.DropdownColors
import top.yukonga.miuix.kmp.basic.DropdownDefaults
import top.yukonga.miuix.kmp.basic.Icon
import top.yukonga.miuix.kmp.basic.IconButton
import top.yukonga.miuix.kmp.basic.Text
import top.yukonga.miuix.kmp.basic.*
import top.yukonga.miuix.kmp.icon.MiuixIcons
import top.yukonga.miuix.kmp.icon.extended.Store
import top.yukonga.miuix.kmp.overlay.OverlayBottomSheet

View File

@@ -1,19 +1,6 @@
package io.github.miuzarte.scrcpyforandroid.widgets
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.Alignment
@@ -129,7 +116,7 @@ internal fun StatusCardLayout(
Column(
modifier = Modifier
.weight(1f)
.fillMaxHeight()
.fillMaxHeight(),
) {
StatusMetricCard(
modifier = Modifier

View File

@@ -3,48 +3,16 @@ package io.github.miuzarte.scrcpyforandroid.widgets
import androidx.annotation.StringRes
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material.icons.automirrored.rounded.VolumeDown
import androidx.compose.material.icons.automirrored.rounded.VolumeOff
import androidx.compose.material.icons.automirrored.rounded.VolumeUp
import androidx.compose.material.icons.rounded.Apps
import androidx.compose.material.icons.rounded.ContentPaste
import androidx.compose.material.icons.rounded.DashboardCustomize
import androidx.compose.material.icons.rounded.Home
import androidx.compose.material.icons.rounded.Keyboard
import androidx.compose.material.icons.rounded.Menu
import androidx.compose.material.icons.rounded.Notifications
import androidx.compose.material.icons.rounded.Password
import androidx.compose.material.icons.rounded.PowerSettingsNew
import androidx.compose.material.icons.rounded.Screenshot
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.material.icons.rounded.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
@@ -69,20 +37,13 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import top.yukonga.miuix.kmp.basic.Button
import top.yukonga.miuix.kmp.basic.ButtonDefaults
import top.yukonga.miuix.kmp.basic.DropdownDefaults
import top.yukonga.miuix.kmp.basic.DropdownItem
import top.yukonga.miuix.kmp.basic.Icon
import top.yukonga.miuix.kmp.basic.ListPopupColumn
import top.yukonga.miuix.kmp.basic.ListPopupDefaults
import top.yukonga.miuix.kmp.basic.PopupPositionProvider
import top.yukonga.miuix.kmp.basic.SpinnerItemImpl
import top.yukonga.miuix.kmp.basic.Text
import top.yukonga.miuix.kmp.basic.*
import top.yukonga.miuix.kmp.icon.MiuixIcons
import top.yukonga.miuix.kmp.icon.extended.More
import top.yukonga.miuix.kmp.overlay.OverlayListPopup
import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme
import kotlin.ranges.coerceAtLeast
import kotlin.ranges.coerceIn
enum class VirtualButtonAction(
val id: String,
@@ -94,97 +55,97 @@ enum class VirtualButtonAction(
"more",
R.string.vb_more,
MiuixIcons.More,
null
null,
),
HOME(
"home",
R.string.vb_home,
Icons.Rounded.Home,
UiAndroidKeycodes.HOME
UiAndroidKeycodes.HOME,
),
BACK(
"back",
R.string.vb_back,
Icons.AutoMirrored.Rounded.ArrowBack,
UiAndroidKeycodes.BACK
UiAndroidKeycodes.BACK,
),
APP_SWITCH(
"app_switch",
R.string.vb_app_switch,
Icons.Rounded.Apps,
UiAndroidKeycodes.APP_SWITCH
UiAndroidKeycodes.APP_SWITCH,
),
MENU(
"menu",
R.string.vb_menu,
Icons.Rounded.Menu,
UiAndroidKeycodes.MENU
UiAndroidKeycodes.MENU,
),
NOTIFICATION(
"notification",
R.string.vb_notifications,
Icons.Rounded.Notifications,
UiAndroidKeycodes.NOTIFICATION
UiAndroidKeycodes.NOTIFICATION,
),
VOLUME_UP(
"volume_up",
R.string.vb_volume_up,
Icons.AutoMirrored.Rounded.VolumeUp,
UiAndroidKeycodes.VOLUME_UP
UiAndroidKeycodes.VOLUME_UP,
),
VOLUME_DOWN(
"volume_down",
R.string.vb_volume_down,
Icons.AutoMirrored.Rounded.VolumeDown,
UiAndroidKeycodes.VOLUME_DOWN
UiAndroidKeycodes.VOLUME_DOWN,
),
VOLUME_MUTE(
"volume_mute",
R.string.vb_volume_mute,
Icons.AutoMirrored.Rounded.VolumeOff,
UiAndroidKeycodes.VOLUME_MUTE
UiAndroidKeycodes.VOLUME_MUTE,
),
POWER(
"power",
R.string.vb_lock_screen,
Icons.Rounded.PowerSettingsNew,
UiAndroidKeycodes.POWER
UiAndroidKeycodes.POWER,
),
SCREENSHOT(
"screenshot",
R.string.vb_screenshot,
Icons.Rounded.Screenshot,
UiAndroidKeycodes.SYSRQ
UiAndroidKeycodes.SYSRQ,
),
PASSWORD_INPUT(
"password_input",
R.string.vb_fill_password,
Icons.Rounded.Password,
null
null,
),
ALL_APPS(
"all_apps",
R.string.vb_all_apps,
Icons.Rounded.Apps,
null
null,
),
RECENT_TASKS(
"recent_tasks",
R.string.vb_recent_tasks,
Icons.Rounded.DashboardCustomize,
null
null,
),
TOGGLE_IME(
"toggle_ime",
R.string.vb_toggle_ime,
Icons.Rounded.Keyboard,
null
null,
),
PASTE_LOCAL_CLIPBOARD(
"paste_local_clipboard",
R.string.vb_paste_clipboard,
Icons.Rounded.ContentPaste,
null
null,
);
}
@@ -418,7 +379,7 @@ class VirtualButtonBar(
Icon(
imageVector = action.icon,
contentDescription = stringResource(action.titleResId),
tint = Color.White
tint = Color.White,
)
}
@@ -475,7 +436,7 @@ class VirtualButtonBar(
Icon(
imageVector = action.icon,
contentDescription = stringResource(action.titleResId),
tint = Color.White
tint = Color.White,
)
}
@@ -546,7 +507,7 @@ class VirtualButtonBar(
latest.copy(
fullscreenFloatingButtonXFraction = offsetXFraction,
fullscreenFloatingButtonYFraction = offsetYFraction,
)
),
)
}
}
@@ -637,7 +598,7 @@ class VirtualButtonBar(
)
} else {
Modifier
}
},
),
)
}