package cc.unitmesh.devins.ui.project 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.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Delete import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import kotlinx.coroutines.launch import kotlinx.datetime.Instant import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime /** * ProjectListScreen - 项目列表界面 */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun ProjectListScreen( viewModel: ProjectViewModel, onProjectClick: (Project) -> Unit ) { val projects by viewModel.projects.collectAsState() val isLoading by viewModel.isLoading.collectAsState() val errorMessage by viewModel.errorMessage.collectAsState() val scope = rememberCoroutineScope() var showCreateDialog by remember { mutableStateOf(false) } LaunchedEffect(Unit) { viewModel.loadProjects() } Scaffold( topBar = { TopAppBar( title = { Text("我的项目") } ) }, floatingActionButton = { FloatingActionButton( onClick = { showCreateDialog = true }, containerColor = MaterialTheme.colorScheme.primary ) { Icon(Icons.Default.Add, contentDescription = "创建项目") } } ) { paddingValues -> Column( modifier = Modifier .fillMaxSize() .padding(paddingValues) ) { // Error message errorMessage?.let { error -> Card( colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.errorContainer ), modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Row( modifier = Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically ) { Text( text = error, color = MaterialTheme.colorScheme.onErrorContainer, style = MaterialTheme.typography.bodySmall, modifier = Modifier.weight(1f) ) TextButton(onClick = { viewModel.clearError() }) { Text("关闭") } } } } // Project list if (isLoading) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { CircularProgressIndicator() } } else if (projects.isEmpty()) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp) ) { Text( text = "还没有项目", style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant ) Text( text = "点击 + 创建新项目", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) } } } else { LazyColumn( modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { items(projects) { project -> ProjectCard( project = project, onClick = { onProjectClick(project) }, onDelete = { scope.launch { viewModel.deleteProject(project.id) } } ) } } } } } // Create project dialog if (showCreateDialog) { CreateProjectDialog( viewModel = viewModel, onDismiss = { showCreateDialog = false } ) } } @Composable fun ProjectCard( project: Project, onClick: () -> Unit, onDelete: () -> Unit ) { Card( modifier = Modifier .fillMaxWidth() .clickable(onClick = onClick), elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) ) { Row( modifier = Modifier .fillMaxWidth() .padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { Column(modifier = Modifier.weight(1f)) { Text( text = project.name, style = MaterialTheme.typography.titleMedium ) Spacer(modifier = Modifier.height(4.dp)) project.description?.let { desc -> Text( text = desc, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) Spacer(modifier = Modifier.height(4.dp)) } project.gitUrl?.let { url -> Text( text = "Git: $url", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.primary ) Spacer(modifier = Modifier.height(4.dp)) } Text( text = formatTimestamp(project.createdAt), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) } IconButton(onClick = onDelete) { Icon( Icons.Default.Delete, contentDescription = "删除项目", tint = MaterialTheme.colorScheme.error ) } } } } private fun formatTimestamp(timestamp: Long): String { val instant = Instant.fromEpochMilliseconds(timestamp) val dateTime = instant.toLocalDateTime(TimeZone.currentSystemDefault()) return "${dateTime.year}-${dateTime.monthNumber.toString().padStart(2, '0')}-${dateTime.dayOfMonth.toString().padStart(2, '0')} " + "${dateTime.hour.toString().padStart(2, '0')}:${dateTime.minute.toString().padStart(2, '0')}" }