Files
lumaplayer/app/src/main/kotlin/com/example/myapplication/MainApp.kt
T

289 lines
18 KiB
Kotlin

package com.example.myapplication
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
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.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.myapplication.data.EmbyRepository
import com.example.myapplication.data.EmbyServer
import com.example.myapplication.data.IconLibrary
import com.example.myapplication.data.IconLibraryRepository
import com.example.myapplication.data.ServerRepository
import com.example.myapplication.screens.HomeScreen
import com.example.myapplication.screens.IconLibraryManagerScreen
import com.example.myapplication.screens.LibraryScreen
import com.example.myapplication.screens.LibraryTopBar
import com.example.myapplication.screens.RecordsScreen
import com.example.myapplication.screens.RecordsTopBar
import com.example.myapplication.screens.SearchScreen
import com.example.myapplication.screens.SearchTopBar
import com.example.myapplication.screens.SettingsScreen
import com.example.myapplication.screens.SettingsTopBar
import com.example.myapplication.ui.AppTheme
import com.example.myapplication.ui.GlassTokens
import com.example.myapplication.ui.LiquidAppBottomBar
import com.example.myapplication.ui.LiquidGlassBackground
import com.kyant.backdrop.backdrops.rememberLayerBackdrop
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.hazeSource
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import top.yukonga.miuix.kmp.basic.Text
import top.yukonga.miuix.kmp.theme.MiuixTheme
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun MainApp() {
var selectedTab by remember { mutableIntStateOf(0) }
var selectedServerId by remember { mutableStateOf("") }
var recordsSegment by remember { mutableIntStateOf(0) }
var searchQuery by remember { mutableStateOf("") }
var showLibraryAddDialog by remember { mutableStateOf(false) }
var libraryIsGrid by remember { mutableStateOf(false) }
var showIconLibraryManager by remember { mutableStateOf(false) }
var showCacheDetail by remember { mutableStateOf(false) }
var homeDetailVisible by remember { mutableStateOf(false) }
var showAboutDetail by remember { mutableStateOf(false) }
var changeIconTarget by remember { mutableStateOf<EmbyServer?>(null) }
var showStartScreen by remember { mutableStateOf(true) }
val servers by ServerRepository.servers.collectAsState()
val iconLibraries by IconLibraryRepository.libraries.collectAsState()
val coroutineScope = rememberCoroutineScope()
val selectedServer = servers.find { it.id == selectedServerId } ?: servers.firstOrNull()
LaunchedEffect(servers.size) {
if (servers.isNotEmpty()) {
ServerRepository.checkAllServersOnline()
}
}
LaunchedEffect(Unit) {
delay(2200)
showStartScreen = false
}
if (showStartScreen) {
Box(Modifier.fillMaxSize().background(Color.White)) {
Image(
painter = painterResource(Res.drawable.app_start),
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop,
)
}
}
AppTheme.systemIsDark = androidx.compose.foundation.isSystemInDarkTheme()
if (!showStartScreen) {
MiuixTheme {
val hazeState = remember { HazeState() }
val glassBackdrop = rememberLayerBackdrop()
val bgColor = if (AppTheme.isDark) Color(0xFF0F1115) else Color(0xFFF2F4F7)
Box(Modifier.fillMaxSize().background(bgColor)) {
Box(Modifier.fillMaxSize().hazeSource(hazeState)) {
if (selectedTab == 2 && showIconLibraryManager) {
IconLibraryManagerScreen(
libraries = iconLibraries,
modifier = Modifier.fillMaxSize(),
onBack = { showIconLibraryManager = false },
onAddIconLibrary = { name, url, icons, isDefault ->
val library = IconLibrary(
id = "lib_${System.currentTimeMillis()}",
name = name,
sourceUrl = url,
iconCount = icons.size,
isDefault = isDefault,
icons = icons,
)
coroutineScope.launch { IconLibraryRepository.addLibrary(library) }
},
onUpdateIconLibrary = { library -> coroutineScope.launch { IconLibraryRepository.updateLibrary(library) } },
onDeleteIconLibrary = { id -> coroutineScope.launch { IconLibraryRepository.removeLibrary(id) } },
onRefreshIconLibrary = { id -> coroutineScope.launch { IconLibraryRepository.refreshLibrary(id) } },
)
} else {
Column(Modifier.fillMaxSize()) {
Box(Modifier.weight(1f).layerBackdrop(glassBackdrop)) {
AnimatedContent(
targetState = selectedTab,
transitionSpec = {
if (targetState > initialState) {
(slideInHorizontally(tween(400)) { it } + fadeIn()).togetherWith(
slideOutHorizontally(tween(400)) { -it } + fadeOut())
} else {
(slideInHorizontally(tween(400)) { -it } + fadeIn()).togetherWith(
slideOutHorizontally(tween(400)) { it } + fadeOut())
}
},
label = "MainTabTransition",
modifier = Modifier.fillMaxSize()
) { tab ->
Column(Modifier.fillMaxSize()) {
when (tab) {
0 -> Spacer(Modifier.height(0.dp))
1 -> RecordsTopBar(recordsSegment) { recordsSegment = it }
2 -> LibraryTopBar(
isGrid = libraryIsGrid,
onViewToggle = { libraryIsGrid = !libraryIsGrid },
onAdd = { showLibraryAddDialog = true },
onOpenIconLibraryManager = { showIconLibraryManager = true },
)
3 -> {
if (showCacheDetail) {
Row(
Modifier.fillMaxWidth().statusBarsPadding().padding(horizontal = 18.dp, vertical = 14.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
Modifier.size(34.dp).clip(CircleShape).background(Color.White).border(0.5.dp, Color.LightGray, CircleShape)
.clickable { showCacheDetail = false },
contentAlignment = Alignment.Center,
) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, null, tint = Color(0xFF374151), modifier = Modifier.size(19.dp))
}
Spacer(Modifier.width(12.dp))
Text("缓存管理", color = GlassTokens.text, fontSize = 20.sp, fontWeight = FontWeight.Bold)
}
} else if (showAboutDetail) {
Row(
Modifier.fillMaxWidth().statusBarsPadding().padding(horizontal = 18.dp, vertical = 14.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
Modifier.size(34.dp).clip(CircleShape).background(Color.White).border(0.5.dp, Color.LightGray, CircleShape)
.clickable { showAboutDetail = false },
contentAlignment = Alignment.Center,
) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, null, tint = Color(0xFF374151), modifier = Modifier.size(19.dp))
}
Spacer(Modifier.width(12.dp))
Text("关于", color = GlassTokens.text, fontSize = 20.sp, fontWeight = FontWeight.Bold)
}
} else {
SettingsTopBar()
}
}
4 -> SearchTopBar(searchQuery, { searchQuery = it }, { })
}
Box(Modifier.weight(1f)) {
when (tab) {
0 -> HomeScreen(servers = servers, selected = selectedServer, onDetailVisibilityChanged = { homeDetailVisible = it })
1 -> RecordsScreen(segment = recordsSegment, activeServer = selectedServer)
2 -> LibraryScreen(
servers = servers,
selectedServer = selectedServer,
onSelectServer = { selectedServerId = it },
onAddServer = { server, onResult ->
coroutineScope.launch {
try {
val authResult = EmbyRepository.authenticate(server)
if (authResult.success) {
ServerRepository.addServer(server.copy(apiKey = authResult.accessToken, userId = authResult.userId))
if (selectedServerId.isBlank()) selectedServerId = server.id
onResult(null)
} else onResult(authResult.errorMessage ?: "登录失败")
} catch (e: Exception) { onResult("网络错误: " + (e.message ?: "未知")) }
}
},
onDeleteServer = { id ->
coroutineScope.launch { ServerRepository.deleteServer(id) }
if (selectedServerId == id) selectedServerId = ""
},
onUpdateServer = { updated, onResult ->
coroutineScope.launch {
try {
val authResult = EmbyRepository.authenticate(updated)
if (authResult.success) {
ServerRepository.updateServer(updated.copy(apiKey = authResult.accessToken, userId = authResult.userId))
onResult(null)
} else onResult(authResult.errorMessage ?: "登录失败")
} catch (e: Exception) { onResult("网络错误: " + (e.message ?: "未知")) }
}
},
showAddDialog = showLibraryAddDialog,
onDismissAddDialog = { showLibraryAddDialog = false },
isGrid = libraryIsGrid,
onChangeIconTarget = changeIconTarget,
onDismissChangeIcon = { changeIconTarget = null },
onPickIcon = { server, iconUrl -> ServerRepository.setRuntimeIcon(server.id, iconUrl) },
onRequestChangeIcon = { server -> changeIconTarget = server },
)
3 -> SettingsScreen(
servers = servers,
showCacheDetail = showCacheDetail,
onOpenCacheDetail = { showCacheDetail = true },
onBackFromCache = { showCacheDetail = false },
showAboutDetail = showAboutDetail,
onOpenAbout = { showAboutDetail = true },
onBackFromAbout = { showAboutDetail = false },
)
4 -> SearchScreen(query = searchQuery)
}
}
}
}
if (!homeDetailVisible) {
Box(Modifier.fillMaxWidth().padding(bottom = 12.dp), contentAlignment = Alignment.BottomCenter) {
LiquidAppBottomBar(
selectedTab = selectedTab,
onSelect = { selectedTab = it },
backdrop = glassBackdrop
)
}
}
}
}
}
}
}
}
}
}