Files
lumaplayer/app/src/main/kotlin/com/example/myapplication/data/EmbyServer.kt
T
beyond 3670466a65 fix: 第一轮性能优化 — R8启用、协程泄漏修复、@Immutable注解、状态提取、并行化
- Release build 启用 R8/ProGuard + 新建 proguard-rules.pro
- MediaDetailScreen: MainScope → rememberCoroutineScope
- AppNavHost: openDetail/openHome lambda 记忆化
- VideoPlayerScreen: 提取 PlayerStatusBar 独立 Composable
- LibraryScreen: nowMillis 下沉到 ServerStatusLine 内部
- 8个核心数据类添加 @Immutable 注解
- TmdbCalendar: coroutineScope+async 并行化 season 请求
- SearchScreen: derivedStateOf 正确观察 SnapshotStateList
- HomeScreen: MediaPoster/ContinueWatchingPoster ImageRequest remember
- MediaDetailScreen: cast/recommendations/resources LazyRow 添加 key
- RecordsScreen: LaunchedEffect key 用 hashCode 替代 joinToString
2026-09-06 13:30:56 +08:00

152 lines
4.9 KiB
Kotlin

package com.example.myapplication.data
import androidx.annotation.StringRes
import androidx.compose.runtime.Immutable
import com.example.myapplication.R
import java.net.URI
import java.util.UUID
@Immutable
data class EmbyLibrary(
val id: String,
val name: String,
val type: LibraryType,
val enabled: Boolean = true,
)
@Immutable
enum class LibraryType(@StringRes val labelRes: Int) {
MOVIES(R.string.library_movies),
TV_SHOWS(R.string.library_tv_shows),
MUSIC(R.string.library_music),
BOOKS(R.string.library_books),
HOME_VIDEOS(R.string.library_home_videos),
PHOTOS(R.string.library_photos),
}
@Immutable
data class EmbyServer(
val id: String,
val name: String = "",
val protocol: String = "https",
val host: String = "",
val port: String = "",
val basePath: String = "",
val username: String = "",
val password: String = "",
val apiKey: String = "",
val userId: String = "",
val note: String = "",
val libraries: List<EmbyLibrary> = emptyList(),
val isEnabled: Boolean = true,
val isOnline: Boolean = true,
val lastActive: String = "",
val iconUrl: String = "",
val authError: String = "",
val isSelected: Boolean = false,
val alternateAddresses: List<EmbyServerAddress> = emptyList(),
val activeAddressId: String = "",
val lastAccessedAt: Long = 0L,
val movieCount: Int? = null,
val seriesCount: Int? = null,
) {
val activeAlternateAddress: EmbyServerAddress?
get() = alternateAddresses.firstOrNull { it.id == activeAddressId }
val fullUrl: String
get() = activeAlternateAddress?.fullUrl ?: buildServerUrl(protocol, host, port, basePath)
val displayHost: String
get() = activeAlternateAddress?.let { address ->
if (address.port.isNotBlank()) "${address.host}:${address.port}" else address.host
} ?: if (port.isNotBlank()) "$host:$port" else host
companion object {
fun generateId(): String = "server_${UUID.randomUUID()}"
}
}
@Immutable
data class EmbyServerAddress(
val id: String = generateId(),
val name: String = "",
val protocol: String = "https",
val host: String = "",
val port: String = "",
val basePath: String = "",
) {
val fullUrl: String
get() = buildServerUrl(protocol, host, port, basePath)
companion object {
fun generateId(): String = "address_${UUID.randomUUID()}"
}
}
data class ParsedServerAddress(
val protocol: String,
val host: String,
val port: String,
val basePath: String,
)
fun parseServerAddressInput(
input: String,
currentProtocol: String,
currentPort: String,
currentBasePath: String,
): ParsedServerAddress {
val trimmed = input.trim()
if (trimmed.isEmpty()) {
return ParsedServerAddress(currentProtocol, "", currentPort, currentBasePath)
}
val schemeMatch = Regex("^(https?)://", RegexOption.IGNORE_CASE).find(trimmed)
val hasExplicitScheme = schemeMatch != null
val protocol = schemeMatch?.groupValues?.get(1)?.lowercase()
?: currentProtocol.lowercase().takeIf { it == "http" } ?: "https"
val uriText = if (hasExplicitScheme) trimmed else "$protocol://$trimmed"
val uri = runCatching { URI(uriText) }.getOrNull()
var host = uri?.host.orEmpty()
val explicitPort = uri?.port?.takeIf { it >= 0 }?.toString()
var path = uri?.rawPath.orEmpty().trim('/')
if (host.isEmpty()) {
val withoutScheme = trimmed.replace(Regex("^(https?)://", RegexOption.IGNORE_CASE), "")
val authority = withoutScheme.substringBefore('/').substringBefore('?').substringBefore('#')
val portMatch = Regex("^(.+):(\\d+)$").matchEntire(authority)
host = portMatch?.groupValues?.get(1) ?: authority
if (path.isEmpty() && withoutScheme.contains('/')) {
path = withoutScheme.substringAfter('/').substringBefore('?').substringBefore('#').trim('/')
}
}
val port = when {
explicitPort != null -> explicitPort
hasExplicitScheme && protocol == "https" -> "443"
hasExplicitScheme -> ""
currentPort.isNotBlank() -> currentPort
protocol == "https" -> "443"
else -> ""
}
return ParsedServerAddress(
protocol = protocol,
host = host.trim().trimEnd('/'),
port = port,
basePath = path.ifBlank { currentBasePath.trim().trim('/') },
)
}
private fun buildServerUrl(protocol: String, host: String, port: String, basePath: String): String {
val effectiveProtocol = if (protocol.equals("http", ignoreCase = true)) "http" else "https"
val cleanHost = host.trim()
.replace(Regex("^(https?)://", RegexOption.IGNORE_CASE), "")
.substringBefore('/')
.trimEnd('/')
val base = if (port.isNotBlank()) "$effectiveProtocol://$cleanHost:$port" else "$effectiveProtocol://$cleanHost"
return listOf(base.trimEnd('/'), basePath.trim().trim('/'))
.filter { it.isNotBlank() }
.joinToString("/")
}