mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-07-30 16:19:25 +00:00
feat: implement empty state handling for streams with descriptive messages
This commit is contained in:
parent
0a70df9e2d
commit
eaba1f9b19
3 changed files with 73 additions and 10 deletions
|
|
@ -36,11 +36,19 @@ data class AddonStreamGroup(
|
|||
val error: String? = null,
|
||||
)
|
||||
|
||||
enum class StreamsEmptyStateReason {
|
||||
NoAddonsInstalled,
|
||||
NoCompatibleAddons,
|
||||
NoStreamsFound,
|
||||
StreamFetchFailed,
|
||||
}
|
||||
|
||||
data class StreamsUiState(
|
||||
val groups: List<AddonStreamGroup> = emptyList(),
|
||||
val activeAddonIds: Set<String> = emptySet(),
|
||||
val selectedFilter: String? = null,
|
||||
val isAnyLoading: Boolean = false,
|
||||
val emptyStateReason: StreamsEmptyStateReason? = null,
|
||||
) {
|
||||
val filteredGroups: List<AddonStreamGroup>
|
||||
get() = if (selectedFilter == null) groups
|
||||
|
|
|
|||
|
|
@ -33,7 +33,16 @@ object StreamsRepository {
|
|||
activeJob?.cancel()
|
||||
_uiState.value = StreamsUiState()
|
||||
|
||||
val streamAddons = AddonRepository.uiState.value.addons
|
||||
val installedAddons = AddonRepository.uiState.value.addons
|
||||
if (installedAddons.isEmpty()) {
|
||||
_uiState.value = StreamsUiState(
|
||||
isAnyLoading = false,
|
||||
emptyStateReason = StreamsEmptyStateReason.NoAddonsInstalled,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val streamAddons = installedAddons
|
||||
.mapNotNull { it.manifest }
|
||||
.filter { manifest ->
|
||||
manifest.resources.any { resource ->
|
||||
|
|
@ -47,7 +56,10 @@ object StreamsRepository {
|
|||
log.d { "Found ${streamAddons.size} addons for stream type=$type id=$videoId" }
|
||||
|
||||
if (streamAddons.isEmpty()) {
|
||||
_uiState.value = StreamsUiState(isAnyLoading = false)
|
||||
_uiState.value = StreamsUiState(
|
||||
isAnyLoading = false,
|
||||
emptyStateReason = StreamsEmptyStateReason.NoCompatibleAddons,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -64,6 +76,7 @@ object StreamsRepository {
|
|||
groups = initialGroups,
|
||||
activeAddonIds = streamAddons.map { it.id }.toSet(),
|
||||
isAnyLoading = true,
|
||||
emptyStateReason = null,
|
||||
)
|
||||
|
||||
activeJob = scope.launch {
|
||||
|
|
@ -111,9 +124,11 @@ object StreamsRepository {
|
|||
val updated = current.groups.map { group ->
|
||||
if (group.addonId == result.addonId) result else group
|
||||
}
|
||||
val anyLoading = updated.any { it.isLoading }
|
||||
current.copy(
|
||||
groups = updated,
|
||||
isAnyLoading = updated.any { it.isLoading },
|
||||
isAnyLoading = anyLoading,
|
||||
emptyStateReason = updated.toEmptyStateReason(anyLoading),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -133,3 +148,15 @@ object StreamsRepository {
|
|||
private fun String.encodeForPath(): String =
|
||||
replace("%", "%25").replace(" ", "%20")
|
||||
}
|
||||
|
||||
private fun List<AddonStreamGroup>.toEmptyStateReason(anyLoading: Boolean): StreamsEmptyStateReason? {
|
||||
if (anyLoading || any { it.streams.isNotEmpty() }) {
|
||||
return null
|
||||
}
|
||||
|
||||
return if (isNotEmpty() && all { !it.error.isNullOrBlank() }) {
|
||||
StreamsEmptyStateReason.StreamFetchFailed
|
||||
} else {
|
||||
StreamsEmptyStateReason.NoStreamsFound
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -417,8 +417,8 @@ private fun StreamList(
|
|||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val filteredGroups = uiState.filteredGroups
|
||||
val hasGroups = filteredGroups.isNotEmpty()
|
||||
val hasAnyStreams = filteredGroups.any { it.streams.isNotEmpty() }
|
||||
val allLoading = filteredGroups.all { it.isLoading }
|
||||
val anyLoading = filteredGroups.any { it.isLoading }
|
||||
|
||||
LazyColumn(
|
||||
|
|
@ -430,15 +430,15 @@ private fun StreamList(
|
|||
verticalArrangement = Arrangement.spacedBy(0.dp),
|
||||
) {
|
||||
when {
|
||||
allLoading && !hasAnyStreams -> {
|
||||
hasGroups && anyLoading && !hasAnyStreams -> {
|
||||
item {
|
||||
LoadingStateBlock()
|
||||
}
|
||||
}
|
||||
|
||||
!hasAnyStreams && !anyLoading -> {
|
||||
!hasAnyStreams && !uiState.isAnyLoading -> {
|
||||
item {
|
||||
EmptyStateBlock()
|
||||
EmptyStateBlock(reason = uiState.emptyStateReason)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -667,7 +667,35 @@ private fun LoadingStateBlock(modifier: Modifier = Modifier) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun EmptyStateBlock(modifier: Modifier = Modifier) {
|
||||
private fun EmptyStateBlock(
|
||||
reason: StreamsEmptyStateReason?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val title: String
|
||||
val message: String
|
||||
|
||||
when (reason) {
|
||||
StreamsEmptyStateReason.NoAddonsInstalled -> {
|
||||
title = "No addons installed"
|
||||
message = "Install an addon first to load streams for this title."
|
||||
}
|
||||
|
||||
StreamsEmptyStateReason.NoCompatibleAddons -> {
|
||||
title = "No stream addon available"
|
||||
message = "Your installed addons do not provide streams for this type of title."
|
||||
}
|
||||
|
||||
StreamsEmptyStateReason.StreamFetchFailed -> {
|
||||
title = "Could not load streams"
|
||||
message = "The installed stream addons failed to return a valid stream response."
|
||||
}
|
||||
|
||||
StreamsEmptyStateReason.NoStreamsFound, null -> {
|
||||
title = "No streams found"
|
||||
message = "None of your installed addons returned streams for this title."
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -683,7 +711,7 @@ private fun EmptyStateBlock(modifier: Modifier = Modifier) {
|
|||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = "No streams found",
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodyLarge.copy(
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
|
|
@ -691,7 +719,7 @@ private fun EmptyStateBlock(modifier: Modifier = Modifier) {
|
|||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Text(
|
||||
text = "None of your installed addons returned streams for this title.",
|
||||
text = message,
|
||||
style = MaterialTheme.typography.bodySmall.copy(fontSize = 14.sp),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
|
||||
textAlign = TextAlign.Center,
|
||||
|
|
|
|||
Loading…
Reference in a new issue