feat(nav): add bottom bar to remaining tab-eligible screens

The bottom-nav settings let users place any of these routes in the bar,
but the screens themselves never rendered AppBottomBar — so even when
configured as a tab, the bar was missing on Profile, Wallet, Lists,
BookmarkGroups, WebBookmarks, Drafts, InterestSets, EmojiPacks,
BrowseEmojiSets, and AllSettings.

AppBottomBar self-hides via nav.canPop(), so adding the bar is safe on
every entry: it appears only when the screen is the tab root, hidden
otherwise (drawer / deep link).

Same-tab tap scrolls the screen's main list to the top, mirroring the
existing tab behavior. State is hoisted as needed:
- WebBookmarks, Drafts, BrowseEmojiSets: feedState.sendToTop()
- Lists, BookmarkGroups, EmojiPacks: hoisted LazyListState/LazyGridState
  through the feed-view helper, animateScrollTo(0)
- Wallet, InterestSets: hoisted LazyListState locally
- AllSettings: hoisted ScrollState (verticalScroll column)
- Profile: wrapped in Scaffold, hoisted ScrollState from RenderSurface

https://claude.ai/code/session_01PrirRcL7g8iX7vTqqLTkBS
This commit is contained in:
Claude
2026-04-27 20:35:56 +00:00
parent 759fd67ef5
commit baaee94555
12 changed files with 198 additions and 30 deletions
@@ -27,6 +27,7 @@ import androidx.compose.foundation.layout.Spacer
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.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
@@ -67,11 +68,12 @@ fun ListOfBookmarkGroupsFeedView(
onItemDescriptionChange: (bookmarkGroup: LabeledBookmarkList) -> Unit, onItemDescriptionChange: (bookmarkGroup: LabeledBookmarkList) -> Unit,
onItemClone: (bookmarkGroup: LabeledBookmarkList, customName: String?, customDesc: String?) -> Unit, onItemClone: (bookmarkGroup: LabeledBookmarkList, customName: String?, customDesc: String?) -> Unit,
onDeleteItem: (bookmarkGroup: LabeledBookmarkList) -> Unit, onDeleteItem: (bookmarkGroup: LabeledBookmarkList) -> Unit,
listState: LazyListState = rememberLazyListState(),
) { ) {
val bookmarkGroupFeedState by groupListFeedSource.collectAsStateWithLifecycle() val bookmarkGroupFeedState by groupListFeedSource.collectAsStateWithLifecycle()
LazyColumn( LazyColumn(
state = rememberLazyListState(), state = listState,
contentPadding = FeedPadding, contentPadding = FeedPadding,
) { ) {
item { item {
@@ -23,12 +23,14 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.list
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
@@ -37,6 +39,7 @@ import com.vitorpamplona.amethyst.model.nip51Lists.BookmarkListState
import com.vitorpamplona.amethyst.model.nip51Lists.OldBookmarkListState import com.vitorpamplona.amethyst.model.nip51Lists.OldBookmarkListState
import com.vitorpamplona.amethyst.model.nip51Lists.PinListState import com.vitorpamplona.amethyst.model.nip51Lists.PinListState
import com.vitorpamplona.amethyst.model.nip51Lists.labeledBookmarkLists.LabeledBookmarkList import com.vitorpamplona.amethyst.model.nip51Lists.labeledBookmarkLists.LabeledBookmarkList
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
@@ -44,6 +47,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.BookmarkType import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.BookmarkType
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
@Composable @Composable
fun ListOfBookmarkGroupsScreen( fun ListOfBookmarkGroupsScreen(
@@ -86,7 +90,8 @@ fun ListOfBookmarkGroupsScreen(
) )
} }
}, },
nav, accountViewModel = accountViewModel,
nav = nav,
) )
} }
@@ -105,12 +110,25 @@ fun ListOfBookmarkGroupsFeed(
changeBookmarkGroupDescription: (bookmarkGroup: LabeledBookmarkList) -> Unit, changeBookmarkGroupDescription: (bookmarkGroup: LabeledBookmarkList) -> Unit,
cloneBookmarkGroup: (bookmarkGroup: LabeledBookmarkList, customName: String?, customDesc: String?) -> Unit, cloneBookmarkGroup: (bookmarkGroup: LabeledBookmarkList, customName: String?, customDesc: String?) -> Unit,
deleteBookmarkGroup: (bookmarkGroup: LabeledBookmarkList) -> Unit, deleteBookmarkGroup: (bookmarkGroup: LabeledBookmarkList) -> Unit,
accountViewModel: AccountViewModel,
nav: INav, nav: INav,
) { ) {
val listState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
Scaffold( Scaffold(
topBar = { topBar = {
TopBarWithBackButton(caption = stringRes(R.string.bookmark_lists), nav) TopBarWithBackButton(caption = stringRes(R.string.bookmark_lists), nav)
}, },
bottomBar = {
AppBottomBar(Route.BookmarkGroups, nav, accountViewModel) { route ->
if (route == Route.BookmarkGroups) {
coroutineScope.launch { listState.animateScrollToItem(0) }
} else {
nav.navBottomBar(route)
}
}
},
floatingActionButton = { floatingActionButton = {
BookmarkGroupFab(onAddGroup = addBookmarkGroup) BookmarkGroupFab(onAddGroup = addBookmarkGroup)
}, },
@@ -135,6 +153,7 @@ fun ListOfBookmarkGroupsFeed(
onItemDescriptionChange = changeBookmarkGroupDescription, onItemDescriptionChange = changeBookmarkGroupDescription,
onItemClone = cloneBookmarkGroup, onItemClone = cloneBookmarkGroup,
onDeleteItem = deleteBookmarkGroup, onDeleteItem = deleteBookmarkGroup,
listState = listState,
) )
} }
} }
@@ -53,7 +53,9 @@ import com.vitorpamplona.amethyst.ui.feeds.ScrollStateKeys.DRAFTS
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
import com.vitorpamplona.amethyst.ui.layouts.rememberFeedContentPadding import com.vitorpamplona.amethyst.ui.layouts.rememberFeedContentPadding
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.topbars.ShorterTopAppBar import com.vitorpamplona.amethyst.ui.navigation.topbars.ShorterTopAppBar
import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.note.NoteCompose
@@ -145,6 +147,15 @@ private fun RenderDraftListScreen(
}, },
) )
}, },
bottomBar = {
AppBottomBar(Route.Drafts, nav, accountViewModel) { route ->
if (route == Route.Drafts) {
feedState.sendToTop()
} else {
nav.navBottomBar(route)
}
}
},
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
) { ) {
RefresheableBox(feedState) { RefresheableBox(feedState) {
@@ -49,6 +49,7 @@ import com.vitorpamplona.amethyst.ui.feeds.WatchScrollToTop
import com.vitorpamplona.amethyst.ui.feeds.rememberForeverLazyGridState import com.vitorpamplona.amethyst.ui.feeds.rememberForeverLazyGridState
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
import com.vitorpamplona.amethyst.ui.layouts.rememberFeedContentPadding import com.vitorpamplona.amethyst.ui.layouts.rememberFeedContentPadding
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -84,6 +85,15 @@ fun BrowseEmojiSetsScreen(
topBar = { topBar = {
BrowseEmojiSetsTopBar(accountViewModel, nav) BrowseEmojiSetsTopBar(accountViewModel, nav)
}, },
bottomBar = {
AppBottomBar(Route.BrowseEmojiSets, nav, accountViewModel) { route ->
if (route == Route.BrowseEmojiSets) {
feedContentState.sendToTop()
} else {
nav.navBottomBar(route)
}
}
},
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
) { ) {
RefresheableBox(feedContentState, true) { RefresheableBox(feedContentState, true) {
@@ -30,6 +30,7 @@ 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.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.lazy.grid.rememberLazyGridState
@@ -44,6 +45,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@@ -59,6 +61,7 @@ import com.vitorpamplona.amethyst.ui.components.ClickableBox
import com.vitorpamplona.amethyst.ui.components.M3ActionDialog import com.vitorpamplona.amethyst.ui.components.M3ActionDialog
import com.vitorpamplona.amethyst.ui.components.M3ActionRow import com.vitorpamplona.amethyst.ui.components.M3ActionRow
import com.vitorpamplona.amethyst.ui.components.M3ActionSection import com.vitorpamplona.amethyst.ui.components.M3ActionSection
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
@@ -70,6 +73,7 @@ import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.Size40Modifier import com.vitorpamplona.amethyst.ui.theme.Size40Modifier
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
@Composable @Composable
fun ListOfEmojiPacksScreen( fun ListOfEmojiPacksScreen(
@@ -88,7 +92,8 @@ fun ListOfEmojiPacksScreen(
accountViewModel.account.deleteOwnedEmojiPack(pack.identifier) accountViewModel.account.deleteOwnedEmojiPack(pack.identifier)
} }
}, },
nav, accountViewModel = accountViewModel,
nav = nav,
) )
} }
@@ -101,12 +106,25 @@ fun ListOfEmojiPacksFeed(
openEmojiPack: (OwnedEmojiPack) -> Unit, openEmojiPack: (OwnedEmojiPack) -> Unit,
editEmojiPack: (OwnedEmojiPack) -> Unit, editEmojiPack: (OwnedEmojiPack) -> Unit,
deleteEmojiPack: (OwnedEmojiPack) -> Unit, deleteEmojiPack: (OwnedEmojiPack) -> Unit,
accountViewModel: AccountViewModel,
nav: INav, nav: INav,
) { ) {
val gridState = rememberLazyGridState()
val coroutineScope = rememberCoroutineScope()
Scaffold( Scaffold(
topBar = { topBar = {
TopBarWithBackButton(caption = stringRes(R.string.emoji_packs_title), nav) TopBarWithBackButton(caption = stringRes(R.string.emoji_packs_title), nav)
}, },
bottomBar = {
AppBottomBar(Route.EmojiPacks, nav, accountViewModel) { route ->
if (route == Route.EmojiPacks) {
coroutineScope.launch { gridState.animateScrollToItem(0) }
} else {
nav.navBottomBar(route)
}
}
},
floatingActionButton = { floatingActionButton = {
EmojiPackFab(onAddPack = addEmojiPack) EmojiPackFab(onAddPack = addEmojiPack)
}, },
@@ -125,6 +143,7 @@ fun ListOfEmojiPacksFeed(
openItem = openEmojiPack, openItem = openEmojiPack,
editItem = editEmojiPack, editItem = editEmojiPack,
deleteItem = deleteEmojiPack, deleteItem = deleteEmojiPack,
gridState = gridState,
) )
} }
} }
@@ -138,6 +157,7 @@ fun ListOfEmojiPacksFeedView(
openItem: (OwnedEmojiPack) -> Unit, openItem: (OwnedEmojiPack) -> Unit,
editItem: (OwnedEmojiPack) -> Unit, editItem: (OwnedEmojiPack) -> Unit,
deleteItem: (OwnedEmojiPack) -> Unit, deleteItem: (OwnedEmojiPack) -> Unit,
gridState: LazyGridState = rememberLazyGridState(),
) { ) {
val feedState by listSource.collectAsStateWithLifecycle() val feedState by listSource.collectAsStateWithLifecycle()
val selectedPacks by selectedPacksFlow.collectAsStateWithLifecycle() val selectedPacks by selectedPacksFlow.collectAsStateWithLifecycle()
@@ -158,7 +178,7 @@ fun ListOfEmojiPacksFeedView(
} else { } else {
LazyVerticalGrid( LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 160.dp), columns = GridCells.Adaptive(minSize = 160.dp),
state = rememberLazyGridState(), state = gridState,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentPadding = contentPadding =
androidx.compose.foundation.layout androidx.compose.foundation.layout
@@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
@@ -37,6 +38,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@@ -45,6 +47,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
@@ -52,6 +55,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import kotlinx.coroutines.launch
@Composable @Composable
fun ListOfInterestSetsScreen( fun ListOfInterestSetsScreen(
@@ -61,10 +65,22 @@ fun ListOfInterestSetsScreen(
val sets by accountViewModel.account.interestSets.listFeedFlow val sets by accountViewModel.account.interestSets.listFeedFlow
.collectAsStateWithLifecycle() .collectAsStateWithLifecycle()
val listState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
Scaffold( Scaffold(
topBar = { topBar = {
TopBarWithBackButton(caption = stringRes(R.string.interest_sets_title), nav) TopBarWithBackButton(caption = stringRes(R.string.interest_sets_title), nav)
}, },
bottomBar = {
AppBottomBar(Route.InterestSets, nav, accountViewModel) { route ->
if (route == Route.InterestSets) {
coroutineScope.launch { listState.animateScrollToItem(0) }
} else {
nav.navBottomBar(route)
}
}
},
floatingActionButton = { floatingActionButton = {
InterestSetFab(onAdd = { nav.nav(Route.InterestSetMetadataEdit()) }) InterestSetFab(onAdd = { nav.nav(Route.InterestSetMetadataEdit()) })
}, },
@@ -80,6 +96,7 @@ fun ListOfInterestSetsScreen(
EmptyInterestSets() EmptyInterestSets()
} else { } else {
LazyColumn( LazyColumn(
state = listState,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentPadding = FeedPadding, contentPadding = FeedPadding,
) { ) {
@@ -27,6 +27,7 @@ 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.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
@@ -53,13 +54,14 @@ import com.vitorpamplona.amethyst.ui.theme.grayText
fun AllPeopleListFeedView( fun AllPeopleListFeedView(
peopleListModel: PeopleListViewModel, peopleListModel: PeopleListViewModel,
followPackModel: FollowPackViewModel, followPackModel: FollowPackViewModel,
listState: LazyListState = rememberLazyListState(),
nav: INav, nav: INav,
) { ) {
val peopleListFeedState by peopleListModel.listFlow().collectAsStateWithLifecycle() val peopleListFeedState by peopleListModel.listFlow().collectAsStateWithLifecycle()
val followPackFeedState by followPackModel.listFlow().collectAsStateWithLifecycle() val followPackFeedState by followPackModel.listFlow().collectAsStateWithLifecycle()
LazyColumn( LazyColumn(
state = rememberLazyListState(), state = listState,
contentPadding = FeedPadding, contentPadding = FeedPadding,
) { ) {
stickyHeader { stickyHeader {
@@ -24,21 +24,26 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
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.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.SpacedBy5dp import com.vitorpamplona.amethyst.ui.theme.SpacedBy5dp
import kotlinx.coroutines.launch
@Composable @Composable
fun ListOfPeopleListsScreen( fun ListOfPeopleListsScreen(
@@ -51,19 +56,32 @@ fun ListOfPeopleListsScreen(
val pack: FollowPackViewModel = viewModel() val pack: FollowPackViewModel = viewModel()
pack.init(accountViewModel) pack.init(accountViewModel)
ListOfPeopleListsScreen(list, pack, nav) ListOfPeopleListsScreen(list, pack, accountViewModel, nav)
} }
@Composable @Composable
fun ListOfPeopleListsScreen( fun ListOfPeopleListsScreen(
list: PeopleListViewModel, list: PeopleListViewModel,
pack: FollowPackViewModel, pack: FollowPackViewModel,
accountViewModel: AccountViewModel,
nav: INav, nav: INav,
) { ) {
val listState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
Scaffold( Scaffold(
topBar = { topBar = {
TopBarWithBackButton(stringRes(R.string.my_lists), nav) TopBarWithBackButton(stringRes(R.string.my_lists), nav)
}, },
bottomBar = {
AppBottomBar(Route.Lists, nav, accountViewModel) { route ->
if (route == Route.Lists) {
coroutineScope.launch { listState.animateScrollToItem(0) }
} else {
nav.navBottomBar(route)
}
}
},
) { paddingValues -> ) { paddingValues ->
Column( Column(
Modifier Modifier
@@ -72,7 +90,7 @@ fun ListOfPeopleListsScreen(
bottom = paddingValues.calculateBottomPadding(), bottom = paddingValues.calculateBottomPadding(),
).fillMaxHeight(), ).fillMaxHeight(),
) { ) {
AllPeopleListFeedView(list, pack, nav) AllPeopleListFeedView(list, pack, listState, nav)
} }
} }
} }
@@ -20,6 +20,7 @@
*/ */
package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -28,6 +29,7 @@ import androidx.compose.foundation.layout.fillMaxHeight
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.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.PagerState
@@ -36,6 +38,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SecondaryScrollableTabRow import androidx.compose.material3.SecondaryScrollableTabRow
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
@@ -62,7 +65,9 @@ import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.observeAccountIsHiddenUser import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.observeAccountIsHiddenUser
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.bookmarks.BookmarkTabHeader import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.bookmarks.BookmarkTabHeader
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.bookmarks.TabBookmarks import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.bookmarks.TabBookmarks
@@ -291,7 +296,26 @@ fun ProfileScreen(
UserProfileFilterAssemblerSubscription(baseUser, accountViewModel.dataSources().profile) UserProfileFilterAssemblerSubscription(baseUser, accountViewModel.dataSources().profile)
RenderSurface { tabRowModifier: Modifier, pagerModifier: Modifier -> val scrollState = rememberScrollState()
val coroutineScope = rememberCoroutineScope()
Scaffold(
bottomBar = {
AppBottomBar(
Route.Profile(accountViewModel.userProfile().pubkeyHex),
nav,
accountViewModel,
) { route ->
if (route is Route.Profile) {
coroutineScope.launch { scrollState.animateScrollTo(0) }
} else {
nav.navBottomBar(route)
}
}
},
) { padding ->
Box(modifier = Modifier.padding(padding)) {
RenderSurface(scrollState) { tabRowModifier: Modifier, pagerModifier: Modifier ->
RenderScreen( RenderScreen(
baseUser, baseUser,
tabRowModifier, tabRowModifier,
@@ -313,9 +337,14 @@ fun ProfileScreen(
) )
} }
} }
}
}
@Composable @Composable
private fun RenderSurface(content: @Composable (tabRowModifier: Modifier, pagerModifier: Modifier) -> Unit) { private fun RenderSurface(
scrollState: ScrollState,
content: @Composable (tabRowModifier: Modifier, pagerModifier: Modifier) -> Unit,
) {
Surface( Surface(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
color = MaterialTheme.colorScheme.background, color = MaterialTheme.colorScheme.background,
@@ -330,7 +359,6 @@ private fun RenderSurface(content: @Composable (tabRowModifier: Modifier, pagerM
.onSizeChanged { columnSize = it }, .onSizeChanged { columnSize = it },
) { ) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val scrollState = rememberScrollState()
val tabRowModifier = remember { Modifier.onSizeChanged { tabsSize = it } } val tabRowModifier = remember { Modifier.onSizeChanged { tabsSize = it } }
@@ -57,6 +57,7 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.Route
@@ -90,13 +91,23 @@ fun AllSettingsScreen(
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var showResetMarmotDialog by remember { mutableStateOf(false) } var showResetMarmotDialog by remember { mutableStateOf(false) }
var isResettingMarmot by remember { mutableStateOf(false) } var isResettingMarmot by remember { mutableStateOf(false) }
val scrollState = rememberScrollState()
Scaffold( Scaffold(
topBar = { topBar = {
TopBarWithBackButton(stringRes(id = R.string.settings), nav) TopBarWithBackButton(stringRes(id = R.string.settings), nav)
}, },
bottomBar = {
AppBottomBar(Route.AllSettings, nav, accountViewModel) { route ->
if (route == Route.AllSettings) {
scope.launch { scrollState.animateScrollTo(0) }
} else {
nav.navBottomBar(route)
}
}
},
) { padding -> ) { padding ->
Column(Modifier.padding(padding).verticalScroll(rememberScrollState())) { Column(Modifier.padding(padding).verticalScroll(scrollState)) {
SettingsSectionHeader(R.string.account_settings) SettingsSectionHeader(R.string.account_settings)
SettingsNavigationRow( SettingsNavigationRow(
title = R.string.relay_setup, title = R.string.relay_setup,
@@ -33,7 +33,9 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
@@ -55,6 +57,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -66,10 +69,12 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.Route
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.amethyst.ui.stringRes
import kotlinx.coroutines.launch
import java.text.NumberFormat import java.text.NumberFormat
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -82,6 +87,8 @@ fun WalletScreen(
walletViewModel.init(accountViewModel) walletViewModel.init(accountViewModel)
val hasWallet by walletViewModel.hasWalletSetup.collectAsState() val hasWallet by walletViewModel.hasWalletSetup.collectAsState()
val listState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
Scaffold( Scaffold(
topBar = { topBar = {
@@ -107,6 +114,15 @@ fun WalletScreen(
}, },
) )
}, },
bottomBar = {
AppBottomBar(Route.Wallet, nav, accountViewModel) { route ->
if (route == Route.Wallet) {
coroutineScope.launch { listState.animateScrollToItem(0) }
} else {
nav.navBottomBar(route)
}
}
},
) { padding -> ) { padding ->
if (!hasWallet) { if (!hasWallet) {
NoWalletSetup( NoWalletSetup(
@@ -117,6 +133,7 @@ fun WalletScreen(
MultiWalletHomeContent( MultiWalletHomeContent(
walletViewModel = walletViewModel, walletViewModel = walletViewModel,
modifier = Modifier.padding(padding), modifier = Modifier.padding(padding),
listState = listState,
nav = nav, nav = nav,
) )
} }
@@ -161,6 +178,7 @@ private fun NoWalletSetup(
private fun MultiWalletHomeContent( private fun MultiWalletHomeContent(
walletViewModel: WalletViewModel, walletViewModel: WalletViewModel,
modifier: Modifier, modifier: Modifier,
listState: LazyListState,
nav: INav, nav: INav,
) { ) {
val walletInfoList by walletViewModel.walletInfoList.collectAsState() val walletInfoList by walletViewModel.walletInfoList.collectAsState()
@@ -173,6 +191,7 @@ private fun MultiWalletHomeContent(
} }
LazyColumn( LazyColumn(
state = listState,
modifier = modifier =
modifier modifier
.fillMaxSize() .fillMaxSize()
@@ -77,7 +77,9 @@ import com.vitorpamplona.amethyst.ui.feeds.ScrollStateKeys
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
import com.vitorpamplona.amethyst.ui.layouts.rememberFeedContentPadding import com.vitorpamplona.amethyst.ui.layouts.rememberFeedContentPadding
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.topbars.ShorterTopAppBar import com.vitorpamplona.amethyst.ui.navigation.topbars.ShorterTopAppBar
import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState
@@ -138,6 +140,15 @@ private fun RenderWebBookmarksScreen(
}, },
) )
}, },
bottomBar = {
AppBottomBar(Route.WebBookmarks, nav, accountViewModel) { route ->
if (route == Route.WebBookmarks) {
feedState.sendToTop()
} else {
nav.navBottomBar(route)
}
}
},
floatingButton = { floatingButton = {
FloatingActionButton( FloatingActionButton(
onClick = { showAddDialog = true }, onClick = { showAddDialog = true },