Keep FAB position stable when AppBottomBar is hidden
AppBottomBar returns nothing on canPop entries (drawer pushes, in-app navigations), so the floating action button drops by the bar's height when navigating into a screen and rises again on a tab root. Reserve the 50dp gap at the FAB site via a fabBottomBarPadding(nav) modifier so the button stays at a consistent vertical position regardless of the bar's visibility on EmojiPacks, BookmarkGroups, and InterestSets. https://claude.ai/code/session_01QSk7CnjNtbcgS3XBD5WEZy
This commit is contained in:
+6
-1
@@ -54,6 +54,11 @@ import com.vitorpamplona.amethyst.ui.theme.Size10Modifier
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.Size24dp
|
import com.vitorpamplona.amethyst.ui.theme.Size24dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size27dp
|
import com.vitorpamplona.amethyst.ui.theme.Size27dp
|
||||||
|
|
||||||
|
/** Content height of the [AppBottomBar] (the 50.dp Column inside [RenderBottomMenu]),
|
||||||
|
* exclusive of the system navigation-bar inset. Used by FAB callers that want to
|
||||||
|
* reserve the same vertical space when the bar hides itself on canPop entries. */
|
||||||
|
val AppBottomBarHeight = 50.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppBottomBar(
|
fun AppBottomBar(
|
||||||
selectedRoute: Route?,
|
selectedRoute: Route?,
|
||||||
@@ -99,7 +104,7 @@ private fun RenderBottomMenu(
|
|||||||
.background(MaterialTheme.colorScheme.background)
|
.background(MaterialTheme.colorScheme.background)
|
||||||
.windowInsetsPadding(windowInsets)
|
.windowInsetsPadding(windowInsets)
|
||||||
.consumeWindowInsets(windowInsets)
|
.consumeWindowInsets(windowInsets)
|
||||||
.height(50.dp),
|
.height(AppBottomBarHeight),
|
||||||
) {
|
) {
|
||||||
HorizontalDivider(
|
HorizontalDivider(
|
||||||
thickness = DividerThickness,
|
thickness = DividerThickness,
|
||||||
|
|||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.navigation.bottombars
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reserves the visual space the [AppBottomBar] occupies on root tab entries so a
|
||||||
|
* FloatingActionButton stays at the same vertical position whether or not the bar is
|
||||||
|
* rendered. [AppBottomBar] hides itself on canPop entries (drawer pushes, in-app
|
||||||
|
* navigations) — without this padding the FAB drops by [AppBottomBarHeight] there.
|
||||||
|
*
|
||||||
|
* The system-navigation-bar inset is already handled by the surrounding Scaffold, so
|
||||||
|
* only the bar's content height needs to be reserved.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun Modifier.fabBottomBarPadding(nav: INav): Modifier = if (nav.canPop()) padding(bottom = AppBottomBarHeight) else this
|
||||||
+10
-2
@@ -40,6 +40,7 @@ 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.bottombars.AppBottomBar
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.bottombars.fabBottomBarPadding
|
||||||
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
|
||||||
@@ -130,7 +131,10 @@ fun ListOfBookmarkGroupsFeed(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
BookmarkGroupFab(onAddGroup = addBookmarkGroup)
|
BookmarkGroupFab(
|
||||||
|
onAddGroup = addBookmarkGroup,
|
||||||
|
modifier = Modifier.fabBottomBarPadding(nav),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
Column(
|
||||||
@@ -160,7 +164,10 @@ fun ListOfBookmarkGroupsFeed(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BookmarkGroupFab(onAddGroup: () -> Unit) {
|
fun BookmarkGroupFab(
|
||||||
|
onAddGroup: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
text = {
|
text = {
|
||||||
Text(text = stringRes(R.string.follow_set_create_btn_label))
|
Text(text = stringRes(R.string.follow_set_create_btn_label))
|
||||||
@@ -174,5 +181,6 @@ fun BookmarkGroupFab(onAddGroup: () -> Unit) {
|
|||||||
onClick = onAddGroup,
|
onClick = onAddGroup,
|
||||||
shape = CircleShape,
|
shape = CircleShape,
|
||||||
containerColor = MaterialTheme.colorScheme.primary,
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-2
@@ -62,6 +62,7 @@ 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.bottombars.AppBottomBar
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.bottombars.fabBottomBarPadding
|
||||||
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
|
||||||
@@ -126,7 +127,10 @@ fun ListOfEmojiPacksFeed(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
EmojiPackFab(onAddPack = addEmojiPack)
|
EmojiPackFab(
|
||||||
|
onAddPack = addEmojiPack,
|
||||||
|
modifier = Modifier.fabBottomBarPadding(nav),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
Column(
|
||||||
@@ -308,7 +312,10 @@ private fun MyEmojiListRow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun EmojiPackFab(onAddPack: () -> Unit) {
|
fun EmojiPackFab(
|
||||||
|
onAddPack: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
text = {
|
text = {
|
||||||
Text(text = stringRes(R.string.new_emoji_pack))
|
Text(text = stringRes(R.string.new_emoji_pack))
|
||||||
@@ -322,5 +329,6 @@ fun EmojiPackFab(onAddPack: () -> Unit) {
|
|||||||
onClick = onAddPack,
|
onClick = onAddPack,
|
||||||
shape = CircleShape,
|
shape = CircleShape,
|
||||||
containerColor = MaterialTheme.colorScheme.primary,
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-2
@@ -48,6 +48,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.MaterialSymbols
|
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
|
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.bottombars.fabBottomBarPadding
|
||||||
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
|
||||||
@@ -82,7 +83,10 @@ fun ListOfInterestSetsScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
InterestSetFab(onAdd = { nav.nav(Route.InterestSetMetadataEdit()) })
|
InterestSetFab(
|
||||||
|
onAdd = { nav.nav(Route.InterestSetMetadataEdit()) },
|
||||||
|
modifier = Modifier.fabBottomBarPadding(nav),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
Column(
|
||||||
@@ -155,7 +159,10 @@ private fun EmptyInterestSets() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun InterestSetFab(onAdd: () -> Unit) {
|
fun InterestSetFab(
|
||||||
|
onAdd: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
text = {
|
text = {
|
||||||
Text(text = stringRes(R.string.interest_set_create_btn_label))
|
Text(text = stringRes(R.string.interest_set_create_btn_label))
|
||||||
@@ -169,5 +176,6 @@ fun InterestSetFab(onAdd: () -> Unit) {
|
|||||||
onClick = onAdd,
|
onClick = onAdd,
|
||||||
shape = CircleShape,
|
shape = CircleShape,
|
||||||
containerColor = MaterialTheme.colorScheme.primary,
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user