mirror of
https://github.com/LiveFastEatTrashRaccoon/RaccoonForLemmy.git
synced 2025-02-08 16:58:40 +01:00
feat: configurable bottom navigation • part 1 (#1111)
This commit is contained in:
parent
3a1b04d010
commit
e8199ed40f
@ -19,6 +19,10 @@ sealed interface TabNavigationSection {
|
||||
data object Profile : TabNavigationSection
|
||||
|
||||
data object Inbox : TabNavigationSection
|
||||
|
||||
data object Settings : TabNavigationSection
|
||||
|
||||
data object Bookmarks : TabNavigationSection
|
||||
}
|
||||
|
||||
sealed interface ComposeEvent {
|
||||
|
@ -0,0 +1,31 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.settings
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import cafe.adriel.voyager.navigator.tab.Tab
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.settings.main.SettingsScreen
|
||||
|
||||
object SettingsTab : Tab {
|
||||
override val options: TabOptions
|
||||
@Composable
|
||||
get() {
|
||||
val icon = rememberVectorPainter(Icons.Default.Settings)
|
||||
val title = LocalStrings.current.navigationSettings
|
||||
|
||||
return TabOptions(
|
||||
index = 4u,
|
||||
title = title,
|
||||
icon = icon,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
Navigator(SettingsScreen())
|
||||
}
|
||||
}
|
@ -33,8 +33,9 @@ kotlin {
|
||||
dependencies {
|
||||
implementation(compose.runtime)
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.material3)
|
||||
implementation(compose.material)
|
||||
implementation(compose.material3)
|
||||
implementation(compose.materialIconsExtended)
|
||||
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
|
||||
implementation(compose.components.resources)
|
||||
|
||||
|
@ -2,6 +2,7 @@ package com.github.diegoberaldin.raccoonforlemmy
|
||||
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.TabNavigationSection
|
||||
|
||||
interface MainMviModel :
|
||||
MviModel<MainMviModel.Intent, MainMviModel.UiState, MainMviModel.Effect>,
|
||||
@ -18,6 +19,13 @@ interface MainMviModel :
|
||||
val bottomBarOffsetHeightPx: Float = 0f,
|
||||
val customProfileUrl: String? = null,
|
||||
val isLogged: Boolean = false,
|
||||
val bottomBarSections: List<TabNavigationSection> =
|
||||
listOf(
|
||||
TabNavigationSection.Home,
|
||||
TabNavigationSection.Explore,
|
||||
TabNavigationSection.Inbox,
|
||||
TabNavigationSection.Profile,
|
||||
),
|
||||
)
|
||||
|
||||
sealed interface Effect {
|
||||
|
@ -33,6 +33,7 @@ import androidx.compose.ui.unit.toSize
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.koin.getScreenModel
|
||||
import cafe.adriel.voyager.navigator.tab.CurrentTab
|
||||
import cafe.adriel.voyager.navigator.tab.Tab
|
||||
import cafe.adriel.voyager.navigator.tab.TabNavigator
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.di.getThemeRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings
|
||||
@ -42,13 +43,10 @@ import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getDrawerCoor
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigationCoordinator
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.home.ui.HomeTab
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.ui.InboxTab
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.ui.ProfileTab
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.ui.ExploreTab
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.settings.main.SettingsScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.ui.navigation.TabNavigationItem
|
||||
import com.github.diegoberaldin.raccoonforlemmy.navigation.TabNavigationItem
|
||||
import com.github.diegoberaldin.raccoonforlemmy.unit.manageaccounts.ManageAccountsScreen
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.drop
|
||||
@ -225,6 +223,37 @@ internal object MainScreen : Screen {
|
||||
uiFontSizeWorkaround = true
|
||||
}.launchIn(this)
|
||||
}
|
||||
|
||||
fun handleOnLongPress(
|
||||
tab: Tab,
|
||||
section: TabNavigationSection,
|
||||
) {
|
||||
when (section) {
|
||||
TabNavigationSection.Explore -> {
|
||||
tabNavigator.current = tab
|
||||
navigationCoordinator.setCurrentSection(TabNavigationSection.Explore)
|
||||
scope.launch {
|
||||
notificationCenter.send(NotificationCenterEvent.OpenSearchInExplore)
|
||||
}
|
||||
}
|
||||
|
||||
TabNavigationSection.Inbox -> {
|
||||
if (uiState.isLogged) {
|
||||
model.reduce(MainMviModel.Intent.ReadAllInbox)
|
||||
}
|
||||
}
|
||||
|
||||
TabNavigationSection.Profile -> {
|
||||
if (uiState.isLogged) {
|
||||
val screen = ManageAccountsScreen()
|
||||
navigationCoordinator.showBottomSheet(screen)
|
||||
}
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
if (uiFontSizeWorkaround) {
|
||||
NavigationBar(
|
||||
modifier =
|
||||
@ -248,45 +277,27 @@ internal object MainScreen : Screen {
|
||||
),
|
||||
tonalElevation = 0.dp,
|
||||
) {
|
||||
TabNavigationItem(
|
||||
tab = HomeTab,
|
||||
withText = titleVisible,
|
||||
)
|
||||
TabNavigationItem(
|
||||
tab = ExploreTab,
|
||||
withText = titleVisible,
|
||||
onLongPress =
|
||||
rememberCallback {
|
||||
tabNavigator.current = ExploreTab
|
||||
navigationCoordinator.setCurrentSection(TabNavigationSection.Explore)
|
||||
|
||||
scope.launch {
|
||||
notificationCenter.send(NotificationCenterEvent.OpenSearchInExplore)
|
||||
}
|
||||
},
|
||||
)
|
||||
TabNavigationItem(
|
||||
tab = InboxTab,
|
||||
withText = titleVisible,
|
||||
onLongPress =
|
||||
rememberCallback(model) {
|
||||
if (uiState.isLogged) {
|
||||
model.reduce(MainMviModel.Intent.ReadAllInbox)
|
||||
}
|
||||
},
|
||||
)
|
||||
TabNavigationItem(
|
||||
tab = ProfileTab,
|
||||
withText = titleVisible,
|
||||
customIconUrl = uiState.customProfileUrl,
|
||||
onLongPress =
|
||||
rememberCallback {
|
||||
if (uiState.isLogged) {
|
||||
val screen = ManageAccountsScreen()
|
||||
navigationCoordinator.showBottomSheet(screen)
|
||||
}
|
||||
},
|
||||
)
|
||||
for (section in uiState.bottomBarSections) {
|
||||
TabNavigationItem(
|
||||
section = section,
|
||||
withText = titleVisible,
|
||||
customIconUrl =
|
||||
if (section == TabNavigationSection.Profile) {
|
||||
uiState.customProfileUrl
|
||||
} else {
|
||||
null
|
||||
},
|
||||
onClick =
|
||||
rememberCallbackArgs { tab ->
|
||||
tabNavigator.current = tab
|
||||
navigationCoordinator.setCurrentSection(section)
|
||||
},
|
||||
onLongPress =
|
||||
rememberCallbackArgs { tab ->
|
||||
handleOnLongPress(tab, section)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,37 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.navigation
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Bookmark
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import cafe.adriel.voyager.navigator.tab.Tab
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings
|
||||
import com.github.diegoberaldin.raccoonforlemmy.unit.filteredcontents.FilteredContentsScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.unit.filteredcontents.FilteredContentsType
|
||||
import com.github.diegoberaldin.raccoonforlemmy.unit.filteredcontents.toInt
|
||||
|
||||
object BookmarksTab : Tab {
|
||||
override val options: TabOptions
|
||||
@Composable
|
||||
get() {
|
||||
val icon = rememberVectorPainter(Icons.Default.Bookmark)
|
||||
val title = LocalStrings.current.navigationDrawerTitleBookmarks
|
||||
|
||||
return TabOptions(
|
||||
index = 5u,
|
||||
title = title,
|
||||
icon = icon,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
Navigator(
|
||||
FilteredContentsScreen(
|
||||
type = FilteredContentsType.Bookmarks.toInt(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.ui.navigation
|
||||
package com.github.diegoberaldin.raccoonforlemmy.navigation
|
||||
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
@ -24,7 +24,6 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||
import cafe.adriel.voyager.navigator.tab.Tab
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.IconSize
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
||||
@ -32,39 +31,28 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CustomI
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.TabNavigationSection
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigationCoordinator
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.home.ui.HomeTab
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.ui.InboxTab
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.ui.ProfileTab
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.ui.ExploreTab
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.settings.SettingsTab
|
||||
|
||||
@Composable
|
||||
internal fun RowScope.TabNavigationItem(
|
||||
tab: Tab,
|
||||
section: TabNavigationSection,
|
||||
withText: Boolean = true,
|
||||
customIconUrl: String? = null,
|
||||
onLongPress: (() -> Unit)? = null,
|
||||
onClick: ((Tab) -> Unit)? = null,
|
||||
onLongPress: ((Tab) -> Unit)? = null,
|
||||
) {
|
||||
val tabNavigator = LocalTabNavigator.current
|
||||
val navigationCoordinator = remember { getNavigationCoordinator() }
|
||||
val unread by navigationCoordinator.inboxUnread.collectAsState()
|
||||
val color =
|
||||
if (tabNavigator.current == tab) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.outline
|
||||
}
|
||||
|
||||
val currentSection by navigationCoordinator.currentSection.collectAsState()
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val tab = section.toTab()
|
||||
|
||||
fun handleClick() {
|
||||
tabNavigator.current = tab
|
||||
val section =
|
||||
when (tab) {
|
||||
ExploreTab -> TabNavigationSection.Explore
|
||||
ProfileTab -> TabNavigationSection.Profile
|
||||
InboxTab -> TabNavigationSection.Inbox
|
||||
else -> TabNavigationSection.Home
|
||||
}
|
||||
navigationCoordinator.setCurrentSection(section)
|
||||
onClick?.invoke(tab)
|
||||
}
|
||||
|
||||
val pointerInputModifier =
|
||||
@ -80,14 +68,14 @@ internal fun RowScope.TabNavigationItem(
|
||||
handleClick()
|
||||
},
|
||||
onLongPress = {
|
||||
onLongPress?.invoke()
|
||||
onLongPress?.invoke(tab)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
NavigationBarItem(
|
||||
onClick = ::handleClick,
|
||||
selected = tabNavigator.current == tab,
|
||||
selected = section == currentSection,
|
||||
interactionSource = interactionSource,
|
||||
icon = {
|
||||
val content = @Composable {
|
||||
@ -107,7 +95,6 @@ internal fun RowScope.TabNavigationItem(
|
||||
modifier = pointerInputModifier,
|
||||
painter = tab.options.icon ?: rememberVectorPainter(Icons.Default.Home),
|
||||
contentDescription = null,
|
||||
tint = color,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -141,7 +128,6 @@ internal fun RowScope.TabNavigationItem(
|
||||
Text(
|
||||
modifier = Modifier,
|
||||
text = tab.options.title,
|
||||
color = color,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
@ -149,3 +135,13 @@ internal fun RowScope.TabNavigationItem(
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun TabNavigationSection.toTab(): Tab =
|
||||
when (this) {
|
||||
TabNavigationSection.Explore -> ExploreTab
|
||||
TabNavigationSection.Profile -> ProfileTab
|
||||
TabNavigationSection.Inbox -> InboxTab
|
||||
TabNavigationSection.Settings -> SettingsTab
|
||||
TabNavigationSection.Bookmarks -> BookmarksTab
|
||||
else -> HomeTab
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user