diff --git a/app/src/main/java/cn/gdeiassistant/ui/components/AppChrome.kt b/app/src/main/java/cn/gdeiassistant/ui/components/AppChrome.kt index 7b49bf2..3ed10b3 100644 --- a/app/src/main/java/cn/gdeiassistant/ui/components/AppChrome.kt +++ b/app/src/main/java/cn/gdeiassistant/ui/components/AppChrome.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -125,7 +126,7 @@ fun BadgePill( text = text, style = MaterialTheme.typography.labelMedium.copy( fontWeight = FontWeight.Bold, - letterSpacing = 0.5.sp + letterSpacing = 0.sp ), color = if (onGradient) Color.White else tint ) @@ -177,7 +178,9 @@ fun ActionTile( text = title, style = MaterialTheme.typography.titleMedium.copy( fontWeight = FontWeight.Bold - ) + ), + maxLines = 2, + overflow = TextOverflow.Ellipsis ) subtitle?.takeIf { it.isNotBlank() }?.let { sub -> Spacer(modifier = Modifier.size(4.dp)) @@ -185,7 +188,7 @@ fun ActionTile( text = sub, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, - maxLines = 1, + maxLines = 2, overflow = TextOverflow.Ellipsis ) } @@ -259,13 +262,19 @@ fun MetricChip( Text( text = label, style = MaterialTheme.typography.labelSmall, - color = if (onGradient) Color.White.copy(alpha = 0.8f) else MaterialTheme.colorScheme.onSurfaceVariant + color = if (onGradient) Color.White.copy(alpha = 0.8f) else MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center ) Spacer(modifier = Modifier.size(4.dp)) Text( text = value, style = MaterialTheme.typography.labelLarge.copy(fontSize = 16.sp), // Monospace from labelLarge - color = if (onGradient) Color.White else MaterialTheme.colorScheme.onSurface + color = if (onGradient) Color.White else MaterialTheme.colorScheme.onSurface, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center ) } } diff --git a/app/src/main/java/cn/gdeiassistant/ui/components/CommonComponents.kt b/app/src/main/java/cn/gdeiassistant/ui/components/CommonComponents.kt index 06b4fb7..0c17e7e 100644 --- a/app/src/main/java/cn/gdeiassistant/ui/components/CommonComponents.kt +++ b/app/src/main/java/cn/gdeiassistant/ui/components/CommonComponents.kt @@ -26,6 +26,8 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -45,9 +47,9 @@ fun BentoCard( val borderColor = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.42f) val cardModifier = modifier .shadow( - elevation = if (onClick != null) 3.dp else 0.dp, + elevation = if (onClick != null) 1.dp else 0.dp, shape = AppShapes.card, - spotColor = Color.Black.copy(alpha = 0.08f), + spotColor = Color.Black.copy(alpha = 0.05f), ambientColor = Color.Black.copy(alpha = 0.03f) ) .clip(AppShapes.card) @@ -90,7 +92,10 @@ fun SelectionPill( modifier = Modifier.padding(horizontal = 14.dp, vertical = 8.dp), style = MaterialTheme.typography.labelLarge, fontWeight = if (selected) FontWeight.Bold else FontWeight.Medium, - color = contentColor + color = contentColor, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center ) } } @@ -253,7 +258,8 @@ fun RemoteThumbnail( modifier: Modifier = Modifier, width: Dp = 72.dp, height: Dp = 72.dp, - tint: Color = MaterialTheme.colorScheme.primary + tint: Color = MaterialTheme.colorScheme.primary, + contentDescription: String? = fallbackLabel ) { val resolvedLabel = fallbackLabel.trim().firstOrNull()?.uppercase() ?: "G" Box( @@ -285,9 +291,9 @@ fun RemoteThumbnail( } else { SubcomposeAsyncImage( model = imageModel, - contentDescription = null, + contentDescription = contentDescription, modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop, + contentScale = ContentScale.Fit, loading = { Text( text = resolvedLabel, @@ -348,7 +354,10 @@ fun TextTabSelector( text = label, style = MaterialTheme.typography.labelLarge, fontWeight = if (selected) FontWeight.Bold else FontWeight.Medium, - color = if (selected) tint else MaterialTheme.colorScheme.onSurfaceVariant + color = if (selected) tint else MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center ) Spacer(modifier = Modifier.height(8.dp)) Box( diff --git a/app/src/main/java/cn/gdeiassistant/ui/components/NativeMediaCards.kt b/app/src/main/java/cn/gdeiassistant/ui/components/NativeMediaCards.kt index 7488e98..97114c1 100644 --- a/app/src/main/java/cn/gdeiassistant/ui/components/NativeMediaCards.kt +++ b/app/src/main/java/cn/gdeiassistant/ui/components/NativeMediaCards.kt @@ -64,6 +64,7 @@ fun NativeImageGallery( horizontalArrangement = Arrangement.spacedBy(12.dp) ) { imageUrls.forEachIndexed { index, url -> + val imageLabel = stringResource(R.string.native_media_image_position, index + 1, imageUrls.size) Column( modifier = Modifier.width(cardWidth), verticalArrangement = Arrangement.spacedBy(8.dp) @@ -74,12 +75,12 @@ fun NativeImageGallery( ) { SubcomposeAsyncImage( model = url, - contentDescription = null, + contentDescription = imageLabel, modifier = Modifier .fillMaxWidth() .height(cardHeight) .clip(AppShapes.card), - contentScale = ContentScale.Crop, + contentScale = ContentScale.Fit, loading = { Box( modifier = Modifier @@ -113,7 +114,7 @@ fun NativeImageGallery( ) } Text( - text = stringResource(R.string.native_media_image_position, index + 1, imageUrls.size), + text = imageLabel, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) diff --git a/app/src/main/java/cn/gdeiassistant/ui/schedule/ScheduleScreen.kt b/app/src/main/java/cn/gdeiassistant/ui/schedule/ScheduleScreen.kt index e62511c..3b7fc2a 100644 --- a/app/src/main/java/cn/gdeiassistant/ui/schedule/ScheduleScreen.kt +++ b/app/src/main/java/cn/gdeiassistant/ui/schedule/ScheduleScreen.kt @@ -203,6 +203,13 @@ private fun ScheduleContent( onCourseClick = { selectedCourse = it } ) } + item { + ScheduleListCard( + state = state, + dayLabels = dayLabels, + onCourseClick = { selectedCourse = it } + ) + } } if (showWeekPicker) { @@ -716,6 +723,101 @@ private fun ScheduleGridCard( private data class SchedulePlacement(val course: Schedule, val row: Int, val column: Int, val length: Int) +@Composable +private fun ScheduleListCard( + state: ScheduleUiState, + dayLabels: List, + onCourseClick: (Schedule) -> Unit +) { + SectionCard(modifier = Modifier.fillMaxWidth()) { + Text( + text = stringResource(R.string.schedule_list_title), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = stringResource(R.string.schedule_list_subtitle), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Spacer(modifier = Modifier.height(14.dp)) + if (state.scheduleList.isEmpty()) { + Text( + text = stringResource(R.string.schedule_empty_grid), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + } else { + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + dayLabels.forEachIndexed { dayIndex, labelRes -> + val courses = state.coursesForDay(dayIndex) + if (courses.isNotEmpty()) { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + text = stringResource(labelRes), + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.primary, + fontWeight = FontWeight.Bold + ) + courses.forEach { course -> + ScheduleListRow(course = course, onClick = { onCourseClick(course) }) + } + } + } + } + } + } + } +} + +@Composable +private fun ScheduleListRow(course: Schedule, onClick: () -> Unit) { + Surface( + modifier = Modifier.fillMaxWidth(), + onClick = onClick, + shape = RoundedCornerShape(18.dp), + color = MaterialTheme.colorScheme.surfaceContainerLow + ) { + Column( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 14.dp), + verticalArrangement = Arrangement.spacedBy(6.dp) + ) { + Text( + text = course.scheduleName ?: stringResource(R.string.schedule_course_unnamed), + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.Bold, + lineHeight = MaterialTheme.typography.titleSmall.fontSize * 1.25f + ) + Text( + text = listOfNotNull( + course.sectionDisplayText(), + course.scheduleLocation ?: stringResource(R.string.schedule_location_pending) + ).joinToString(" · "), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Text( + text = course.scheduleTeacher ?: stringResource(R.string.schedule_teacher_pending), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.tertiary + ) + Text( + text = course.scheduleWeek + ?: stringResource( + R.string.schedule_week_range, + course.minScheduleWeek ?: 1, + course.maxScheduleWeek ?: 1 + ), + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } +} + // ────────────────────────────────────────────────────────── // 对话框 // ────────────────────────────────────────────────────────── diff --git a/app/src/main/java/cn/gdeiassistant/ui/theme/Type.kt b/app/src/main/java/cn/gdeiassistant/ui/theme/Type.kt index fcd15f8..fb0c3b9 100644 --- a/app/src/main/java/cn/gdeiassistant/ui/theme/Type.kt +++ b/app/src/main/java/cn/gdeiassistant/ui/theme/Type.kt @@ -44,14 +44,14 @@ val AppTypography = Typography( fontWeight = FontWeight.Normal, fontSize = 16.sp, lineHeight = 24.sp, - letterSpacing = 0.5.sp + letterSpacing = 0.sp ), bodyMedium = TextStyle( fontFamily = FontFamily.SansSerif, fontWeight = FontWeight.Normal, fontSize = 14.sp, lineHeight = 20.sp, - letterSpacing = 0.25.sp + letterSpacing = 0.sp ), // Specific style for numbers/data (e.g., grades, balance) labelLarge = TextStyle( @@ -59,6 +59,6 @@ val AppTypography = Typography( fontWeight = FontWeight.Bold, fontSize = 14.sp, lineHeight = 20.sp, - letterSpacing = 0.1.sp + letterSpacing = 0.sp ) ) diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index ad8076e..7f02e6c 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -178,6 +178,8 @@ No classes scheduled this week Weekly Schedule Tap a class card to view location, instructor and weeks + Full Course List + Grouped by day, with long names, locations and instructors shown in full. Section No classes scheduled this week Select Week diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 43b968a..9d1095f 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -178,6 +178,8 @@ 今週は授業の予定がありません 週間時間割 科目カードをタップすると教室・教員・対象週を確認できます + 全授業リスト + 曜日ごとに表示し、長い科目名・教室・教員名も確認できます。 コマ 今週は授業の予定がありません 週を選択 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 9af98bf..5db21a6 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -178,6 +178,8 @@ 이번 주에는 수업 일정이 없습니다 주간 시간표 수업 카드를 탭하면 장소, 교수 및 주차를 확인할 수 있습니다 + 전체 수업 목록 + 요일별로 펼쳐 긴 수업명, 장소와 교수 정보를 확인할 수 있습니다. 교시 이번 주에는 수업 일정이 없습니다 주차 선택 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index bb380d4..b884ce4 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -177,6 +177,8 @@ 當前周還沒有課程安排 周課表 點按課程卡片可查看上課地點、教師與周次 + 完整課程列表 + 按星期展開,長課程名、地點和教師會完整顯示。 節次 當前周沒有課程安排 選擇周數 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 65657ff..24a3123 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -177,6 +177,8 @@ 當前週還沒有課程安排 週課表 點按課程卡片可查看上課地點、教師與週次 + 完整課程列表 + 按星期展開,長課程名、地點和教師會完整顯示。 節次 當前週沒有課程安排 選擇週數 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8364ef8..3e1211d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -177,6 +177,8 @@ 当前周还没有课程安排 周课表 点按课程卡片可查看上课地点、教师与周次 + 完整课程列表 + 按星期展开,长课程名、地点和教师会完整显示。 节次 当前周没有课程安排 选择周数