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

@@ -21,7 +21,7 @@ import kotlinx.coroutines.runBlocking
import java.util.Locale
// 生物认证需要 FragmentActivity
class MainActivity : FragmentActivity() {
class MainActivity: FragmentActivity() {
override fun attachBaseContext(newBase: Context) {
val languageTag = getAppLanguageTag(newBase)

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

@@ -19,7 +19,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.lang.ref.WeakReference
class StreamActivity : FragmentActivity() {
class StreamActivity: FragmentActivity() {
private val basicPip = BasicPictureInPicture(this)
private val pipActionReceiver = PictureInPictureActionReceiver()

View File

@@ -7,7 +7,7 @@ package io.github.miuzarte.scrcpyforandroid.constants
* @param T The type of preset values (typically Int)
* @param values The list of preset values
*/
class Preset<T : Comparable<T>>(val values: List<T>) {
class Preset<T: Comparable<T>>(val values: List<T>) {
val lastIndex: Int get() = values.lastIndex
val size: Int get() = values.size
val indices: IntRange get() = values.indices

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

@@ -6,7 +6,7 @@ import io.github.miuzarte.scrcpyforandroid.storage.ScrcpyOptions
import kotlinx.parcelize.Parcelize
// Composable 用, 不可变 List
class DeviceShortcuts(val devices: List<DeviceShortcut>) : List<DeviceShortcut> by devices {
class DeviceShortcuts(val devices: List<DeviceShortcut>): List<DeviceShortcut> by devices {
fun marshalToString(
separator: String = DEFAULT_SEPARATOR,
): String = joinToString(separator) { it.marshalToString() }
@@ -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 {
@@ -259,7 +259,7 @@ data class DeviceShortcut(
data class ConnectionTarget(
val host: String,
val port: Int = Defaults.ADB_PORT,
) : Parcelable {
): Parcelable {
override fun toString(): String =
if (':' in host) "[$host]:$port"
else "$host:$port"

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
@@ -56,7 +56,7 @@ internal object AdbMdnsDiscoverer {
val discoveryFinished = AtomicBoolean(false)
val latch = CountDownLatch(1)
val discoveryListener = object : NsdManager.DiscoveryListener {
val discoveryListener = object: NsdManager.DiscoveryListener {
override fun onDiscoveryStarted(serviceType: String) {
Log.v(TAG, "discovery started: $serviceType")
}
@@ -79,7 +79,7 @@ internal object AdbMdnsDiscoverer {
override fun onServiceFound(serviceInfo: NsdServiceInfo) {
if (discoveryFinished.get()) return
Log.v(TAG, "service found: ${serviceInfo.serviceName}")
val resolveListener = object : NsdManager.ResolveListener {
val resolveListener = object: NsdManager.ResolveListener {
override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
Log.v(TAG, "resolve failed: ${serviceInfo.serviceName}, error=$errorCode")
}

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
@@ -73,7 +69,7 @@ internal class AdbPairingKey(
}
private val keyManager: X509ExtendedKeyManager
get() = object : X509ExtendedKeyManager() {
get() = object: X509ExtendedKeyManager() {
private val keyAlias = "adbkey"
override fun chooseClientAlias(
@@ -110,7 +106,7 @@ internal class AdbPairingKey(
@get:SuppressLint("CustomX509TrustManager")
@get:Suppress("TrustAllX509TrustManager")
private val trustManager: X509ExtendedTrustManager
get() = object : X509ExtendedTrustManager() {
get() = object: X509ExtendedTrustManager() {
// ADB pairing uses SPAKE2 + exported keying material to authenticate the peer.
// The peer cert is ephemeral/self-signed, so PKIX validation is intentionally bypassed here.
private fun acceptForPairing(
@@ -213,4 +209,4 @@ private fun RSAPublicKey.adbEncoded(name: String): ByteArray {
/**
* Thrown when the supplied pairing code is invalid during the pairing flow.
*/
internal class AdbInvalidPairingCodeException : Exception()
internal class AdbInvalidPairingCodeException: Exception()

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)),
)
}
@@ -538,7 +528,7 @@ internal class DirectAdbConnection(
private val privateKey: PrivateKey,
private val publicKeyX509: ByteArray,
private val keyName: String = AppSettings.ADB_KEY_NAME.defaultValue,
) : AutoCloseable {
): AutoCloseable {
private val sha1DigestInfoPrefix = byteArrayOf(
0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E,
@@ -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)
@@ -957,7 +947,7 @@ internal class DirectAdbConnection(
class AdbSocketStream(
val localId: Int,
private val sender: (cmd: Int, arg0: Int, arg1: Int, `data`: ByteArray) -> Unit,
) : Closeable {
): Closeable {
companion object {
private const val A_WRTE = 0x45545257
@@ -1016,7 +1006,7 @@ class AdbSocketStream(
queue.offer(EndOfStreamMarker)
}
private inner class InStream : InputStream() {
private inner class InStream: InputStream() {
private var chunk: ByteArray? = null
private var off = 0
@@ -1047,7 +1037,7 @@ class AdbSocketStream(
override fun available(): Int = chunk?.let { it.size - off } ?: 0
}
private inner class OutStream : OutputStream() {
private inner class OutStream: OutputStream() {
override fun write(b: Int) = write(byteArrayOf(b.toByte()))
override fun write(b: ByteArray, off: Int, len: Int) {
if (closed) throw IOException("ADB stream closed")

View File

@@ -134,7 +134,7 @@ internal class DirectAdbPairingClient(
private val port: Int,
private val pairingCode: String,
private val key: AdbPairingKey,
) : Closeable {
): Closeable {
private enum class State {
READY,

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({
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}"
)
}
drawFrame()
}, handler)
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}",
)
}
drawFrame()
},
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
@@ -58,7 +37,7 @@ private const val ADB_TCP_PROBE_TIMEOUT_MS = 500
internal class DeviceTabViewModel(
internal val scrcpy: Scrcpy,
connectionServices: DeviceConnectionServices,
) : ViewModel() {
): ViewModel() {
val scrcpyListings: Scrcpy.Listings get() = scrcpy.listings
@@ -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,
)
}
@@ -1001,9 +981,9 @@ internal class DeviceTabViewModel(
class Factory(
private val scrcpy: Scrcpy,
private val connectionServices: DeviceConnectionServices,
) : ViewModelProvider.Factory {
): ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
override fun <T: ViewModel> create(modelClass: Class<T>): T {
return DeviceTabViewModel(scrcpy, connectionServices) as T
}
}

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
@@ -36,11 +23,11 @@ internal enum class FileManagerSortField { NAME, SIZE, TIME, EXTENSION }
internal data class FileManagerScrollPosition(val index: Int, val offset: Int)
internal sealed interface PendingTreeDownload {
data class File(val remotePath: String, val fileName: String) : PendingTreeDownload
data class Directory(val snapshot: DirectoryDownloadSnapshot) : PendingTreeDownload
data class File(val remotePath: String, val fileName: String): PendingTreeDownload
data class Directory(val snapshot: DirectoryDownloadSnapshot): PendingTreeDownload
}
internal class FileManagerViewModel : ViewModel() {
internal class FileManagerViewModel: ViewModel() {
private val asBundleSync = BundleSyncDelegate(
sharedFlow = appSettings.bundleState,
@@ -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) {
@@ -148,7 +118,7 @@ fun FullscreenControlScreen(
if (displayManager == null || activity == null) {
onDispose {}
} else {
val listener = object : DisplayManager.DisplayListener {
val listener = object: DisplayManager.DisplayListener {
override fun onDisplayAdded(displayId: Int) = Unit
override fun onDisplayRemoved(displayId: Int) = Unit
@@ -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,
@@ -298,7 +273,7 @@ private fun TerminalPage(
}
val terminalViewClient = remember {
object : TerminalViewClient {
object: TerminalViewClient {
override fun onScale(scale: Float): Float {
when {
scale >= FONT_SCALE_STEP_THRESHOLD -> {
@@ -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
@@ -32,7 +28,7 @@ import kotlin.math.roundToInt
private const val DEFAULT_TERMINAL_FONT_SIZE_SP = 14f
private const val LOG_TAG = "TerminalScreen"
internal class TerminalViewModel : ViewModel() {
internal class TerminalViewModel: ViewModel() {
private val asBundleSync = BundleSyncDelegate(
sharedFlow = appSettings.bundleState,

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

@@ -61,7 +61,7 @@ object BiometricGate {
prompt = BiometricPrompt(
activity,
ContextCompat.getMainExecutor(activity),
object : BiometricPrompt.AuthenticationCallback() {
object: BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
finish(true)
}
@@ -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

@@ -22,7 +22,7 @@ object PasswordRepository {
private const val PASSWORD_SUFFIX = ".password"
private const val CREATED_WITH_AUTH_SUFFIX = ".created_with_auth"
class PasswordStorageCorruptedException(cause: Throwable) : Exception(cause)
class PasswordStorageCorruptedException(cause: Throwable): Exception(cause)
private val _entriesState = MutableStateFlow<List<PasswordEntry>>(emptyList())
val entriesState: StateFlow<List<PasswordEntry>> = _entriesState.asStateFlow()
@@ -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
@@ -51,13 +43,13 @@ class ReorderableList(
val icon: ImageVector,
val contentDescription: String,
val onClick: () -> Unit,
) : EndAction
): EndAction
data class Checkbox(
val checked: Boolean,
val enabled: Boolean = true,
val onClick: () -> Unit,
) : EndAction
): EndAction
}
data class Item(
@@ -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 {
append(info.codec?.string ?: "null")
if (info.width > 0 && info.height > 0) append(" ${info.width}x${info.height}")
} else "off"}, " +
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"
}, " +
"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 ->
size.substringBefore('x').toIntOrNull() ?: 0
})
.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

@@ -32,10 +32,10 @@ object AppUpdateChecker {
)
sealed interface State {
data object Idle : State
data object Checking : State
data object Error : State
data class Ready(val release: ReleaseInfo) : State
data object Idle: State
data object Checking: State
data object Error: State
data class Ready(val release: ReleaseInfo): State
}
private val checkMutex = Mutex()

View File

@@ -7,7 +7,7 @@ internal class DeviceAdbAutoReconnectManager(
private val controller: ConnectionController,
private val stateStore: ConnectionStateStore,
private val backgroundRunner: DeviceAdbBackgroundRunner = DeviceAdbBackgroundRunner(),
) : Closeable {
): Closeable {
suspend fun runKeepAliveLoop(
isForeground: () -> Boolean,
intervalMs: Long,
@@ -31,7 +31,7 @@ internal class DeviceAdbAutoReconnectManager(
onReconnectFailure = onReconnectFailure,
shouldAutoReconnect = {
stateStore.state.value.disconnectCause != DisconnectCause.User &&
stateStore.state.value.disconnectCause != DisconnectCause.KillAdbOnClose
stateStore.state.value.disconnectCause != DisconnectCause.KillAdbOnClose
},
)
}

View File

@@ -1,15 +1,11 @@
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
internal class DeviceAdbBackgroundRunner : Closeable {
internal class DeviceAdbBackgroundRunner: Closeable {
private val executor = Executors.newSingleThreadExecutor { runnable ->
Thread(runnable, "device-adb-monitor").apply { isDaemon = true }
}

View File

@@ -22,7 +22,7 @@ internal data class DeviceAdbSessionState(
val connectedScrcpyProfileId: String = ScrcpyOptions.GLOBAL_PROFILE_ID,
val audioForwardingSupported: Boolean = true,
val cameraMirroringSupported: Boolean = true,
) : Parcelable
): Parcelable
internal class DeviceAdbConnectionCoordinator(
private val adbService: NativeAdbService = NativeAdbService,

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

@@ -10,11 +10,11 @@ import java.util.Date
import java.util.Locale
sealed interface EventLogMessage {
data class Raw(val text: String) : EventLogMessage
data class Raw(val text: String): EventLogMessage
data class Resource(
@param:StringRes val resId: Int,
val args: List<Any> = emptyList(),
) : EventLogMessage
): EventLogMessage
}
data class EventLogEntry(

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
}
@@ -653,15 +654,15 @@ object FileManagerService {
class DirectorySnapshotSession private constructor(
private val shellSession: InteractiveShellSession,
) : Closeable {
): Closeable {
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)
@@ -723,7 +724,7 @@ class DirectorySnapshotSession private constructor(
private class InteractiveShellSession private constructor(
private val stream: AdbSocketStream,
) : Closeable {
): Closeable {
private val mutex = Mutex()
suspend fun execute(command: String): String = withContext(Dispatchers.IO) {

View File

@@ -9,7 +9,7 @@ import io.github.miuzarte.scrcpyforandroid.BuildConfig
import kotlinx.coroutines.runBlocking
// not working in MIUI
class PictureInPictureActionReceiver : BroadcastReceiver() {
class PictureInPictureActionReceiver: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
if (intent?.action != ACTION_STOP_SCRCPY) return
val pendingResult = goAsync()

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

@@ -7,7 +7,7 @@ import androidx.datastore.preferences.core.stringPreferencesKey
import kotlinx.coroutines.flow.StateFlow
import kotlinx.parcelize.Parcelize
class AdbClientData(context: Context) : Settings(context, "AdbClient") {
class AdbClientData(context: Context): Settings(context, "AdbClient") {
companion object {
val RSA_PRIVATE_KEY = Pair(
stringPreferencesKey("rsa_private_key"),
@@ -50,7 +50,7 @@ class AdbClientData(context: Context) : Settings(context, "AdbClient") {
val importedPrivateKeyFileName: String,
val importedPublicKeyX509: String,
val importedPublicKeyFileName: String,
) : Parcelable {
): Parcelable {
}
private val bundleFields = arrayOf<BundleField<Bundle>>(

View File

@@ -3,19 +3,14 @@ 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
import kotlinx.parcelize.Parcelize
import top.yukonga.miuix.kmp.theme.ColorSchemeMode
class AppSettings(context: Context) : Settings(context, "AppSettings") {
class AppSettings(context: Context): Settings(context, "AppSettings") {
object ThemeModes {
data class Option(
@field:StringRes val labelResId: Int,
@@ -92,6 +87,7 @@ class AppSettings(context: Context) : Settings(context, "AppSettings") {
stringPreferencesKey("language_tag"),
"",
)
// Theme
val THEME_BASE_INDEX = Pair(
intPreferencesKey("theme_base_index"),
@@ -360,7 +356,7 @@ class AppSettings(context: Context) : Settings(context, "AppSettings") {
val lastUpdateCheckAt: Long,
val clearLogsOnExit: Boolean,
val hideDeviceLogs: Boolean,
) : Parcelable {
): Parcelable {
}
private val bundleFields = arrayOf<BundleField<Bundle>>(

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

@@ -7,7 +7,7 @@ import androidx.datastore.preferences.core.stringPreferencesKey
import kotlinx.coroutines.flow.StateFlow
import kotlinx.parcelize.Parcelize
class QuickDevices(context: Context) : Settings(context, "QuickDevices") {
class QuickDevices(context: Context): Settings(context, "QuickDevices") {
companion object {
val QUICK_DEVICES_LIST = Pair(
stringPreferencesKey("quick_devices_list"),
@@ -26,7 +26,7 @@ class QuickDevices(context: Context) : Settings(context, "QuickDevices") {
data class Bundle(
val quickDevicesList: String,
val quickConnectInput: String,
) : Parcelable {
): Parcelable {
}
private val bundleFields = arrayOf<BundleField<Bundle>>(

View File

@@ -2,31 +2,18 @@ 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
import org.json.JSONObject
class ScrcpyOptions(context: Context) : Settings(context, "ScrcpyOptions") {
class ScrcpyOptions(context: Context): Settings(context, "ScrcpyOptions") {
companion object {
const val GLOBAL_PROFILE_ID = "global"
val GLOBAL_PROFILE_NAME_RES_ID = R.string.text_global
@@ -435,7 +422,7 @@ class ScrcpyOptions(context: Context) : Settings(context, "ScrcpyOptions") {
val cameraTorch: Boolean,
val keepActive: Boolean,
val flexDisplay: Boolean,
) : Parcelable {
): Parcelable {
}
private val bundleFields = arrayOf<BundleField<Bundle>>(

View File

@@ -8,7 +8,7 @@ import org.json.JSONArray
import org.json.JSONObject
import java.util.UUID
class ScrcpyProfiles(context: Context) : Settings(context, "ScrcpyProfiles") {
class ScrcpyProfiles(context: Context): Settings(context, "ScrcpyProfiles") {
companion object {
private val PROFILES_JSON = Pair(
stringPreferencesKey("profiles_json"),
@@ -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())
@@ -56,7 +43,7 @@ abstract class Settings(
}
protected fun <B, T> bundleField(pair: Pair<T>, selector: (B) -> T): BundleField<B> =
object : BundleField<B> {
object: BundleField<B> {
override suspend fun persist(settings: Settings, current: B, new: B) {
val currentValue = selector(current)
val newValue = selector(new)
@@ -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 =
@@ -156,7 +143,7 @@ abstract class Settings(
val state = asState(pair)
return rememberSaveable(state.value) {
object : MutableState<T> {
object: MutableState<T> {
override var value: T
get() = state.value
set(newValue) {

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

@@ -13,7 +13,7 @@ class ScrcpyInputSurfaceView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : SurfaceView(context, attrs, defStyleAttr) {
): SurfaceView(context, attrs, defStyleAttr) {
interface InputCallbacks {
fun handleKeyEvent(event: KeyEvent): Boolean
fun handleCommitText(text: CharSequence): Boolean
@@ -46,7 +46,7 @@ class ScrcpyInputSurfaceView @JvmOverloads constructor(
outAttrs.inputType = InputType.TYPE_CLASS_TEXT
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
return object : BaseInputConnection(this, false) {
return object: BaseInputConnection(this, false) {
override fun commitText(text: CharSequence, newCursorPosition: Int): Boolean {
if (inputCallbacks?.handleCommitText(text) == true) return true
return super.commitText(text, newCursorPosition)

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

@@ -13,7 +13,7 @@ class TerminalInputView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : View(context, attrs, defStyleAttr) {
): View(context, attrs, defStyleAttr) {
interface InputCallbacks {
fun handleKeyEvent(event: KeyEvent): Boolean
fun handleCommitText(text: CharSequence): Boolean
@@ -51,7 +51,7 @@ class TerminalInputView @JvmOverloads constructor(
outAttrs.inputType = InputType.TYPE_CLASS_TEXT
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
return object : BaseInputConnection(this, false) {
return object: BaseInputConnection(this, false) {
override fun commitText(text: CharSequence, newCursorPosition: Int): Boolean {
if (inputCallbacks?.handleCommitText(text) == true) return true
return super.commitText(text, newCursorPosition)

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
}
},
),
)
}
@@ -785,7 +746,7 @@ class VirtualButtonBar(
private class BottomSafeContextMenuPositionProvider(
private val bottomPadding: Dp,
) : PopupPositionProvider {
): PopupPositionProvider {
private val delegate = ListPopupDefaults.ContextMenuPositionProvider
override fun calculatePosition(