From b6fa1cbdb9db18a58e7be6cad4f527fe04cec698 Mon Sep 17 00:00:00 2001 From: KhooLy <73142442+KhooLy@users.noreply.github.com> Date: Fri, 17 Jul 2026 01:22:32 +0300 Subject: [PATCH] Show sources progressively and render full stream name/title in cards --- .../app/ui/catalog/AndroidDetailDataSource.kt | 3 +- .../com/fluxa/app/ui/routes/PlayerRoutes.kt | 3 +- .../shared/feature/detail/DetailContracts.kt | 3 +- .../feature/detail/SourceSelectionScreen.kt | 86 +++++-------------- 4 files changed, 28 insertions(+), 67 deletions(-) diff --git a/app/src/main/java/com/fluxa/app/ui/catalog/AndroidDetailDataSource.kt b/app/src/main/java/com/fluxa/app/ui/catalog/AndroidDetailDataSource.kt index 4f4ce88..2c7c1d6 100644 --- a/app/src/main/java/com/fluxa/app/ui/catalog/AndroidDetailDataSource.kt +++ b/app/src/main/java/com/fluxa/app/ui/catalog/AndroidDetailDataSource.kt @@ -149,7 +149,8 @@ internal fun com.fluxa.app.data.remote.Stream.toDetailStreamUiModel(): DetailStr return DetailStreamUiModel( addonName = addonName.orEmpty(), title = title ?: name.orEmpty(), - playableUrl = url + playableUrl = url, + name = name.orEmpty() ) } diff --git a/app/src/main/java/com/fluxa/app/ui/routes/PlayerRoutes.kt b/app/src/main/java/com/fluxa/app/ui/routes/PlayerRoutes.kt index 63eead3..77edc91 100644 --- a/app/src/main/java/com/fluxa/app/ui/routes/PlayerRoutes.kt +++ b/app/src/main/java/com/fluxa/app/ui/routes/PlayerRoutes.kt @@ -28,7 +28,8 @@ internal fun PlayerRoute( DetailStreamUiModel( addonName = stream.addonName.orEmpty(), title = stream.title.orEmpty(), - playableUrl = stream.playableUrl.orEmpty() + playableUrl = stream.playableUrl.orEmpty(), + name = stream.name.orEmpty() ) }.filter { it.playableUrl.isNotBlank() } SourceSelectionScreen( diff --git a/shared/src/commonMain/kotlin/com/fluxa/app/shared/feature/detail/DetailContracts.kt b/shared/src/commonMain/kotlin/com/fluxa/app/shared/feature/detail/DetailContracts.kt index 9307e9c..7dbb0f6 100644 --- a/shared/src/commonMain/kotlin/com/fluxa/app/shared/feature/detail/DetailContracts.kt +++ b/shared/src/commonMain/kotlin/com/fluxa/app/shared/feature/detail/DetailContracts.kt @@ -36,7 +36,8 @@ data class DetailStreamUiModel( val addonName: String, val title: String, val playableUrl: String, - val requestHeadersJson: String = "{}" + val requestHeadersJson: String = "{}", + val name: String = "" ) data class DetailUiModel( diff --git a/shared/src/commonMain/kotlin/com/fluxa/app/shared/feature/detail/SourceSelectionScreen.kt b/shared/src/commonMain/kotlin/com/fluxa/app/shared/feature/detail/SourceSelectionScreen.kt index 8fbf558..f7edf2f 100644 --- a/shared/src/commonMain/kotlin/com/fluxa/app/shared/feature/detail/SourceSelectionScreen.kt +++ b/shared/src/commonMain/kotlin/com/fluxa/app/shared/feature/detail/SourceSelectionScreen.kt @@ -79,7 +79,7 @@ fun SourceSelectionScreen( ) } when { - content.isLoadingStreams -> Box( + visibleStreams.isEmpty() && content.isLoadingStreams -> Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { @@ -103,6 +103,19 @@ fun SourceSelectionScreen( items(visibleStreams, key = { it.playableUrl }) { stream -> StreamCard(stream = stream, onClick = { onStreamSelected(stream) }) } + if (content.isLoadingStreams) { + item(key = "loading-more") { + Box( + modifier = Modifier.fillMaxWidth().padding(vertical = 16.dp), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator( + color = Color.White.copy(alpha = 0.7f), + modifier = Modifier.size(22.dp) + ) + } + } + } } } } @@ -246,44 +259,10 @@ private fun AddonChip(label: String, selected: Boolean, onClick: () -> Unit) { } } -private data class StreamCardInfo( - val headline: String, - val detail: String?, - val badges: List -) - -private val qualityPattern = Regex("(2160p|4k|1440p|1080p|720p|480p|360p)", RegexOption.IGNORE_CASE) -private val tagPatterns = listOf( - Regex("\\bdolby\\s*vision\\b|\\bdv\\b", RegexOption.IGNORE_CASE) to "DV", - Regex("\\bhdr10\\+?|\\bhdr\\b", RegexOption.IGNORE_CASE) to "HDR", - Regex("\\bhevc\\b|\\bx265\\b|\\bh\\.?265\\b", RegexOption.IGNORE_CASE) to "HEVC", - Regex("\\bremux\\b", RegexOption.IGNORE_CASE) to "REMUX" -) - -private fun parseStreamCard(stream: DetailStreamUiModel): StreamCardInfo { - val lines = stream.title.lines().map { it.trim() }.filter { it.isNotBlank() } - val headline = lines.firstOrNull() ?: stream.addonName - val extra = lines.drop(1).joinToString(" · ") - val detail = listOf(stream.addonName, extra) - .filter { it.isNotBlank() && !headline.contains(it) } - .joinToString(" · ") - .takeIf { it.isNotBlank() } - - val badges = buildList { - qualityPattern.find(stream.title)?.let { match -> - val value = match.value.lowercase() - add(if (value == "2160p" || value == "4k") "4K" else value) - } - tagPatterns.forEach { (pattern, label) -> - if (pattern.containsMatchIn(stream.title)) add(label) - } - } - return StreamCardInfo(headline = headline, detail = detail, badges = badges) -} - @Composable private fun StreamCard(stream: DetailStreamUiModel, onClick: () -> Unit) { - val info = remember(stream) { parseStreamCard(stream) } + val headline = stream.name.trim().ifBlank { stream.addonName } + val body = stream.title.trim().takeIf { it.isNotBlank() && it != headline } var focused by remember { mutableStateOf(false) } val bg by animateColorAsState( targetValue = if (focused) Color.White.copy(alpha = 0.16f) else Color.White.copy(alpha = 0.05f), @@ -311,27 +290,19 @@ private fun StreamCard(stream: DetailStreamUiModel, onClick: () -> Unit) { verticalAlignment = Alignment.CenterVertically ) { Column(modifier = Modifier.weight(1f)) { - if (info.badges.isNotEmpty()) { - Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { - info.badges.forEach { badge -> QualityBadge(text = badge) } - } - Spacer(modifier = Modifier.height(8.dp)) - } Text( - text = info.headline, + text = headline, color = Color.White, fontSize = 14.sp, - fontWeight = FontWeight.SemiBold, - maxLines = 2, - overflow = TextOverflow.Ellipsis + lineHeight = 19.sp, + fontWeight = FontWeight.SemiBold ) - info.detail?.let { + body?.let { Text( text = it, - color = Color.White.copy(alpha = 0.55f), + color = Color.White.copy(alpha = 0.6f), fontSize = 12.sp, - maxLines = 2, - overflow = TextOverflow.Ellipsis, + lineHeight = 17.sp, modifier = Modifier.padding(top = 4.dp) ) } @@ -346,16 +317,3 @@ private fun StreamCard(stream: DetailStreamUiModel, onClick: () -> Unit) { } } -@Composable -private fun QualityBadge(text: String) { - Text( - text = text, - color = Color.White, - fontSize = 10.sp, - fontWeight = FontWeight.Bold, - letterSpacing = 0.5.sp, - modifier = Modifier - .background(Color.White.copy(alpha = 0.12f), RoundedCornerShape(4.dp)) - .padding(horizontal = 6.dp, vertical = 3.dp) - ) -}