289 lines
18 KiB
Kotlin
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
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|