Collapse Nest summary by default; tap title to expand
Move the LIVE chip and listener count out of the header strip and into the right side of the Stage card's title row (listeners first, then chip). The remaining summary now hides by default and toggles when the user taps the room title in the top bar, reclaiming vertical space for the stage and chat.
This commit is contained in:
+38
-62
@@ -20,14 +20,12 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.screen
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Badge
|
||||
import androidx.compose.material3.BadgedBox
|
||||
@@ -39,7 +37,6 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.PrimaryTabRow
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
@@ -52,11 +49,9 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
@@ -86,11 +81,13 @@ import kotlinx.coroutines.launch
|
||||
/**
|
||||
* Full-screen layout for [com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.activity.NestActivity]. Vertically split into:
|
||||
*
|
||||
* 1. TopAppBar — room title + overflow menu (Share, host's Edit).
|
||||
* 2. Header strip — LIVE chip, listener count, optional 1-line summary.
|
||||
* 3. Stage — vertical adaptive grid of host/speakers (height-bounded
|
||||
* so a 30-speaker room scrolls inside the strip and never pushes
|
||||
* the chat below the fold).
|
||||
* 1. TopAppBar — room title (tap to expand/collapse summary) + overflow
|
||||
* menu (Share, host's Edit).
|
||||
* 2. Optional summary strip — only visible when the user taps the title.
|
||||
* 3. Stage — vertical adaptive grid of host/speakers, with the LIVE
|
||||
* chip and listener count rendered in the stage header row
|
||||
* (height-bounded so a 30-speaker room scrolls inside the strip
|
||||
* and never pushes the chat below the fold).
|
||||
* 4. Tabs — `Chat | Audience · N | Hands · N` (Hands host-only,
|
||||
* shown only while there's at least one raised hand).
|
||||
* 5. Tab content — fills the remaining vertical space:
|
||||
@@ -122,6 +119,11 @@ internal fun NestFullScreen(
|
||||
var showHostLeaveConfirm by rememberSaveable { mutableStateOf(false) }
|
||||
var showReactionPicker by rememberSaveable { mutableStateOf(false) }
|
||||
var hostMenuTarget by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
// Summary is collapsed by default; tapping the top-bar title
|
||||
// toggles it so the user can preview the room description without
|
||||
// opening a separate sheet, then collapse again to reclaim vertical
|
||||
// room for the stage and chat.
|
||||
var summaryExpanded by rememberSaveable { mutableStateOf(false) }
|
||||
// Tab selection survives configuration changes and PIP transitions.
|
||||
// Stored as ordinal so rememberSaveable can persist it without a
|
||||
// custom Saver.
|
||||
@@ -187,6 +189,7 @@ internal fun NestFullScreen(
|
||||
onMinimize = onMinimize,
|
||||
onMenuOpen = { showHostMenu = true },
|
||||
onMenuDismiss = { showHostMenu = false },
|
||||
onTitleClick = { summaryExpanded = !summaryExpanded },
|
||||
onShare = {
|
||||
showHostMenu = false
|
||||
shareRoomNaddr(topBarContext, event)
|
||||
@@ -223,10 +226,9 @@ internal fun NestFullScreen(
|
||||
.fillMaxSize()
|
||||
.padding(padding),
|
||||
) {
|
||||
RoomHeaderStrip(
|
||||
summary = event.summary(),
|
||||
listenerCount = presences.size,
|
||||
)
|
||||
if (summaryExpanded) {
|
||||
RoomSummaryStrip(summary = event.summary())
|
||||
}
|
||||
// Self-cell tap toggles mic-mute when broadcasting; null
|
||||
// when not broadcasting so the avatar falls back to the
|
||||
// default no-op tap (rather than offering a button that
|
||||
@@ -245,6 +247,7 @@ internal fun NestFullScreen(
|
||||
onLongPressParticipant = onLongPressParticipant,
|
||||
myPubkey = myPubkey,
|
||||
onTapSelf = onTapSelf,
|
||||
listenerCount = presences.size,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
NestTabRow(
|
||||
@@ -366,55 +369,23 @@ internal fun NestFullScreen(
|
||||
}
|
||||
|
||||
/**
|
||||
* Header strip rendered between the TopAppBar and the stage grid.
|
||||
* Carries the LIVE chip + listener count and (when present) a single-
|
||||
* line ellipsised summary. Lives at the screen level rather than in
|
||||
* the TopAppBar so the chip + count have room to breathe and the
|
||||
* summary's typography matches the body, not the title.
|
||||
* Expanded summary strip shown between the TopAppBar and the stage grid
|
||||
* when the user taps the title. The LIVE chip and listener count have
|
||||
* moved to the stage card's header row, so this strip now carries only
|
||||
* the room description.
|
||||
*/
|
||||
@Composable
|
||||
private fun RoomHeaderStrip(
|
||||
summary: String?,
|
||||
listenerCount: Int,
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 4.dp)) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
LiveChip()
|
||||
Text(
|
||||
text = pluralStringResource(R.plurals.nest_listener_count, listenerCount, listenerCount),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
if (!summary.isNullOrBlank()) {
|
||||
Text(
|
||||
text = summary,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LiveChip() {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(50),
|
||||
color = MaterialTheme.colorScheme.errorContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onErrorContainer,
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.nest_live_chip),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp),
|
||||
)
|
||||
}
|
||||
private fun RoomSummaryStrip(summary: String?) {
|
||||
if (summary.isNullOrBlank()) return
|
||||
Text(
|
||||
text = summary,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 4.dp),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -491,6 +462,7 @@ private fun NestTopAppBar(
|
||||
onMinimize: () -> Unit,
|
||||
onMenuOpen: () -> Unit,
|
||||
onMenuDismiss: () -> Unit,
|
||||
onTitleClick: () -> Unit,
|
||||
onShare: () -> Unit,
|
||||
onEdit: () -> Unit,
|
||||
) {
|
||||
@@ -500,6 +472,10 @@ private fun NestTopAppBar(
|
||||
text = title,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onTitleClick),
|
||||
)
|
||||
},
|
||||
actions = {
|
||||
|
||||
+41
-6
@@ -34,6 +34,7 @@ import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
@@ -57,6 +58,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -137,6 +139,7 @@ internal fun StageGrid(
|
||||
onLongPressParticipant: ((String) -> Unit)? = null,
|
||||
myPubkey: String? = null,
|
||||
onTapSelf: (() -> Unit)? = null,
|
||||
listenerCount: Int = 0,
|
||||
) {
|
||||
// Float currently-speaking members to the top so the listener can
|
||||
// see who they're hearing without scrolling. sortedBy is stable in
|
||||
@@ -165,12 +168,29 @@ internal fun StageGrid(
|
||||
vertical = STAGE_CARD_PADDING_VERTICAL,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.nest_stage),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.nest_stage),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Text(
|
||||
text =
|
||||
pluralStringResource(
|
||||
R.plurals.nest_listener_count,
|
||||
listenerCount,
|
||||
listenerCount,
|
||||
),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
)
|
||||
LiveChip()
|
||||
}
|
||||
if (members.isEmpty()) {
|
||||
EmptyStageHint()
|
||||
return@Column
|
||||
@@ -203,6 +223,21 @@ internal fun StageGrid(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LiveChip() {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(50),
|
||||
color = MaterialTheme.colorScheme.errorContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onErrorContainer,
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.nest_live_chip),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Idle "Waiting for speakers…" placeholder for [StageGrid]. Pairs an
|
||||
* hourglass glyph with the existing copy so the empty state reads as
|
||||
|
||||
Reference in New Issue
Block a user