diff --git a/app/src/main/kotlin/com/example/myapplication/data/EmbyRepository.kt b/app/src/main/kotlin/com/example/myapplication/data/EmbyRepository.kt index a92713a..98de38b 100644 --- a/app/src/main/kotlin/com/example/myapplication/data/EmbyRepository.kt +++ b/app/src/main/kotlin/com/example/myapplication/data/EmbyRepository.kt @@ -295,7 +295,7 @@ object EmbyRepository { limit: Int = 6, ): EmbyLibraryPage = withContext(Dispatchers.IO) { try { - val url = "${server.fullUrl}/emby/Users/${server.userId}/Items?ParentId=$libraryId&SortBy=$sortBy&SortOrder=$sortOrder&StartIndex=${startIndex.coerceAtLeast(0)}&Limit=${limit.coerceAtLeast(1)}&IncludeItemTypes=Movie,Series&Fields=PrimaryImageAspectRatio,Overview,ProductionYear,Genres,RecursiveChildCount,ChildCount,DateCreated,RunTimeTicks,UserData&Recursive=true" + val url = "${server.fullUrl}/emby/Users/${server.userId}/Items?ParentId=$libraryId&SortBy=$sortBy&SortOrder=$sortOrder&StartIndex=${startIndex.coerceAtLeast(0)}&Limit=${limit.coerceAtLeast(1)}&IncludeItemTypes=Movie,Series&Fields=PrimaryImageAspectRatio,Overview,ProductionYear,Genres,RecursiveChildCount,ChildCount,DateCreated,RunTimeTicks,UserData,CommunityRating,OfficialRating&Recursive=true" val request = Request.Builder() .url(url) .header("X-Emby-Token", server.apiKey) diff --git a/app/src/main/kotlin/com/example/myapplication/screens/HomeScreen.kt b/app/src/main/kotlin/com/example/myapplication/screens/HomeScreen.kt index 0292c17..fc0e6ea 100644 --- a/app/src/main/kotlin/com/example/myapplication/screens/HomeScreen.kt +++ b/app/src/main/kotlin/com/example/myapplication/screens/HomeScreen.kt @@ -490,9 +490,17 @@ private fun MediaPoster( ImageRequest.Builder(context).data(it).crossfade(true).build() } } + val subtitle = listOfNotNull( + item.communityRating?.takeIf { it > 0.0 }?.let { "★ ${String.format("%.1f", it)}" }, + item.productionYear?.let { "$it" }, + ).takeIf { it.isNotEmpty() }?.joinToString(" ") + LumaMediaCard( model = imageRequest, title = item.seriesName ?: item.name, + subtitle = subtitle, + subtitleColor = homeSecondaryColor(), + subtitleStarColor = LumaColors.ratingStar, modifier = modifier.width(115.dp), imageAspectRatio = 115f / 165f, titleColor = homeContentColor(), @@ -590,6 +598,7 @@ private fun LazyLibrarySection( refreshToken: Int, onItemClick: (EmbyItem) -> Unit = {}, onViewAll: () -> Unit = {}, + onEmpty: () -> Unit = {}, ) { val cacheKey = remember(activeServer.id, activeServer.fullUrl, activeServer.userId) { homeCacheKey(activeServer) @@ -616,6 +625,7 @@ private fun LazyLibrarySection( EmbyLibraryPage() }.also { loaded -> HomeContentCache.putLibraryPage(cacheKey, folder.id, loaded) + if (loaded.items.isEmpty()) onEmpty() } } // Fetch accurate episode counts for series items (parallel) @@ -637,7 +647,7 @@ private fun LazyLibrarySection( } } - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { SectionTitle(title = folder.name, count = page?.totalCount ?: 0, onMoreClick = onViewAll) val list = page?.items if (list == null) { @@ -676,13 +686,12 @@ private fun SectionTitle( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, ) { - Text(text = title, color = homeContentColor(), fontSize = 18.sp, fontWeight = FontWeight.Bold) + Text(text = title, color = homeContentColor(), fontSize = 15.sp, fontWeight = FontWeight.Bold) if (count > 0) { Box( Modifier - .heightIn(min = 48.dp) .clickable(onClick = { onMoreClick?.invoke() }) - .padding(horizontal = 8.dp), + .padding(horizontal = 8.dp, vertical = 4.dp), contentAlignment = Alignment.Center, ) { Text( @@ -698,7 +707,7 @@ private fun SectionTitle( @Composable private fun MyMediaGrid(libraries: List, serverUrl: String, onSelect: (EmbyMediaFolder) -> Unit) { - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { SectionTitle(title = stringResource(Res.string.section_library)) LazyRow(horizontalArrangement = Arrangement.spacedBy(12.dp)) { items(libraries, key = { it.id }) { folder -> @@ -898,6 +907,7 @@ fun HomeScreen( var reloadToken by remember(homeKey) { mutableIntStateOf(0) } var showResourceStats by remember(homeKey) { mutableStateOf(false) } var serverMenuVisible by remember(homeKey) { mutableStateOf(false) } + val emptyLibraries = remember(homeKey) { mutableStateMapOf() } var serverMenuMounted by remember(homeKey) { mutableStateOf(false) } var serverAnchorBounds by remember { mutableStateOf(null) } var homeRootOrigin by remember { mutableStateOf(Offset.Zero) } @@ -1064,8 +1074,8 @@ fun HomeScreen( if (resumeItems.isNotEmpty()) { item(key = "resume", contentType = "resume") { - HomeSection(first = true) { - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + HomeSection { + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { SectionTitle(title = stringResource(Res.string.continue_watching)) LazyRow(horizontalArrangement = Arrangement.spacedBy(10.dp)) { items(resumeItems, key = { it.id }) { item -> @@ -1081,7 +1091,7 @@ fun HomeScreen( if (libraries.isNotEmpty()) { item(key = "media", contentType = "media") { - HomeSection(first = resumeItems.isEmpty()) { + HomeSection { MyMediaGrid(libraries, server.fullUrl) { folder -> onViewAll(server.id, folder.id, folder.name) } @@ -1089,7 +1099,8 @@ fun HomeScreen( } } - items(libraries, key = { "library_${it.id}" }, contentType = { "library" }) { folder -> + val visibleLibraries = libraries.filter { emptyLibraries[it.id] != true } + items(visibleLibraries, key = { "library_${it.id}" }, contentType = { "library" }) { folder -> HomeSection { LazyLibrarySection( folder = folder, @@ -1097,12 +1108,13 @@ fun HomeScreen( refreshToken = reloadToken, onItemClick = { onOpenDetail(it.id) }, onViewAll = { onViewAll(server.id, folder.id, folder.name) }, + onEmpty = { emptyLibraries[folder.id] = true }, ) } } item(key = "stats", contentType = "stats") { - HomeSection(first = resumeItems.isEmpty() && libraries.isEmpty()) { + HomeSection { ResourceStatsCard( stats = resourceStats, onClick = { showResourceStats = true }, @@ -1172,12 +1184,12 @@ fun HomeScreen( } @Composable -private fun HomeSection(first: Boolean = false, content: @Composable () -> Unit) { +private fun HomeSection(content: @Composable () -> Unit) { Box( Modifier.fillMaxWidth().padding( start = LumaSpacing.md, end = LumaSpacing.md, - top = if (first) LumaSpacing.md else LumaSpacing.xl, + top = LumaSpacing.md, ), ) { content() diff --git a/app/src/main/kotlin/com/example/myapplication/ui/design/LumaComponents.kt b/app/src/main/kotlin/com/example/myapplication/ui/design/LumaComponents.kt index 451c003..ce30a22 100644 --- a/app/src/main/kotlin/com/example/myapplication/ui/design/LumaComponents.kt +++ b/app/src/main/kotlin/com/example/myapplication/ui/design/LumaComponents.kt @@ -192,6 +192,9 @@ fun LumaMediaCard( titleSize: TextUnit = LumaTypography.body, titleWeight: FontWeight = FontWeight.Medium, titleSpacing: Dp = LumaSpacing.xs, + subtitle: String? = null, + subtitleColor: Color = titleColor.copy(alpha = 0.6f), + subtitleStarColor: Color = subtitleColor, progress: Float? = null, onClick: (() -> Unit)? = null, overlay: @Composable BoxScope.() -> Unit = {}, @@ -283,6 +286,29 @@ fun LumaMediaCard( maxLines = 1, overflow = TextOverflow.Ellipsis, ) + if (!subtitle.isNullOrBlank()) { + val parts = subtitle.split(" ", limit = 2) + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = parts[0], + color = subtitleStarColor, + fontSize = (titleSize.value - 1).sp, + fontWeight = FontWeight.Normal, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + if (parts.size > 1) { + Text( + text = " ${parts[1]}", + color = subtitleColor, + fontSize = (titleSize.value - 1).sp, + fontWeight = FontWeight.Normal, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } + } } }