cleans up broadcasting tracker
This commit is contained in:
+1
-4
@@ -141,7 +141,4 @@ data class BroadcastEvent(
|
|||||||
data class BroadcastResult(
|
data class BroadcastResult(
|
||||||
val broadcast: BroadcastEvent,
|
val broadcast: BroadcastEvent,
|
||||||
val isSuccess: Boolean,
|
val isSuccess: Boolean,
|
||||||
) {
|
)
|
||||||
val successCount: Int get() = broadcast.successCount
|
|
||||||
val totalRelays: Int get() = broadcast.totalRelays
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -51,7 +51,9 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.service.broadcast.BroadcastEvent
|
import com.vitorpamplona.amethyst.service.broadcast.BroadcastEvent
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Banner showing active broadcast progress.
|
* Banner showing active broadcast progress.
|
||||||
@@ -102,7 +104,7 @@ private fun SingleBroadcastContent(broadcast: BroadcastEvent) {
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Sync,
|
imageVector = Icons.Default.Sync,
|
||||||
contentDescription = "Broadcasting",
|
contentDescription = stringRes(R.string.broadcasting),
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
modifier = Modifier.size(18.dp),
|
modifier = Modifier.size(18.dp),
|
||||||
)
|
)
|
||||||
@@ -113,7 +115,7 @@ private fun SingleBroadcastContent(broadcast: BroadcastEvent) {
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Broadcasting ${broadcast.eventName} (kind ${broadcast.kind})",
|
text = stringRes(R.string.broadcasting_name, broadcast.eventName),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
@@ -124,7 +126,7 @@ private fun SingleBroadcastContent(broadcast: BroadcastEvent) {
|
|||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "[${broadcast.results.size}/${broadcast.totalRelays}]",
|
text = stringRes(R.string.share_of, broadcast.results.size, broadcast.totalRelays),
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
@@ -160,7 +162,7 @@ private fun MultipleBroadcastsContent(broadcasts: List<BroadcastEvent>) {
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Sync,
|
imageVector = Icons.Default.Sync,
|
||||||
contentDescription = "Broadcasting",
|
contentDescription = stringRes(R.string.broadcasting),
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
modifier = Modifier.size(18.dp),
|
modifier = Modifier.size(18.dp),
|
||||||
)
|
)
|
||||||
@@ -171,13 +173,13 @@ private fun MultipleBroadcastsContent(broadcasts: List<BroadcastEvent>) {
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Broadcasting ${broadcasts.size} events...",
|
text = stringRes(R.string.broadcasting_number_events, broadcasts.size),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "[$completedResponses/$totalRelays]",
|
text = stringRes(R.string.share_of, completedResponses, totalRelays),
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
|
|||||||
+15
-42
@@ -70,42 +70,20 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.service.broadcast.BroadcastEvent
|
import com.vitorpamplona.amethyst.service.broadcast.BroadcastEvent
|
||||||
import com.vitorpamplona.amethyst.service.broadcast.BroadcastStatus
|
import com.vitorpamplona.amethyst.service.broadcast.BroadcastStatus
|
||||||
import com.vitorpamplona.amethyst.service.broadcast.RelayResult
|
import com.vitorpamplona.amethyst.service.broadcast.RelayResult
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
|
||||||
|
|
||||||
private const val MAX_EXPANDED_SECTIONS = 2
|
private const val MAX_EXPANDED_SECTIONS = 2
|
||||||
|
|
||||||
/**
|
|
||||||
* Modal bottom sheet showing detailed relay results for broadcasts.
|
|
||||||
* Shows up to 2 expanded sections, with a summary for additional broadcasts.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun BroadcastDetailsSheet(
|
|
||||||
broadcast: BroadcastEvent,
|
|
||||||
onDismiss: () -> Unit,
|
|
||||||
onRetryRelay: (NormalizedRelayUrl) -> Unit,
|
|
||||||
onRetryAllFailed: () -> Unit,
|
|
||||||
sheetState: SheetState =
|
|
||||||
rememberModalBottomSheetState(
|
|
||||||
skipPartiallyExpanded = true,
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
MultiBroadcastDetailsSheet(
|
|
||||||
broadcasts = listOf(broadcast),
|
|
||||||
onDismiss = onDismiss,
|
|
||||||
onRetryRelay = { _, relay -> onRetryRelay(relay) },
|
|
||||||
onRetryAllFailed = { onRetryAllFailed() },
|
|
||||||
sheetState = sheetState,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modal bottom sheet showing detailed relay results for multiple broadcasts.
|
* Modal bottom sheet showing detailed relay results for multiple broadcasts.
|
||||||
*/
|
*/
|
||||||
@@ -164,7 +142,7 @@ fun MultiBroadcastDetailsSheet(
|
|||||||
) {
|
) {
|
||||||
// Header
|
// Header
|
||||||
Text(
|
Text(
|
||||||
text = if (broadcasts.size == 1) "Broadcast Results" else "Broadcasts (${broadcasts.size})",
|
text = if (broadcasts.size == 1) stringRes(R.string.broadcast_results) else stringRes(R.string.broadcasts_number, broadcasts.size),
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
)
|
)
|
||||||
@@ -220,7 +198,7 @@ fun MultiBroadcastDetailsSheet(
|
|||||||
onClick = onDismiss,
|
onClick = onDismiss,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
Text("Dismiss")
|
Text(stringRes(R.string.dismiss))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,22 +237,17 @@ private fun BroadcastSection(
|
|||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
Text(
|
|
||||||
text = "kind ${broadcast.kind}",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "${broadcast.successCount}/${broadcast.totalRelays}",
|
text = stringResource(R.string.share_of, broadcast.successCount, broadcast.totalRelays),
|
||||||
style = MaterialTheme.typography.labelLarge,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
color = statusColor(broadcast.status),
|
color = statusColor(broadcast.status),
|
||||||
)
|
)
|
||||||
|
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = if (isExpanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore,
|
imageVector = if (isExpanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore,
|
||||||
contentDescription = if (isExpanded) "Collapse" else "Expand",
|
contentDescription = if (isExpanded) stringRes(R.string.collapse) else stringRes(R.string.expand),
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.size(24.dp),
|
modifier = Modifier.size(24.dp),
|
||||||
)
|
)
|
||||||
@@ -315,7 +288,7 @@ private fun BroadcastSection(
|
|||||||
modifier = Modifier.size(18.dp),
|
modifier = Modifier.size(18.dp),
|
||||||
)
|
)
|
||||||
Spacer(Modifier.width(4.dp))
|
Spacer(Modifier.width(4.dp))
|
||||||
Text("Retry Failed (${broadcast.failedRelays.size})")
|
Text(stringRes(R.string.retry_failed_number, broadcast.failedRelays.size))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,16 +322,16 @@ private fun OverflowSummary(broadcasts: List<BroadcastEvent>) {
|
|||||||
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Text(
|
||||||
text = "+${broadcasts.size} more broadcasts",
|
text = stringRes(R.string.more_broadcasts_number, broadcasts.size),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text =
|
text =
|
||||||
buildString {
|
buildString {
|
||||||
append("$totalSuccess/$totalRelays relays")
|
append(stringResource(R.string.share_of_relays, totalSuccess, totalRelays))
|
||||||
if (inProgressCount > 0) {
|
if (inProgressCount > 0) {
|
||||||
append(" ($inProgressCount in progress)")
|
append(stringResource(R.string.number_in_progress, inProgressCount))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
@@ -479,7 +452,7 @@ private fun RelayResultRow(
|
|||||||
|
|
||||||
is RelayResult.Timeout -> {
|
is RelayResult.Timeout -> {
|
||||||
Text(
|
Text(
|
||||||
text = "Timeout",
|
text = stringRes(R.string.timeout),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = warningColor,
|
color = warningColor,
|
||||||
)
|
)
|
||||||
@@ -487,7 +460,7 @@ private fun RelayResultRow(
|
|||||||
|
|
||||||
is RelayResult.Retrying -> {
|
is RelayResult.Retrying -> {
|
||||||
Text(
|
Text(
|
||||||
text = "Retrying...",
|
text = stringRes(R.string.retrying),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
@@ -498,14 +471,14 @@ private fun RelayResultRow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Retry button for failed relays (not when already retrying)
|
// Retry button for failed relays (not when already retrying)
|
||||||
if ((result is RelayResult.Error || result is RelayResult.Timeout) && result !is RelayResult.Retrying) {
|
if (result is RelayResult.Error || result is RelayResult.Timeout) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onRetry,
|
onClick = onRetry,
|
||||||
modifier = Modifier.size(32.dp),
|
modifier = Modifier.size(32.dp),
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Refresh,
|
imageVector = Icons.Default.Refresh,
|
||||||
contentDescription = "Retry",
|
contentDescription = stringResource(R.string.retry),
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
modifier = Modifier.size(18.dp),
|
modifier = Modifier.size(18.dp),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,145 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
* this software and associated documentation files (the "Software"), to deal in
|
|
||||||
* the Software without restriction, including without limitation the rights to use,
|
|
||||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
||||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
||||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
|
||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
package com.vitorpamplona.amethyst.ui.broadcast
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material3.Snackbar
|
|
||||||
import androidx.compose.material3.SnackbarData
|
|
||||||
import androidx.compose.material3.SnackbarDuration
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
|
||||||
import androidx.compose.material3.SnackbarResult
|
|
||||||
import androidx.compose.material3.SnackbarVisuals
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.vitorpamplona.amethyst.service.broadcast.BroadcastEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.broadcast.BroadcastStatus
|
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom snackbar visuals for broadcast results.
|
|
||||||
*/
|
|
||||||
data class BroadcastSnackbarVisuals(
|
|
||||||
val broadcast: BroadcastEvent,
|
|
||||||
override val actionLabel: String? = "View",
|
|
||||||
override val duration: SnackbarDuration = SnackbarDuration.Short,
|
|
||||||
override val withDismissAction: Boolean = true,
|
|
||||||
) : SnackbarVisuals {
|
|
||||||
override val message: String
|
|
||||||
get() =
|
|
||||||
when (broadcast.status) {
|
|
||||||
BroadcastStatus.SUCCESS -> "${broadcast.eventName} sent to ${broadcast.successCount}/${broadcast.totalRelays} relays"
|
|
||||||
BroadcastStatus.PARTIAL -> "${broadcast.eventName} sent to ${broadcast.successCount}/${broadcast.totalRelays} relays"
|
|
||||||
BroadcastStatus.FAILED -> "${broadcast.eventName} failed - 0/${broadcast.totalRelays} relays"
|
|
||||||
BroadcastStatus.IN_PROGRESS -> "Broadcasting ${broadcast.eventName}..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Snackbar host that listens to completed broadcasts and shows result notifications.
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun BroadcastSnackbarHost(
|
|
||||||
completedBroadcast: SharedFlow<BroadcastEvent>,
|
|
||||||
onViewDetails: (BroadcastEvent) -> Unit,
|
|
||||||
onRetry: (BroadcastEvent) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
|
||||||
|
|
||||||
LaunchedEffect(completedBroadcast) {
|
|
||||||
completedBroadcast.collect { broadcast ->
|
|
||||||
val visuals = BroadcastSnackbarVisuals(broadcast)
|
|
||||||
val result = snackbarHostState.showSnackbar(visuals)
|
|
||||||
|
|
||||||
when (result) {
|
|
||||||
SnackbarResult.ActionPerformed -> {
|
|
||||||
if (broadcast.status == BroadcastStatus.FAILED) {
|
|
||||||
onRetry(broadcast)
|
|
||||||
} else {
|
|
||||||
onViewDetails(broadcast)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SnackbarResult.Dismissed -> { /* No action */ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SnackbarHost(
|
|
||||||
hostState = snackbarHostState,
|
|
||||||
modifier = modifier.padding(bottom = 8.dp),
|
|
||||||
) { snackbarData ->
|
|
||||||
BroadcastSnackbarContent(snackbarData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun BroadcastSnackbarContent(snackbarData: SnackbarData) {
|
|
||||||
val visuals = snackbarData.visuals
|
|
||||||
val broadcastVisuals = visuals as? BroadcastSnackbarVisuals
|
|
||||||
|
|
||||||
val containerColor =
|
|
||||||
when (broadcastVisuals?.broadcast?.status) {
|
|
||||||
BroadcastStatus.FAILED -> Color(0xFF7F1D1D)
|
|
||||||
|
|
||||||
// Dark red
|
|
||||||
BroadcastStatus.PARTIAL -> Color(0xFF78350F)
|
|
||||||
|
|
||||||
// Dark amber
|
|
||||||
else -> Color(0xFF1E3A5F) // Dark blue (default)
|
|
||||||
}
|
|
||||||
|
|
||||||
val actionLabel =
|
|
||||||
when (broadcastVisuals?.broadcast?.status) {
|
|
||||||
BroadcastStatus.FAILED -> "Retry"
|
|
||||||
else -> "View"
|
|
||||||
}
|
|
||||||
|
|
||||||
Snackbar(
|
|
||||||
action = {
|
|
||||||
snackbarData.visuals.actionLabel?.let {
|
|
||||||
TextButton(onClick = { snackbarData.performAction() }) {
|
|
||||||
Text(actionLabel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissAction =
|
|
||||||
if (visuals.withDismissAction) {
|
|
||||||
{
|
|
||||||
TextButton(onClick = { snackbarData.dismiss() }) {
|
|
||||||
Text("Dismiss")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
},
|
|
||||||
containerColor = containerColor,
|
|
||||||
) {
|
|
||||||
Text(visuals.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+13
-7
@@ -33,6 +33,7 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -58,10 +59,12 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.service.broadcast.BroadcastEvent
|
import com.vitorpamplona.amethyst.service.broadcast.BroadcastEvent
|
||||||
import com.vitorpamplona.amethyst.service.broadcast.BroadcastStatus
|
import com.vitorpamplona.amethyst.service.broadcast.BroadcastStatus
|
||||||
import com.vitorpamplona.amethyst.service.broadcast.BroadcastTracker
|
import com.vitorpamplona.amethyst.service.broadcast.BroadcastTracker
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -80,7 +83,6 @@ fun DisplayBroadcastProgress(accountViewModel: AccountViewModel) {
|
|||||||
// Only show in COMPLETE UI mode
|
// Only show in COMPLETE UI mode
|
||||||
if (!accountViewModel.settings.isCompleteUIMode()) return
|
if (!accountViewModel.settings.isCompleteUIMode()) return
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val activeBroadcasts by accountViewModel.broadcastTracker.activeBroadcasts.collectAsStateWithLifecycle()
|
val activeBroadcasts by accountViewModel.broadcastTracker.activeBroadcasts.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
// State for completed broadcast (with auto-dismiss)
|
// State for completed broadcast (with auto-dismiss)
|
||||||
@@ -116,11 +118,13 @@ fun DisplayBroadcastProgress(accountViewModel: AccountViewModel) {
|
|||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.align(Alignment.BottomCenter)
|
.align(Alignment.BottomCenter)
|
||||||
.padding(bottom = 56.dp),
|
.navigationBarsPadding()
|
||||||
|
.padding(bottom = 50.dp),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Completed broadcast indicator (when no active broadcasts)
|
// Completed broadcast indicator (when no active broadcasts)
|
||||||
if (activeBroadcasts.isEmpty()) {
|
if (activeBroadcasts.isEmpty()) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
CompletedBroadcastIndicator(
|
CompletedBroadcastIndicator(
|
||||||
broadcast = completedBroadcast,
|
broadcast = completedBroadcast,
|
||||||
onTap = { broadcast ->
|
onTap = { broadcast ->
|
||||||
@@ -142,13 +146,15 @@ fun DisplayBroadcastProgress(accountViewModel: AccountViewModel) {
|
|||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.align(Alignment.BottomCenter)
|
.align(Alignment.BottomCenter)
|
||||||
.padding(bottom = 56.dp),
|
.navigationBarsPadding()
|
||||||
|
.padding(bottom = 50.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Details sheet - show all broadcasts when opened
|
// Details sheet - show all broadcasts when opened
|
||||||
if (selectedBroadcast != null) {
|
if (selectedBroadcast != null) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
// Combine active broadcasts with the selected/completed one
|
// Combine active broadcasts with the selected/completed one
|
||||||
val allBroadcasts =
|
val allBroadcasts =
|
||||||
(activeBroadcasts + listOfNotNull(completedBroadcast))
|
(activeBroadcasts + listOfNotNull(completedBroadcast))
|
||||||
@@ -238,7 +244,7 @@ private fun CompletedBroadcastIndicator(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "${b.eventName} sent",
|
text = stringRes(R.string.event_sent, b.eventName),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
@@ -247,7 +253,7 @@ private fun CompletedBroadcastIndicator(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "[${b.successCount}/${b.totalRelays}]",
|
text = stringRes(R.string.share_of, b.successCount, b.totalRelays),
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = iconTint,
|
color = iconTint,
|
||||||
)
|
)
|
||||||
@@ -255,7 +261,7 @@ private fun CompletedBroadcastIndicator(
|
|||||||
|
|
||||||
if (b.failedRelays.isNotEmpty()) {
|
if (b.failedRelays.isNotEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
text = "Tap to view details",
|
text = stringRes(R.string.tap_to_view_details),
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
@@ -270,7 +276,7 @@ private fun CompletedBroadcastIndicator(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Refresh,
|
imageVector = Icons.Default.Refresh,
|
||||||
contentDescription = "Retry failed",
|
contentDescription = stringRes(R.string.retry_failed),
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
modifier = Modifier.size(18.dp),
|
modifier = Modifier.size(18.dp),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1475,4 +1475,24 @@
|
|||||||
<string name="follow_set_delete">Delete List</string>
|
<string name="follow_set_delete">Delete List</string>
|
||||||
<string name="follow_pack_delete">Delete Pack</string>
|
<string name="follow_pack_delete">Delete Pack</string>
|
||||||
<string name="kinds">Kinds</string>
|
<string name="kinds">Kinds</string>
|
||||||
|
<string name="retry_failed">Retry failed</string>
|
||||||
|
<string name="tap_to_view_details">Tap to view details</string>
|
||||||
|
<string name="event_sent">%1$s sent</string>
|
||||||
|
<string name="broadcast_results">Broadcast Results</string>
|
||||||
|
<string name="broadcasts_number">Broadcasts (%1$d)</string>
|
||||||
|
<string name="collapse">Collapse</string>
|
||||||
|
<string name="expand">Expand</string>
|
||||||
|
<string name="retry_failed_number">Retry Failed (%1$d)</string>
|
||||||
|
<string name="more_broadcasts_number">+%1$d more broadcasts</string>
|
||||||
|
<string name="number_in_progress">" (%1$d in progress)"</string>
|
||||||
|
<string name="share_of_relays">%1$d/%2$d relays</string>
|
||||||
|
<string name="timeout">Timeout</string>
|
||||||
|
<string name="retrying">Retrying…</string>
|
||||||
|
<string name="retry">Retry</string>
|
||||||
|
<string name="view">View</string>
|
||||||
|
<string name="dismiss">Dismiss</string>
|
||||||
|
<string name="share_of">%1$d/%2$d</string>
|
||||||
|
<string name="broadcasting">Broadcasting</string>
|
||||||
|
<string name="broadcasting_name">Broadcasting %1$s</string>
|
||||||
|
<string name="broadcasting_number_events">Broadcasting %1$d events...</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user