From 156a6fe067babefa748548a01ccfc4762883ee2c Mon Sep 17 00:00:00 2001
From: beyond
Date: Fri, 28 Aug 2026 06:13:07 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BC=98=E5=8C=96=E6=92=AD?=
=?UTF-8?q?=E6=94=BE=E5=99=A8=E7=95=8C=E9=9D=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../screens/VideoPlayerScreen.kt | 285 ++++++++++++------
.../myapplication/ui/design/LumaComponents.kt | 3 +-
.../myapplication/ui/design/LumaIcons.kt | 2 +
app/src/main/res/drawable/aurora_signal.xml | 35 +++
.../res/drawable/aurora_signal_selected.xml | 35 +++
app/src/main/res/drawable/aurora_wifi.xml | 31 ++
.../res/drawable/aurora_wifi_selected.xml | 31 ++
app/src/main/res/values-en/strings.xml | 1 +
app/src/main/res/values/strings.xml | 1 +
9 files changed, 327 insertions(+), 97 deletions(-)
create mode 100644 app/src/main/res/drawable/aurora_signal.xml
create mode 100644 app/src/main/res/drawable/aurora_signal_selected.xml
create mode 100644 app/src/main/res/drawable/aurora_wifi.xml
create mode 100644 app/src/main/res/drawable/aurora_wifi_selected.xml
diff --git a/app/src/main/kotlin/com/example/myapplication/screens/VideoPlayerScreen.kt b/app/src/main/kotlin/com/example/myapplication/screens/VideoPlayerScreen.kt
index 0f8077b..e6555f2 100644
--- a/app/src/main/kotlin/com/example/myapplication/screens/VideoPlayerScreen.kt
+++ b/app/src/main/kotlin/com/example/myapplication/screens/VideoPlayerScreen.kt
@@ -12,12 +12,14 @@ import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
+import androidx.compose.foundation.Canvas
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
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.Row
import androidx.compose.foundation.layout.Spacer
@@ -40,6 +42,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -49,6 +52,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalContext
@@ -59,6 +67,9 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
@@ -70,18 +81,11 @@ import androidx.media3.common.TrackSelectionOverride
import androidx.media3.common.util.UnstableApi
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
-import android.os.BatteryManager
import android.os.SystemClock
-import androidx.compose.foundation.Canvas
+import android.os.BatteryManager
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.awaitLongPressOrCancellation
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.ui.geometry.CornerRadius
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.Path
-import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.input.pointer.pointerInput
import androidx.media3.exoplayer.DefaultLoadControl
import androidx.media3.exoplayer.analytics.AnalyticsListener
@@ -118,6 +122,24 @@ private fun formatNetworkSpeed(bps: Long): String {
else -> "$bps B/s"
}
}
+private fun formatVideoCodec(mimeType: String?): String = when (mimeType) {
+ "video/avc" -> "H.264"
+ "video/hevc" -> "H.265"
+ "video/av01" -> "AV1"
+ "video/x-vnd.on2.vp9" -> "VP9"
+ "video/x-vnd.on2.vp8" -> "VP8"
+ "video/mp4v-es" -> "MPEG-4"
+ "video/mpeg2" -> "MPEG-2"
+ "video/3gpp" -> "H.263"
+ "video/x-ms-wmv" -> "WMV"
+ else -> mimeType?.substringAfterLast('/') ?: ""
+}
+
+private fun formatBitrate(bps: Int): String = when {
+ bps >= 1_000_000 -> String.format("%.1f Mbps", bps / 1_000_000f)
+ bps >= 1_000 -> String.format("%.0f Kbps", bps / 1_000f)
+ else -> "$bps bps"
+}
private class SpeedMonitor {
private var lastTotalBytes = 0L
@@ -216,14 +238,18 @@ fun VideoPlayerScreen(
var hasNetworkSample by remember { mutableStateOf(false) }
var batteryPercent by remember { mutableIntStateOf(-1) }
var isCharging by remember { mutableStateOf(false) }
- var isWifi by remember { mutableStateOf(false) }
var topTimeText by remember { mutableStateOf("") }
+ var isWifi by remember { mutableStateOf(false) }
+ var videoCodecText by remember { mutableStateOf("") }
+ var videoBitrateText by remember { mutableStateOf("") }
+ var videoFrameRateText by remember { mutableStateOf("") }
var speedUpVisible by remember { mutableStateOf(false) }
var speedUpValue by remember { mutableFloatStateOf(2f) }
var controlsVisible by remember { mutableStateOf(true) }
- LaunchedEffect(controlsVisible, isPlaying) {
- if (controlsVisible && isPlaying) {
+ var controlsLocked by remember { mutableStateOf(false) }
+ LaunchedEffect(controlsVisible, isPlaying, controlsLocked) {
+ if (controlsVisible && isPlaying && !controlsLocked) {
delay(5000)
controlsVisible = false
}
@@ -241,18 +267,18 @@ fun VideoPlayerScreen(
LaunchedEffect(Unit) {
while (true) {
- topTimeText = java.text.SimpleDateFormat("HH:mm", java.util.Locale.getDefault()).format(java.util.Date())
- delay(1000)
+ val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
+ isWifi = cm?.activeNetwork?.let { net ->
+ cm.getNetworkCapabilities(net)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
+ } ?: false
+ delay(2000)
}
}
LaunchedEffect(Unit) {
while (true) {
- val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
- isWifi = cm?.allNetworks?.any { cap ->
- cm.getNetworkCapabilities(cap)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
- } ?: false
- delay(2000)
+ topTimeText = java.text.SimpleDateFormat("HH:mm", java.util.Locale.getDefault()).format(java.util.Date())
+ delay(1000)
}
}
@@ -271,6 +297,18 @@ fun VideoPlayerScreen(
}
}
+ LaunchedEffect(player) {
+ while (true) {
+ val vf = player.videoFormat
+ if (vf != null) {
+ videoCodecText = formatVideoCodec(vf.sampleMimeType)
+ videoBitrateText = formatBitrate(vf.bitrate)
+ videoFrameRateText = if (vf.frameRate > 0f) String.format("%.0f fps", vf.frameRate) else ""
+ }
+ delay(1000)
+ }
+ }
+
LaunchedEffect(Unit) {
while (true) {
val speed = speedMonitor.currentBytesPerSec()
@@ -292,8 +330,19 @@ fun VideoPlayerScreen(
DisposableEffect(Unit) {
val activity = context.findActivity()
val prev = activity?.requestedOrientation ?: ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+ val window = activity?.window
+ val insetsController = window?.let { WindowCompat.getInsetsController(it, it.decorView) }
+ val prevBehavior = insetsController?.systemBarsBehavior
+ insetsController?.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ insetsController?.hide(WindowInsetsCompat.Type.systemBars())
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
- onDispose { activity?.requestedOrientation = prev }
+ onDispose {
+ insetsController?.let { controller ->
+ controller.show(WindowInsetsCompat.Type.systemBars())
+ controller.systemBarsBehavior = prevBehavior ?: WindowInsetsControllerCompat.BEHAVIOR_DEFAULT
+ }
+ activity?.requestedOrientation = prev
+ }
}
DisposableEffect(playbackUrl) {
playerError = null
@@ -367,7 +416,7 @@ fun VideoPlayerScreen(
.background(Color.Black)
.pointerInput(player) {
awaitEachGesture {
- val down = awaitFirstDown(requireUnconsumed = false)
+ val down = awaitFirstDown(requireUnconsumed = true)
val longPress = awaitLongPressOrCancellation(down.id)
if (longPress != null) {
// 长按:2倍速播放,并显示倍速胶囊
@@ -435,7 +484,8 @@ fun VideoPlayerScreen(
enter = fadeIn(),
exit = fadeOut()
) {
- Box(Modifier.fillMaxSize()) {
+ BoxWithConstraints(Modifier.fillMaxSize()) {
+ val capsuleInset = maxWidth * 0.075f + 20.dp
Box(
Modifier
.fillMaxSize()
@@ -459,7 +509,7 @@ fun VideoPlayerScreen(
icon = LumaIcon.Replay,
contentDescription = stringResource(Res.string.player_seek_backward),
active = false,
- size = 48.dp,
+ size = 32.dp,
) {
val pos = player.currentPosition - 10_000L
player.seekTo(maxOf(0L, pos))
@@ -469,7 +519,7 @@ fun VideoPlayerScreen(
icon = LumaIcon.Previous,
contentDescription = stringResource(Res.string.player_previous_episode),
active = false,
- size = 56.dp,
+ size = 40.dp,
) {
val idx = episodes.indexOfFirst { it.id == currentEpisodeId }
if (idx > 0) onSelectEpisode(episodes[idx - 1])
@@ -478,8 +528,8 @@ fun VideoPlayerScreen(
icon = if (isPlaying) LumaIcon.Pause else LumaIcon.Play,
contentDescription = stringResource(if (isPlaying) Res.string.player_pause else Res.string.player_play),
active = true,
- size = 72.dp,
- iconSize = 40.dp,
+ size = 48.dp,
+ iconSize = 28.dp,
) {
if (player.isPlaying) {
player.pause()
@@ -491,7 +541,7 @@ fun VideoPlayerScreen(
icon = LumaIcon.Next,
contentDescription = stringResource(Res.string.player_next_episode),
active = false,
- size = 56.dp,
+ size = 40.dp,
) {
val idx = episodes.indexOfFirst { it.id == currentEpisodeId }
if (idx >= 0 && idx < episodes.lastIndex) onSelectEpisode(episodes[idx + 1])
@@ -500,24 +550,25 @@ fun VideoPlayerScreen(
icon = LumaIcon.Speed,
contentDescription = stringResource(Res.string.player_seek_forward),
active = false,
- size = 48.dp,
+ size = 32.dp,
) {
val pos = player.currentPosition + 10_000L
player.seekTo(minOf(pos, player.duration))
}
}
- Column(Modifier.fillMaxWidth().statusBarsPadding().padding(16.dp)) {
+ Column(Modifier.fillMaxWidth().statusBarsPadding().padding(top = 16.dp)) {
+ // 第一排:当前时间(左)|网速 + 电量(右)
Row(
- Modifier.fillMaxWidth(),
+ Modifier.fillMaxWidth().padding(horizontal = capsuleInset),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(topTimeText, color = Color.White, fontSize = 13.sp)
- Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
+ Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp)) {
if (playbackUrl != null) {
Row(verticalAlignment = Alignment.CenterVertically) {
- LumaIconView(LumaIcon.Network, contentDescription = null, tint = Color.White, modifier = Modifier.size(16.dp))
+ LumaIconView(if (isWifi) LumaIcon.Wifi else LumaIcon.Signal, contentDescription = null, tint = Color.White, modifier = Modifier.size(16.dp))
Spacer(Modifier.width(4.dp))
if (hasNetworkSample) Text(formatNetworkSpeed(networkSpeed), color = Color.White, fontSize = 13.sp)
}
@@ -531,28 +582,32 @@ fun VideoPlayerScreen(
}
}
}
- Spacer(Modifier.height(10.dp))
+ Spacer(Modifier.height(8.dp))
+ // 第二排:关闭按钮(左)|Emby 艺术字(紧跟关闭按钮右侧)|字幕/剧集按钮(右)
Row(
- Modifier.fillMaxWidth(),
+ Modifier.fillMaxWidth().padding(horizontal = capsuleInset),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
- Column(Modifier.weight(1f).padding(end = 12.dp), verticalArrangement = Arrangement.Center) {
- if (titleLogoUrl.isNotBlank()) {
- AsyncImage(
- model = titleLogoUrl,
- contentDescription = null,
- contentScale = ContentScale.Fit,
- modifier = Modifier.height(28.dp).widthIn(max = 220.dp),
- )
- } else {
- Text(title, color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.Bold, maxLines = 1, overflow = TextOverflow.Ellipsis)
- }
- if (serverName.isNotBlank()) {
- Text(serverName, color = Color.White.copy(alpha = 0.8f), fontSize = 12.sp, maxLines = 1, overflow = TextOverflow.Ellipsis)
- }
-}
- Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
+ Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
+ LiquidSurfaceButton(
+ icon = LumaIcon.Close,
+ contentDescription = stringResource(Res.string.player_close),
+ active = false,
+ size = 40.dp,
+ ) { onClose() }
+ if (titleLogoUrl.isNotBlank()) {
+ AsyncImage(
+ model = titleLogoUrl,
+ contentDescription = null,
+ contentScale = ContentScale.Fit,
+ modifier = Modifier.height(28.dp).widthIn(max = 220.dp),
+ )
+ } else {
+ Text(title, color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.Bold, maxLines = 1, overflow = TextOverflow.Ellipsis)
+ }
+ }
+ Row(horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically) {
LiquidSurfaceButton(
icon = LumaIcon.Subtitle,
contentDescription = stringResource(Res.string.player_subtitle),
@@ -571,6 +626,26 @@ fun VideoPlayerScreen(
}
}
}
+ if (serverName.isNotBlank()) {
+ Spacer(Modifier.height(8.dp))
+ // 第三排:服务器图标 + 服务器名字
+ Row(Modifier.fillMaxWidth().padding(start = capsuleInset + 52.dp, end = capsuleInset), verticalAlignment = Alignment.CenterVertically) {
+ Box(Modifier.size(30.dp).clip(CircleShape), contentAlignment = Alignment.Center) {
+ if (serverIconUrl.isNotBlank()) {
+ AsyncImage(
+ model = serverIconUrl,
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier.fillMaxSize(),
+ )
+ } else {
+ LumaIconView(LumaIcon.Server, contentDescription = null, tint = Color.White, modifier = Modifier.size(24.dp))
+ }
+ }
+ Spacer(Modifier.width(8.dp))
+ Text(serverName, color = Color.White.copy(alpha = 0.8f), fontSize = 15.sp, maxLines = 1, overflow = TextOverflow.Ellipsis)
+ }
+ }
}
if (playbackUrl != null) {
@@ -578,56 +653,73 @@ fun VideoPlayerScreen(
Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth(0.85f)
- .padding(bottom = 32.dp)
- .lumaGlass(
- backdrop = null,
- radius = 24.dp,
- level = LumaGlassLevel.Strong,
- tint = Color.Black.copy(alpha = 0.40f),
- borderColor = Color.White.copy(alpha = 0.16f),
- shadowElevation = 4.dp,
- )
- .padding(horizontal = 20.dp, vertical = 12.dp),
+ .padding(bottom = 32.dp),
) {
- var totalDurationMs by remember { mutableLongStateOf(0L) }
- var currentPositionMs by remember { mutableLongStateOf(0L) }
- var isSeeking by remember { mutableStateOf(false) }
- var seekPosition by remember { mutableFloatStateOf(0f) }
- var bufferProgress by remember { mutableFloatStateOf(0f) }
-
- LaunchedEffect(player) {
- while (true) {
- if (player.duration > 0) {
- totalDurationMs = player.duration
- if (!isSeeking) {
- currentPositionMs = player.currentPosition
- progress = player.currentPosition.toFloat() / player.duration.toFloat()
- val b = (player.bufferedPosition.toFloat() / player.duration).coerceIn(0f, 1f)
- bufferProgress = maxOf(b, progress)
- }
- }
- delay(250)
- }
+ // 技术信息行(进度胶囊上方、胶囊外部):内核|格式|码率|帧率
+ Row(
+ Modifier.fillMaxWidth().padding(horizontal = 20.dp, vertical = 4.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
+ ) {
+ Text("ExoPlayer", color = Color.White.copy(alpha = 0.75f), fontSize = 12.sp)
+ if (videoCodecText.isNotBlank()) Text(videoCodecText, color = Color.White.copy(alpha = 0.75f), fontSize = 12.sp)
+ if (videoBitrateText.isNotBlank()) Text(videoBitrateText, color = Color.White.copy(alpha = 0.75f), fontSize = 12.sp)
+ if (videoFrameRateText.isNotBlank()) Text(videoFrameRateText, color = Color.White.copy(alpha = 0.75f), fontSize = 12.sp)
}
+ Column(
+ Modifier
+ .fillMaxWidth()
+ .lumaGlass(
+ backdrop = null,
+ radius = 24.dp,
+ level = LumaGlassLevel.Strong,
+ tint = Color.Black.copy(alpha = 0.40f),
+ borderColor = Color.White.copy(alpha = 0.22f),
+ shadowElevation = 4.dp,
+ )
+ .padding(horizontal = 16.dp, vertical = 6.dp),
+ ) {
+ var totalDurationMs by remember { mutableLongStateOf(0L) }
+ var currentPositionMs by remember { mutableLongStateOf(0L) }
+ var isSeeking by remember { mutableStateOf(false) }
+ var seekPosition by remember { mutableFloatStateOf(0f) }
+ var bufferProgress by remember { mutableFloatStateOf(0f) }
- Row(verticalAlignment = Alignment.CenterVertically) {
- Text(formatMs(currentPositionMs), color = Color.White, fontSize = 12.sp)
- LumaSeekBar(
- value = if (isSeeking) seekPosition else progress,
- onValueChange = { v -> isSeeking = true; seekPosition = v },
- onValueChangeFinished = {
- player.seekTo((seekPosition * player.duration).toLong())
- isSeeking = false
- latestOnPlaybackProgress(player.currentPosition, !player.isPlaying, "TimeUpdate")
- },
- modifier = Modifier.weight(1f).padding(horizontal = 12.dp),
- activeColor = Color.White,
- trackColor = Color.White.copy(alpha = 0.12f),
- bufferColor = Color.White.copy(alpha = 0.28f),
- bufferedValue = bufferProgress,
- thumbColor = Color.White,
- )
- Text(formatMs(totalDurationMs), color = Color.White, fontSize = 12.sp)
+ LaunchedEffect(player) {
+ while (true) {
+ if (player.duration > 0) {
+ totalDurationMs = player.duration
+ if (!isSeeking) {
+ currentPositionMs = player.currentPosition
+ progress = player.currentPosition.toFloat() / player.duration.toFloat()
+ val b = (player.bufferedPosition.toFloat() / player.duration).coerceIn(0f, 1f)
+ bufferProgress = maxOf(b, progress)
+ }
+ }
+ delay(250)
+ }
+ }
+
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text(formatMs(currentPositionMs), color = Color.White, fontSize = 12.sp)
+ LumaSeekBar(
+ value = if (isSeeking) seekPosition else progress,
+ onValueChange = { v -> isSeeking = true; seekPosition = v; controlsLocked = true },
+ onValueChangeFinished = {
+ player.seekTo((seekPosition * player.duration).toLong())
+ isSeeking = false
+ controlsLocked = false
+ latestOnPlaybackProgress(player.currentPosition, !player.isPlaying, "TimeUpdate")
+ },
+ modifier = Modifier.weight(1f).padding(horizontal = 12.dp),
+ activeColor = Color.White,
+ trackColor = Color.White.copy(alpha = 0.12f),
+ bufferColor = Color.White.copy(alpha = 0.45f),
+ bufferedValue = bufferProgress,
+ thumbColor = Color.White,
+ )
+ Text(formatMs(totalDurationMs), color = Color.White, fontSize = 12.sp)
+ }
}
}
}
@@ -750,6 +842,7 @@ private fun LiquidGlassPanel(
) {
Column(
Modifier
+ .padding(top = 16.dp, bottom = 16.dp, end = 16.dp)
.fillMaxHeight()
.width(320.dp)
.lumaGlass(
diff --git a/app/src/main/kotlin/com/example/myapplication/ui/design/LumaComponents.kt b/app/src/main/kotlin/com/example/myapplication/ui/design/LumaComponents.kt
index 127dab7..1f40161 100644
--- a/app/src/main/kotlin/com/example/myapplication/ui/design/LumaComponents.kt
+++ b/app/src/main/kotlin/com/example/myapplication/ui/design/LumaComponents.kt
@@ -176,7 +176,7 @@ fun LumaSeekBar(
Canvas(
modifier
- .heightIn(min = 48.dp)
+ .heightIn(min = 30.dp)
.onSizeChanged { trackWidthPx = it.width.toFloat() }
.semantics {
progressBarRangeInfo = ProgressBarRangeInfo(currentValue, 0f..1f)
@@ -189,6 +189,7 @@ fun LumaSeekBar(
.pointerInput(trackWidthPx) {
awaitEachGesture {
val down = awaitFirstDown(requireUnconsumed = false)
+ down.consume()
dragging = true
updateFromPosition(down.position.x)
var lastX = down.position.x
diff --git a/app/src/main/kotlin/com/example/myapplication/ui/design/LumaIcons.kt b/app/src/main/kotlin/com/example/myapplication/ui/design/LumaIcons.kt
index 12aebe0..ffc0906 100644
--- a/app/src/main/kotlin/com/example/myapplication/ui/design/LumaIcons.kt
+++ b/app/src/main/kotlin/com/example/myapplication/ui/design/LumaIcons.kt
@@ -54,6 +54,8 @@ enum class LumaIcon(
Database(R.drawable.aurora_database, R.drawable.aurora_database_selected),
Cloud(R.drawable.aurora_cloud, R.drawable.aurora_cloud_selected),
Network(R.drawable.aurora_network, R.drawable.aurora_network_selected),
+ Wifi(R.drawable.aurora_wifi, R.drawable.aurora_wifi_selected),
+ Signal(R.drawable.aurora_signal, R.drawable.aurora_signal_selected),
Globe(R.drawable.aurora_globe, R.drawable.aurora_globe_selected),
Shield(R.drawable.aurora_shield, R.drawable.aurora_shield_selected),
Key(R.drawable.aurora_key, R.drawable.aurora_key_selected),
diff --git a/app/src/main/res/drawable/aurora_signal.xml b/app/src/main/res/drawable/aurora_signal.xml
new file mode 100644
index 0000000..5e34d5f
--- /dev/null
+++ b/app/src/main/res/drawable/aurora_signal.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/aurora_signal_selected.xml b/app/src/main/res/drawable/aurora_signal_selected.xml
new file mode 100644
index 0000000..e10001d
--- /dev/null
+++ b/app/src/main/res/drawable/aurora_signal_selected.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/aurora_wifi.xml b/app/src/main/res/drawable/aurora_wifi.xml
new file mode 100644
index 0000000..5d70b50
--- /dev/null
+++ b/app/src/main/res/drawable/aurora_wifi.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/aurora_wifi_selected.xml b/app/src/main/res/drawable/aurora_wifi_selected.xml
new file mode 100644
index 0000000..27ef341
--- /dev/null
+++ b/app/src/main/res/drawable/aurora_wifi_selected.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml
index 59d8f84..0303158 100644
--- a/app/src/main/res/values-en/strings.xml
+++ b/app/src/main/res/values-en/strings.xml
@@ -137,6 +137,7 @@
Playback failed
Subtitles
Episode list
+ Close
Seek back 10 seconds
Previous episode
Play
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 80e41eb..e134918 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -137,6 +137,7 @@
播放失败
字幕
剧集列表
+ 关闭
后退 10 秒
上一集
播放