chore: format codes
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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],
|
||||
// 不需要默认值
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>>(
|
||||
|
||||
@@ -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>>(
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>>(
|
||||
|
||||
@@ -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>>(
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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() }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user