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