1. 改服务详情卡片为侧边滑出卡片
2. 修复重连导致的通知栏信息不变更问题
This commit is contained in:
@@ -2,25 +2,36 @@ package com.tsmobile.app.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
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.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.LinkOff
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalDrawerSheet
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tsmobile.app.data.ConnectionState
|
||||
import com.tsmobile.app.data.ServerInfo
|
||||
@@ -36,27 +47,84 @@ fun ServerDetailCard(
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
var showDisconnectDialog by remember { mutableStateOf(false) }
|
||||
val maxHeight = (LocalConfiguration.current.screenHeightDp * 0.8f).dp
|
||||
|
||||
DetailSheetScaffold(
|
||||
title = serverName.ifEmpty { "服务器详情" },
|
||||
subtitle = serverAddress,
|
||||
onDismiss = onDismiss,
|
||||
modifier = Modifier.padding(UiTokens.Spacing.Large),
|
||||
maxHeight = maxHeight,
|
||||
content = { ServerInfoSection(serverInfo, connectionState) },
|
||||
footer = {
|
||||
ModalDrawerSheet {
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
// ═══ 顶部:服务器名称 + 地址 + 关闭按钮 ═══
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = UiTokens.Spacing.Large,
|
||||
end = UiTokens.Spacing.Small,
|
||||
top = UiTokens.Spacing.Large,
|
||||
bottom = UiTokens.Spacing.Medium,
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = serverName.ifEmpty { "服务器详情" },
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
if (serverAddress.isNotEmpty()) {
|
||||
Spacer(Modifier.height(UiTokens.Spacing.ExtraSmall))
|
||||
Text(
|
||||
text = serverAddress,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(onClick = onDismiss) {
|
||||
Icon(Icons.Default.Close, contentDescription = "关闭")
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant)
|
||||
|
||||
// ═══ 中部:欢迎消息 + 服务器信息(可滚动) ═══
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(UiTokens.Spacing.Large),
|
||||
verticalArrangement = Arrangement.spacedBy(UiTokens.Spacing.Medium),
|
||||
) {
|
||||
// 欢迎消息
|
||||
if (serverInfo != null && serverInfo.welcomeMessage.isNotEmpty()) {
|
||||
SectionCard(title = "欢迎消息") {
|
||||
Text(
|
||||
text = serverInfo.welcomeMessage,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 服务器详细信息
|
||||
SectionCard(title = "服务器信息") {
|
||||
ServerInfoContent(serverInfo, connectionState)
|
||||
}
|
||||
}
|
||||
|
||||
// ═══ 底部:断开连接按钮 ═══
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant)
|
||||
OutlinedButton(
|
||||
onClick = { showDisconnectDialog = true },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.error),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(UiTokens.Spacing.Large),
|
||||
colors = ButtonDefaults.outlinedButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.error
|
||||
),
|
||||
) {
|
||||
Icon(Icons.Default.LinkOff, contentDescription = null)
|
||||
Spacer(Modifier.width(UiTokens.Spacing.Small))
|
||||
Text("断开连接")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (showDisconnectDialog) {
|
||||
DisconnectConfirmDialog(
|
||||
@@ -67,7 +135,34 @@ fun ServerDetailCard(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ServerInfoSection(serverInfo: ServerInfo?, connectionState: ConnectionState?) {
|
||||
private fun SectionCard(
|
||||
title: String,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
tonalElevation = UiTokens.Elevation.Raised,
|
||||
) {
|
||||
Column(modifier = Modifier.padding(UiTokens.Spacing.Medium)) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
Spacer(Modifier.height(UiTokens.Spacing.Small))
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ServerInfoContent(
|
||||
serverInfo: ServerInfo?,
|
||||
connectionState: ConnectionState?,
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(UiTokens.Spacing.Small)) {
|
||||
DetailInfoRow("状态", when (connectionState) {
|
||||
is ConnectionState.Connected -> "已连接"
|
||||
@@ -78,17 +173,18 @@ private fun ServerInfoSection(serverInfo: ServerInfo?, connectionState: Connecti
|
||||
null -> "未连接"
|
||||
})
|
||||
if (serverInfo == null) {
|
||||
Text("正在加载服务器信息...", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Text(
|
||||
"正在加载服务器信息...",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
} else {
|
||||
DetailInfoRow("在线人数", "${serverInfo.clientsOnline} / ${serverInfo.maxClients}")
|
||||
DetailInfoRow("频道数", "${serverInfo.channelsOnline}")
|
||||
if (serverInfo.version.isNotEmpty()) DetailInfoRow("版本", serverInfo.version)
|
||||
if (serverInfo.platform.isNotEmpty()) DetailInfoRow("平台", serverInfo.platform)
|
||||
(serverInfo.uptime.toLongOrNull() ?: 0).takeIf { it > 0 }?.let { DetailInfoRow("运行时长", formatUptime(it)) }
|
||||
if (serverInfo.welcomeMessage.isNotEmpty()) {
|
||||
Spacer(Modifier.height(UiTokens.Spacing.Small))
|
||||
Text("欢迎消息", style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Text(serverInfo.welcomeMessage, style = MaterialTheme.typography.bodySmall)
|
||||
(serverInfo.uptime.toLongOrNull() ?: 0).takeIf { it > 0 }?.let {
|
||||
DetailInfoRow("运行时长", formatUptime(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,5 +194,9 @@ private fun formatUptime(seconds: Long): String {
|
||||
val days = seconds / 86400
|
||||
val hours = (seconds % 86400) / 3600
|
||||
val minutes = (seconds % 3600) / 60
|
||||
return when { days > 0 -> "${days}天${hours}小时"; hours > 0 -> "${hours}小时${minutes}分钟"; else -> "${minutes}分钟" }
|
||||
return when {
|
||||
days > 0 -> "${days}天${hours}小时"
|
||||
hours > 0 -> "${hours}小时${minutes}分钟"
|
||||
else -> "${minutes}分钟"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,12 +110,52 @@ fun ChannelListScreen(
|
||||
// 防抖:服务器详情按钮上次点击时间
|
||||
var lastServerDetailClickTime by remember { mutableLongStateOf(0L) }
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize().systemBarsPadding()) {
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
// 头部
|
||||
// 服务器状态(提前声明,供侧栏 drawerContent 读取)
|
||||
val serverInfo by serverViewModel.serverInfo.collectAsState()
|
||||
val serverState by serverViewModel.state.collectAsState()
|
||||
val themeMode by serverViewModel.themeMode.collectAsState()
|
||||
|
||||
// 侧栏抽屉状态
|
||||
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||
|
||||
// 同步 showServerDetail 与抽屉开合
|
||||
LaunchedEffect(showServerDetail) {
|
||||
if (showServerDetail) drawerState.open() else drawerState.close()
|
||||
}
|
||||
// 手势滑动打开时同步状态并加载数据
|
||||
LaunchedEffect(drawerState.isOpen) {
|
||||
if (drawerState.isOpen && !showServerDetail) {
|
||||
serverViewModel.fetchServerInfo()
|
||||
showServerDetail = true
|
||||
}
|
||||
}
|
||||
// 手势滑动关闭或编程关闭后同步状态
|
||||
LaunchedEffect(drawerState.isClosed) {
|
||||
if (drawerState.isClosed && showServerDetail) {
|
||||
showServerDetail = false
|
||||
}
|
||||
}
|
||||
|
||||
ModalNavigationDrawer(
|
||||
drawerState = drawerState,
|
||||
drawerContent = {
|
||||
ServerDetailCard(
|
||||
serverInfo = serverInfo,
|
||||
serverName = serverInfo?.name ?: "",
|
||||
serverAddress = serverState.address,
|
||||
connectionState = connectionState,
|
||||
onDisconnect = {
|
||||
showServerDetail = false
|
||||
serverViewModel.disconnect()
|
||||
onNavigateToServerConfig()
|
||||
},
|
||||
onDismiss = { showServerDetail = false },
|
||||
)
|
||||
},
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize().systemBarsPadding()) {
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
// 头部
|
||||
ChannelListHeader(
|
||||
connectionState = connectionState,
|
||||
serverName = serverInfo?.name ?: "",
|
||||
@@ -182,6 +222,7 @@ fun ChannelListScreen(
|
||||
}
|
||||
|
||||
}
|
||||
} // ModalNavigationDrawer
|
||||
|
||||
// ─── 弹窗层 ───
|
||||
|
||||
@@ -201,28 +242,6 @@ fun ChannelListScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// 服务器详情卡弹窗(BottomSheet)
|
||||
if (showServerDetail) {
|
||||
val serverInfo by serverViewModel.serverInfo.collectAsState()
|
||||
val serverState by serverViewModel.state.collectAsState()
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = { showServerDetail = false },
|
||||
) {
|
||||
ServerDetailCard(
|
||||
serverInfo = serverInfo,
|
||||
serverName = serverInfo?.name ?: "",
|
||||
serverAddress = serverState.address,
|
||||
connectionState = connectionState,
|
||||
onDisconnect = {
|
||||
showServerDetail = false
|
||||
serverViewModel.disconnect()
|
||||
onNavigateToServerConfig()
|
||||
},
|
||||
onDismiss = { showServerDetail = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 频道详情卡弹窗(BottomSheet)
|
||||
if (showChannelDetailCard) {
|
||||
ModalBottomSheet(
|
||||
|
||||
@@ -192,6 +192,7 @@ class ServerViewModel(application: Application) : AndroidViewModel(application)
|
||||
lastChannelId = chId
|
||||
}
|
||||
_connectionState.value = ConnectionState.WaitingForNetwork()
|
||||
ConnectionService.stop(getApplication())
|
||||
if (networkMonitor.isAvailable.value) {
|
||||
startReconnect("网络变化,正在重连...")
|
||||
}
|
||||
@@ -1071,11 +1072,13 @@ class ServerViewModel(application: Application) : AndroidViewModel(application)
|
||||
return
|
||||
}
|
||||
|
||||
// 已在 WaitingForNetwork 或 Reconnecting 状态 → 我们的响应式监控已处理
|
||||
// 已在 WaitingForNetwork 或 Reconnecting 状态 → 响应式监控已处理
|
||||
// 但仍需更新通知栏(否则断连后仍显示"已连接")
|
||||
if (_connectionState.value is ConnectionState.WaitingForNetwork ||
|
||||
_connectionState.value is ConnectionState.Reconnecting
|
||||
) {
|
||||
android.util.Log.d(TAG, "onDisconnected while already handling reconnect, ignoring")
|
||||
android.util.Log.d(TAG, "onDisconnected while already handling reconnect, stopping notification")
|
||||
ConnectionService.stop(getApplication())
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user