diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/BouncingIntentNav.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/BouncingIntentNav.kt index d441417a4..a345ed112 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/BouncingIntentNav.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/BouncingIntentNav.kt @@ -25,6 +25,7 @@ import android.content.Intent import android.net.Uri import androidx.compose.material3.DrawerState import androidx.compose.material3.DrawerValue +import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.ui.MainActivity import com.vitorpamplona.amethyst.ui.navigation.routes.Route @@ -98,6 +99,7 @@ class BouncingIntentNav( override fun navBottomBar(route: Route) = nav(route) + @Composable override fun canPop(): Boolean = false override fun popBack() { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/EmptyNav.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/EmptyNav.kt index 06c7edab7..94c3f86a8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/EmptyNav.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/EmptyNav.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.navigation.navs import androidx.compose.material3.DrawerState import androidx.compose.material3.DrawerValue +import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.ui.navigation.routes.Route import kotlinx.coroutines.CoroutineScope @@ -46,6 +47,7 @@ class EmptyNav : INav { override fun navBottomBar(route: Route) {} + @Composable override fun canPop(): Boolean = false override fun popBack() {} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/INav.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/INav.kt index 7ec762f8e..f7ceff737 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/INav.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/INav.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.amethyst.ui.navigation.navs import androidx.compose.material3.DrawerState +import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.ui.navigation.routes.Route import kotlinx.coroutines.CoroutineScope @@ -43,6 +44,7 @@ interface INav { fun navBottomBar(route: Route) + @Composable fun canPop(): Boolean fun popBack() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/Nav.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/Nav.kt index 3d2a94b4f..05ca546fd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/Nav.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/Nav.kt @@ -23,8 +23,12 @@ package com.vitorpamplona.amethyst.ui.navigation.navs import android.annotation.SuppressLint import androidx.compose.material3.DrawerState import androidx.compose.material3.DrawerValue +import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController +import androidx.navigation.compose.currentBackStackEntryAsState import com.vitorpamplona.amethyst.ui.navigation.BOTTOM_NAV_ROOT_KEY import com.vitorpamplona.amethyst.ui.navigation.isBottomNavRoot import com.vitorpamplona.amethyst.ui.navigation.routes.Route @@ -93,9 +97,20 @@ class Nav( } } + @Composable override fun canPop(): Boolean { - val current = controller.currentBackStackEntry ?: return false - if (current.isBottomNavRoot()) return false + // Observe the current entry as State so consumers recompose when the + // back stack settles after a navigation or back-swipe transition. + // A non-reactive read would leave a stale value behind: e.g. on + // back-swipe to Home, previousBackStackEntry is still the popping + // entry until the gesture finishes, and nothing would re-evaluate + // canPop afterwards. + val current by controller.currentBackStackEntryAsState() + val entry = current ?: return false + if (entry.isBottomNavRoot()) return false + // Home is the graph's start destination and nothing can sit below + // it, so a back arrow there is never meaningful. + if (entry.destination.id == controller.graph.findStartDestination().id) return false return controller.previousBackStackEntry != null } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/StreamingStatusFlags.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/StreamingStatusFlags.kt index 86b035d5d..f76ba3080 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/StreamingStatusFlags.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/StreamingStatusFlags.kt @@ -73,6 +73,22 @@ fun EndedFlag() { ) } +@Composable +fun PrivateFlag() { + Text( + text = stringRes(id = R.string.live_stream_private_tag), + color = Color.White, + fontWeight = FontWeight.Bold, + modifier = + remember { + Modifier + .clip(SmallBorder) + .background(Color.Black) + .padding(horizontal = 5.dp) + }, + ) +} + @Composable fun OfflineFlag() { Text( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/NestsFeedLoaded.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/NestsFeedLoaded.kt index 436fbf0cf..3bcc58b87 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/NestsFeedLoaded.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/NestsFeedLoaded.kt @@ -39,6 +39,7 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -70,11 +71,9 @@ import com.vitorpamplona.amethyst.ui.note.ZapReaction import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.EndedFlag import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.LiveFlag -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.OfflineFlag +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.PrivateFlag import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.ScheduledFlag -import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities.LiveActivityCard import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities.LoadParticipants -import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CheckIfVideoIsOnline import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.NestActivity import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.NestBridge import com.vitorpamplona.amethyst.ui.stringRes @@ -87,8 +86,12 @@ import com.vitorpamplona.amethyst.ui.theme.Size34dp import com.vitorpamplona.amethyst.ui.theme.Size35dp import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.StdPadding +import com.vitorpamplona.quartz.nip01Core.core.Address +import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent -import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.StatusTag +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.StatusTag +import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.ParticipantTag +import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @OptIn(ExperimentalFoundationApi::class) @@ -177,7 +180,8 @@ fun ObserveAndRenderSpace( val card by observeNoteAndMap(baseNote, accountViewModel) { when (val noteEvent = it.event) { is MeetingSpaceEvent -> { - LiveActivityCard( + println("AABBCC ${noteEvent.address()} ${noteEvent.status()}") + NestCard( id = noteEvent.address(), name = noteEvent.dTag(), cover = noteEvent.image()?.ifBlank { null }, @@ -185,13 +189,7 @@ fun ObserveAndRenderSpace( subject = noteEvent.room()?.ifBlank { null }, content = noteEvent.summary(), participants = noteEvent.participants().toImmutableList(), - status = - when (noteEvent.status()) { - com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.StatusTag.STATUS.OPEN -> StatusTag.STATUS.LIVE - com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.StatusTag.STATUS.PRIVATE -> StatusTag.STATUS.PLANNED - com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.StatusTag.STATUS.CLOSED -> StatusTag.STATUS.ENDED - else -> null - }, + status = noteEvent.status(), starts = null, ) } @@ -212,9 +210,22 @@ fun ObserveAndRenderSpace( } } +@Immutable +data class NestCard( + val id: Address?, + val name: String, + val cover: String?, + val media: String?, + val subject: String?, + val content: String?, + val participants: ImmutableList, + val status: StatusTag.STATUS?, + val starts: Long?, +) + @Composable fun RenderLiveSpacesThumb( - card: LiveActivityCard, + card: NestCard, baseNote: Note, accountViewModel: AccountViewModel, nav: INav, @@ -244,22 +255,11 @@ fun RenderLiveSpacesThumb( Box(Modifier.padding(10.dp)) { CrossfadeIfEnabled(targetState = card.status, accountViewModel = accountViewModel) { when (it) { - StatusTag.STATUS.LIVE -> { - val url = card.media - if (url.isNullOrBlank()) { - EndedFlag() - } else { - CheckIfVideoIsOnline(url, accountViewModel) { isOnline -> - if (isOnline) { - LiveFlag() - } else { - OfflineFlag() - } - } - } + StatusTag.STATUS.OPEN -> { + LiveFlag() } - StatusTag.STATUS.ENDED -> { + StatusTag.STATUS.CLOSED -> { EndedFlag() } @@ -267,6 +267,10 @@ fun RenderLiveSpacesThumb( ScheduledFlag(card.starts) } + StatusTag.STATUS.PRIVATE -> { + PrivateFlag() + } + else -> { EndedFlag() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/NestFullScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/NestFullScreen.kt index c7c85ee41..e2e7de16e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/NestFullScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/NestFullScreen.kt @@ -64,7 +64,6 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.viewmodels.NestUiState import com.vitorpamplona.amethyst.commons.viewmodels.NestViewModel -import com.vitorpamplona.amethyst.commons.viewmodels.RoomTheme import com.vitorpamplona.amethyst.commons.viewmodels.buildParticipantGrid import com.vitorpamplona.amethyst.ui.navigation.topbars.ShorterTopAppBar import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -108,240 +107,237 @@ internal fun NestFullScreen( onHandRaisedChange: (Boolean) -> Unit, onLeave: () -> Unit, ) { - val roomTheme = remember(event) { RoomTheme.from(event) } - NestThemedScope(theme = roomTheme, accountViewModel = accountViewModel) { - var showEditSheet by rememberSaveable { mutableStateOf(false) } - var showHostMenu by rememberSaveable { mutableStateOf(false) } - var showHostLeaveConfirm by rememberSaveable { mutableStateOf(false) } - var showReactionPicker by rememberSaveable { mutableStateOf(false) } - var hostMenuTarget by rememberSaveable { mutableStateOf(null) } - // Tab selection survives configuration changes and PIP transitions. - // Stored as ordinal so rememberSaveable can persist it without a - // custom Saver. - var selectedTabIndex by rememberSaveable { mutableStateOf(0) } + var showEditSheet by rememberSaveable { mutableStateOf(false) } + var showHostMenu by rememberSaveable { mutableStateOf(false) } + var showHostLeaveConfirm by rememberSaveable { mutableStateOf(false) } + var showReactionPicker by rememberSaveable { mutableStateOf(false) } + var hostMenuTarget by rememberSaveable { mutableStateOf(null) } + // Tab selection survives configuration changes and PIP transitions. + // Stored as ordinal so rememberSaveable can persist it without a + // custom Saver. + var selectedTabIndex by rememberSaveable { mutableStateOf(0) } - val isHost = accountViewModel.account.signer.pubKey == event.pubKey - val myPubkey = accountViewModel.account.signer.pubKey - val isOnStageMe = remember(onStage, myPubkey) { onStage.any { it.pubKey == myPubkey } } - val leaveScope = rememberCoroutineScope() - val topBarContext = LocalContext.current + val isHost = accountViewModel.account.signer.pubKey == event.pubKey + val myPubkey = accountViewModel.account.signer.pubKey + val isOnStageMe = remember(onStage, myPubkey) { onStage.any { it.pubKey == myPubkey } } + val leaveScope = rememberCoroutineScope() + val topBarContext = LocalContext.current - val presences by viewModel.presences.collectAsState() - val reactionsByPubkey by viewModel.recentReactions.collectAsState() - val speakerCatalogs by viewModel.speakerCatalogs.collectAsState() + val presences by viewModel.presences.collectAsState() + val reactionsByPubkey by viewModel.recentReactions.collectAsState() + val speakerCatalogs by viewModel.speakerCatalogs.collectAsState() - val onStageKeys = remember(onStage) { onStage.map { it.pubKey }.toSet() } - val participantGrid = - remember(event, presences) { - buildParticipantGrid( - participants = event.participants(), - presences = presences, - ) + val onStageKeys = remember(onStage) { onStage.map { it.pubKey }.toSet() } + val participantGrid = + remember(event, presences) { + buildParticipantGrid( + participants = event.participants(), + presences = presences, + ) + } + // Same logic HandRaiseQueueSection uses internally — duplicated + // here so the tab label can show a count without coupling the + // section to the screen. + val handsCount = + remember(presences, onStageKeys) { + presences.values.count { it.handRaised && it.pubkey !in onStageKeys } + } + val showHandsTab = isHost && handsCount > 0 + + // Tab roster changes when the Hands tab appears/disappears. + // If the user was on Hands and the queue empties, fall back to + // Chat — kept as a stable default rather than Audience because + // chat is the room's primary engagement surface. + val tabs = + remember(showHandsTab) { + buildList { + add(NestTab.Chat) + add(NestTab.Audience) + if (showHandsTab) add(NestTab.Hands) } - // Same logic HandRaiseQueueSection uses internally — duplicated - // here so the tab label can show a count without coupling the - // section to the screen. - val handsCount = - remember(presences, onStageKeys) { - presences.values.count { it.handRaised && it.pubkey !in onStageKeys } - } - val showHandsTab = isHost && handsCount > 0 + } + val effectiveTab = tabs.getOrNull(selectedTabIndex) ?: NestTab.Chat - // Tab roster changes when the Hands tab appears/disappears. - // If the user was on Hands and the queue empties, fall back to - // Chat — kept as a stable default rather than Audience because - // chat is the room's primary engagement surface. - val tabs = - remember(showHandsTab) { - buildList { - add(NestTab.Chat) - add(NestTab.Audience) - if (showHandsTab) add(NestTab.Hands) + // Long-press on any participant opens the host-actions sheet + // (T2 #2). The sheet itself gates which rows render based on + // host status. Skip self. + val onLongPressParticipant: ((String) -> Unit) = { target -> + if (target != myPubkey) hostMenuTarget = target + } + + Scaffold( + modifier = Modifier.fillMaxSize(), + containerColor = Color.Transparent, + topBar = { + NestTopAppBar( + title = event.room().orEmpty(), + isHost = isHost, + showHostMenu = showHostMenu, + onMenuOpen = { showHostMenu = true }, + onMenuDismiss = { showHostMenu = false }, + onShare = { + showHostMenu = false + shareRoomNaddr(topBarContext, event) + }, + onEdit = { + showHostMenu = false + showEditSheet = true + }, + ) + }, + bottomBar = { + NestActionBar( + viewModel = viewModel, + ui = ui, + isOnStage = isOnStageMe, + canBroadcast = viewModel.canBroadcast, + speakerPubkeyHex = myPubkey, + handRaised = handRaised, + onHandRaisedChange = onHandRaisedChange, + onShowReactionPicker = { showReactionPicker = true }, + onLeave = { + if (isHost) { + showHostLeaveConfirm = true + } else { + onLeave() + } + }, + ) + }, + ) { padding -> + Column( + modifier = + Modifier + .fillMaxSize() + .padding(padding), + ) { + RoomHeaderStrip( + summary = event.summary(), + listenerCount = presences.size, + ) + StageGrid( + members = participantGrid.onStage, + speakingNow = ui.speakingNow, + accountViewModel = accountViewModel, + reactionsByPubkey = reactionsByPubkey, + connectingSpeakers = ui.connectingSpeakers, + onLongPressParticipant = onLongPressParticipant, + modifier = Modifier.padding(horizontal = 16.dp), + ) + NestTabRow( + tabs = tabs, + selectedTab = effectiveTab, + audienceCount = participantGrid.audience.size, + handsCount = handsCount, + onSelect = { tab -> selectedTabIndex = tabs.indexOf(tab).coerceAtLeast(0) }, + modifier = Modifier.padding(top = 8.dp), + ) + when (effectiveTab) { + NestTab.Chat -> { + NestChatPanel( + event = event, + viewModel = viewModel, + accountViewModel = accountViewModel, + modifier = + Modifier + .weight(1f) + .padding(horizontal = 16.dp), + ) + } + + NestTab.Audience -> { + AudienceGrid( + members = participantGrid.audience, + accountViewModel = accountViewModel, + onLongPressParticipant = onLongPressParticipant, + modifier = + Modifier + .weight(1f) + .padding(horizontal = 12.dp), + ) + } + + NestTab.Hands -> { + HandRaiseQueueSection( + event = event, + viewModel = viewModel, + accountViewModel = accountViewModel, + modifier = + Modifier + .weight(1f) + .padding(horizontal = 16.dp), + ) } } - val effectiveTab = tabs.getOrNull(selectedTabIndex) ?: NestTab.Chat - - // Long-press on any participant opens the host-actions sheet - // (T2 #2). The sheet itself gates which rows render based on - // host status. Skip self. - val onLongPressParticipant: ((String) -> Unit) = { target -> - if (target != myPubkey) hostMenuTarget = target } + } - Scaffold( - modifier = Modifier.fillMaxSize(), - containerColor = Color.Transparent, - topBar = { - NestTopAppBar( - title = event.room().orEmpty(), - isHost = isHost, - showHostMenu = showHostMenu, - onMenuOpen = { showHostMenu = true }, - onMenuDismiss = { showHostMenu = false }, - onShare = { - showHostMenu = false - shareRoomNaddr(topBarContext, event) - }, - onEdit = { - showHostMenu = false - showEditSheet = true - }, - ) - }, - bottomBar = { - NestActionBar( - viewModel = viewModel, - ui = ui, - isOnStage = isOnStageMe, - canBroadcast = viewModel.canBroadcast, - speakerPubkeyHex = myPubkey, - handRaised = handRaised, - onHandRaisedChange = onHandRaisedChange, - onShowReactionPicker = { showReactionPicker = true }, - onLeave = { - if (isHost) { - showHostLeaveConfirm = true - } else { + // Sheets and dialogs render alongside the Scaffold so they + // cover the room content (including the action bar) when + // open. ParticipantHostActionsSheet stays outside the Column + // for the same reason — modal bottom sheets are not affected + // by parent layout. + hostMenuTarget?.let { target -> + ParticipantHostActionsSheet( + target = target, + event = event, + accountViewModel = accountViewModel, + onDismiss = { hostMenuTarget = null }, + catalog = speakerCatalogs[target], + ) + } + + if (showReactionPicker) { + RoomReactionPickerSheet( + onPick = { emoji -> accountViewModel.reactToOrDelete(roomNote, emoji) }, + onDismiss = { showReactionPicker = false }, + ) + } + + if (showHostLeaveConfirm) { + AlertDialog( + onDismissRequest = { showHostLeaveConfirm = false }, + title = { Text(stringRes(R.string.nest_leave_host_title)) }, + text = { Text(stringRes(R.string.nest_leave_host_body)) }, + confirmButton = { + TextButton( + colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error), + onClick = { + showHostLeaveConfirm = false + leaveScope.launch { + val ok = closeMeetingSpace(accountViewModel, event) + if (!ok) { + accountViewModel.toastManager.toast( + R.string.nests, + R.string.nest_leave_host_close_failed, + ) + } onLeave() } }, - ) - }, - ) { padding -> - Column( - modifier = - Modifier - .fillMaxSize() - .padding(padding), - ) { - RoomHeaderStrip( - summary = event.summary(), - listenerCount = presences.size, - ) - StageGrid( - members = participantGrid.onStage, - speakingNow = ui.speakingNow, - accountViewModel = accountViewModel, - reactionsByPubkey = reactionsByPubkey, - connectingSpeakers = ui.connectingSpeakers, - onLongPressParticipant = onLongPressParticipant, - modifier = Modifier.padding(horizontal = 16.dp), - ) - NestTabRow( - tabs = tabs, - selectedTab = effectiveTab, - audienceCount = participantGrid.audience.size, - handsCount = handsCount, - onSelect = { tab -> selectedTabIndex = tabs.indexOf(tab).coerceAtLeast(0) }, - modifier = Modifier.padding(top = 8.dp), - ) - when (effectiveTab) { - NestTab.Chat -> { - NestChatPanel( - event = event, - viewModel = viewModel, - accountViewModel = accountViewModel, - modifier = - Modifier - .weight(1f) - .padding(horizontal = 16.dp), - ) - } - - NestTab.Audience -> { - AudienceGrid( - members = participantGrid.audience, - accountViewModel = accountViewModel, - onLongPressParticipant = onLongPressParticipant, - modifier = - Modifier - .weight(1f) - .padding(horizontal = 12.dp), - ) - } - - NestTab.Hands -> { - HandRaiseQueueSection( - event = event, - viewModel = viewModel, - accountViewModel = accountViewModel, - modifier = - Modifier - .weight(1f) - .padding(horizontal = 16.dp), - ) - } + ) { + Text(stringRes(R.string.nest_leave_host_close)) } - } - } + }, + dismissButton = { + TextButton( + onClick = { + showHostLeaveConfirm = false + onLeave() + }, + ) { + Text(stringRes(R.string.nest_leave_host_just_leave)) + } + }, + ) + } - // Sheets and dialogs render alongside the Scaffold so they - // cover the room content (including the action bar) when - // open. ParticipantHostActionsSheet stays outside the Column - // for the same reason — modal bottom sheets are not affected - // by parent layout. - hostMenuTarget?.let { target -> - ParticipantHostActionsSheet( - target = target, - event = event, - accountViewModel = accountViewModel, - onDismiss = { hostMenuTarget = null }, - catalog = speakerCatalogs[target], - ) - } - - if (showReactionPicker) { - RoomReactionPickerSheet( - onPick = { emoji -> accountViewModel.reactToOrDelete(roomNote, emoji) }, - onDismiss = { showReactionPicker = false }, - ) - } - - if (showHostLeaveConfirm) { - AlertDialog( - onDismissRequest = { showHostLeaveConfirm = false }, - title = { Text(stringRes(R.string.nest_leave_host_title)) }, - text = { Text(stringRes(R.string.nest_leave_host_body)) }, - confirmButton = { - TextButton( - colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error), - onClick = { - showHostLeaveConfirm = false - leaveScope.launch { - val ok = closeMeetingSpace(accountViewModel, event) - if (!ok) { - accountViewModel.toastManager.toast( - R.string.nests, - R.string.nest_leave_host_close_failed, - ) - } - onLeave() - } - }, - ) { - Text(stringRes(R.string.nest_leave_host_close)) - } - }, - dismissButton = { - TextButton( - onClick = { - showHostLeaveConfirm = false - onLeave() - }, - ) { - Text(stringRes(R.string.nest_leave_host_just_leave)) - } - }, - ) - } - - if (showEditSheet) { - EditNestSheet( - accountViewModel = accountViewModel, - event = event, - onDismiss = { showEditSheet = false }, - ) - } + if (showEditSheet) { + EditNestSheet( + accountViewModel = accountViewModel, + event = event, + onDismiss = { showEditSheet = false }, + ) } } @@ -458,9 +454,8 @@ private enum class NestTab { * state lives at the screen level so the EditNestSheet can be * triggered from here and rendered alongside the Scaffold. * - * Container color is transparent so the themed background painted - * by [NestThemedScope]'s Surface (and any optional `bg` image) shows - * through cleanly. + * Container color is transparent so the surface beneath the + * Scaffold (set by the activity / parent theme) shows through. */ @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/amethyst/src/main/res/values-zh-rCN/strings.xml b/amethyst/src/main/res/values-zh-rCN/strings.xml index 6703efbc4..cf4fd906d 100644 --- a/amethyst/src/main/res/values-zh-rCN/strings.xml +++ b/amethyst/src/main/res/values-zh-rCN/strings.xml @@ -479,6 +479,94 @@ 无法发送信息 没有安装打开此链接的应用。 关闭这个聊天室吗? + 所有参与者将被断开连接。该房间会在源中显示为已关闭的。 + 关闭聊天室 + 选择未来的开始时间。 + 音频失败: %1$s + 此聊天室没有音频可用 + 交谈 + 停止交谈 + 离开舞台 + 麦克风静音 + 取消麦克风静音 + 正在开始广播… + 直播中 + 广播失败: %1$s + 在这个房间进行交谈需要麦克风权限。 + 打开设置 + 音频室 + 当聊天室开放时保持音频播放。 + 已连接音频聊天室 + 语音聊天室 — 在线 + 轻触返回。 + 停止 + 加入语音聊天室 + 主持人 + 举手 + 步入舞台 + 正在说话 + 作为观众加入 + 离开语音聊天室 + 离开 + 结束这个语音聊天室? + 您是主持人。关闭聊天室会断开所有人的连接。 如果您想稍后再回来,请选择\"仅离开\" — 该聊天室将在8小时不活动后自动关闭。 + 关闭聊天室 + 仅离开 + 不能将聊天室标记为已关闭。仍然离开——聊天室将自动关闭。 + + %1$d 位听众 + + 发送 + 说些什么… + 还没有消息。成为第一个说话的。 + 回应 + 回应 + 编辑聊天室 + 保存 + 关闭聊天室 + 聊天室操作 + 举手 + 批准 + 提升为演讲者 + 降级为听众 + 踢出 + 查看个人资料 + 发送打闪 + 不支持在聊天内分割打闪。打开个人资料屏发送打闪。 + 关注 + 取关 + 静音 + 取消静音 + 分享聊天室 + 启动空间 + 设置语音聊天室服务器 + 您尚未选择语音聊天服务器。添加 %1$s 到您的服务器列表并继续吗?\n\n您可以稍后在设置中更改此设置。 + 使用默认 + 取消 + 无法保存您的语音聊天服务器列表。请重试。 + 启动新的语音聊天室 + 聊天室名称 + 这是什么? + MoQ 服务 URL + Auth sidecar —— 默认 nostrnests.com + MoQ 中继端点 + WebTransport URL - 通常是同一个主机 + 封面图片URL (可选) + 取消 + 启动空间 + 计划稍后进行 + 选择开始时间 + 语音聊天服务器 + 选择 Amethyst 将你的语音聊天室发布到哪些 MoQ 主机服务器。启动新空间时第一个条目将用作默认。 + 您的服务器 + 保存为 10112 类型的可替换事件,以便其他客户端可以读取您的首选项。 + 添加语音聊天服务器 + 推荐的服务器 + 内置建议,您可轻触添加到列表中。 + 使用 Amethyst 默认值 + 添加服务器 + 移除服务器 + 暂无语音聊天服务器。在下方添加一个或选择推荐服务器。 视频 文章 私人书签 @@ -999,6 +1087,8 @@ 开放 私密 关闭 + 已计划 + 启动 %1$s 登出将删除你的本地信息。 请确保备份你的私钥以避免失去你的帐户。你想要继续吗? 已关注的标签 中继器 @@ -1797,6 +1887,7 @@ 个人资料徽章 中继黑名单 Blossom 服务器 + 语音聊天服务器 Blossom 认证 广播中继 书签列表 diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 6e657b75d..6c9149cdd 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -1207,6 +1207,7 @@ OFFLINE ENDED SCHEDULED + PRIVATE Livestream is Offline Livestream Ended