package cc.unitmesh.devins.ui.session import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.ExitToApp import androidx.compose.material.icons.filled.Refresh import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import cc.unitmesh.devins.ui.compose.theme.AutoDevColors import androidx.compose.ui.unit.dp import cc.unitmesh.session.Session import cc.unitmesh.session.SessionStatus import kotlinx.coroutines.launch import kotlinx.datetime.Instant import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime /** * SessionListScreen - 会话列表界面 */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun SessionListScreen( viewModel: SessionViewModel, onSessionClick: (Session) -> Unit, onCreateSession: () -> Unit, onLogout: () -> Unit ) { val sessions by viewModel.sessions.collectAsState() val activeSessions by viewModel.activeSessions.collectAsState() val currentUser by viewModel.currentUser.collectAsState() val isLoading by viewModel.isLoading.collectAsState() val scope = rememberCoroutineScope() var showActiveSessions by remember { mutableStateOf(true) } var showCreateDialog by remember { mutableStateOf(false) } Scaffold( topBar = { TopAppBar( title = { Text("我的会话") }, actions = { // Refresh button IconButton(onClick = { scope.launch { viewModel.loadSessions() } }) { Icon(Icons.Default.Refresh, contentDescription = "刷新") } // Logout button IconButton(onClick = onLogout) { Icon(Icons.Default.ExitToApp, contentDescription = "登出") } } ) }, floatingActionButton = { FloatingActionButton( onClick = { showCreateDialog = true }, containerColor = MaterialTheme.colorScheme.primary ) { Icon(Icons.Default.Add, contentDescription = "创建会话") } } ) { paddingValues -> Column( modifier = Modifier .fillMaxSize() .padding(paddingValues) .padding(16.dp) ) { // User info currentUser?.let { user -> Card( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.primaryContainer ) ) { Row( modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { Text( text = "👤 $user", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onPrimaryContainer ) } } } Spacer(modifier = Modifier.height(16.dp)) // Tab: Active / All Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { FilterChip( selected = showActiveSessions, onClick = { showActiveSessions = true }, label = { Text("进行中 (${activeSessions.size})") } ) FilterChip( selected = !showActiveSessions, onClick = { showActiveSessions = false }, label = { Text("全部 (${sessions.size})") } ) } Spacer(modifier = Modifier.height(16.dp)) // Session list if (isLoading) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { CircularProgressIndicator() } } else { val displaySessions = if (showActiveSessions) activeSessions else sessions if (displaySessions.isEmpty()) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Text( text = if (showActiveSessions) "没有进行中的会话" else "还没有会话,点击 + 创建", style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant ) } } else { LazyColumn( verticalArrangement = Arrangement.spacedBy(12.dp) ) { items(displaySessions) { session -> SessionCard( session = session, onClick = { onSessionClick(session) } ) } } } } } } // Create Session Dialog if (showCreateDialog) { CreateSessionDialog( viewModel = viewModel, onDismiss = { showCreateDialog = false } ) } } @Composable fun SessionCard(session: Session, onClick: () -> Unit) { Card( modifier = Modifier .fillMaxWidth() .clickable(onClick = onClick), elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) ) { Column(modifier = Modifier.padding(16.dp)) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text( text = session.task, style = MaterialTheme.typography.titleMedium, modifier = Modifier.weight(1f) ) Spacer(modifier = Modifier.width(8.dp)) StatusBadge(session.status) } Spacer(modifier = Modifier.height(8.dp)) Text( text = "Project: ${session.projectId}", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) Spacer(modifier = Modifier.height(4.dp)) Text( text = "Created: ${formatTimestamp(session.createdAt)}", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) } } } @Composable fun StatusBadge(status: SessionStatus) { val (color, text) = when (status) { SessionStatus.PENDING -> AutoDevColors.Signal.warn to "等待中" SessionStatus.RUNNING -> AutoDevColors.Signal.success to "运行中" SessionStatus.PAUSED -> AutoDevColors.Signal.warn to "暂停" SessionStatus.COMPLETED -> AutoDevColors.Signal.info to "完成" SessionStatus.FAILED -> AutoDevColors.Signal.error to "失败" SessionStatus.CANCELLED -> AutoDevColors.Text.tertiary to "取消" } Surface( color = color, shape = RoundedCornerShape(4.dp) ) { Text( text = text, modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp), style = MaterialTheme.typography.labelSmall, color = Color.White ) } } fun formatTimestamp(timestamp: Long): String { val instant = Instant.fromEpochMilliseconds(timestamp) val localDateTime = instant.toLocalDateTime(TimeZone.currentSystemDefault()) return "${localDateTime.year}-${localDateTime.monthNumber.toString().padStart(2, '0')}-${localDateTime.dayOfMonth.toString().padStart(2, '0')} " + "${localDateTime.hour.toString().padStart(2, '0')}:${localDateTime.minute.toString().padStart(2, '0')}" }