feat: configurable bottom navigation • part 3 (#1117)

This commit is contained in:
Diego Beraldin 2024-07-12 14:12:56 +02:00 committed by GitHub
parent 29ecfe8e1c
commit 3f99e68764
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 1156 additions and 130 deletions

View File

@ -0,0 +1,84 @@
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import cafe.adriel.voyager.core.screen.Screen
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.BottomSheetHeader
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.core.navigation.toInt
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toReadableName
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.onClick
class SelectTabNavigationSectionBottomSheet(
private val values: List<TabNavigationSection>,
) : Screen {
@Composable
override fun Content() {
val navigationCoordinator = remember { getNavigationCoordinator() }
val notificationCenter = remember { getNotificationCenter() }
Column(
modifier =
Modifier
.windowInsetsPadding(WindowInsets.navigationBars)
.padding(
top = Spacing.s,
start = Spacing.s,
end = Spacing.s,
bottom = Spacing.m,
),
verticalArrangement = Arrangement.spacedBy(Spacing.s),
) {
BottomSheetHeader(LocalStrings.current.selectTabNavigationTitle)
Column(
modifier = Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(Spacing.xxs),
) {
for (value in values) {
Row(
modifier =
Modifier
.padding(
horizontal = Spacing.s,
vertical = Spacing.s,
).fillMaxWidth()
.onClick(
onClick = {
notificationCenter.send(
NotificationCenterEvent.TabNavigationSectionSelected(value.toInt()),
)
navigationCoordinator.hideBottomSheet()
},
),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = value.toReadableName(),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onBackground,
)
}
}
}
}
}
}

View File

@ -429,4 +429,6 @@ internal open class DefaultStrings : Strings {
override val settingsSubtitleOpenPostWebPageOnImageClick = override val settingsSubtitleOpenPostWebPageOnImageClick =
"If a post has an URL, open web page on image click" "If a post has an URL, open web page on image click"
override val settingsItemAlternateMarkdownRendering = "Enable alternate Markdown rendering" override val settingsItemAlternateMarkdownRendering = "Enable alternate Markdown rendering"
override val settingsItemConfigureBottomNavigationBar = "Configure navigation bar"
override val selectTabNavigationTitle = "Select a section"
} }

View File

@ -427,6 +427,8 @@ interface Strings {
val settingsItemOpenPostWebPageOnImageClick: String val settingsItemOpenPostWebPageOnImageClick: String
val settingsSubtitleOpenPostWebPageOnImageClick: String val settingsSubtitleOpenPostWebPageOnImageClick: String
val settingsItemAlternateMarkdownRendering: String val settingsItemAlternateMarkdownRendering: String
val settingsItemConfigureBottomNavigationBar: String
val selectTabNavigationTitle: String
} }
object Locales { object Locales {

View File

@ -42,6 +42,7 @@ kotlin {
implementation(libs.voyager.screenmodel) implementation(libs.voyager.screenmodel)
implementation(libs.voyager.koin) implementation(libs.voyager.koin)
implementation(projects.core.l10n)
implementation(projects.core.persistence) implementation(projects.core.persistence)
implementation(projects.core.preferences) implementation(projects.core.preferences)
implementation(projects.domain.lemmy.data) implementation(projects.domain.lemmy.data)

View File

@ -1,5 +1,6 @@
package com.github.diegoberaldin.raccoonforlemmy.core.navigation.di package com.github.diegoberaldin.raccoonforlemmy.core.navigation.di
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.BottomNavItemsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.DrawerCoordinator import com.github.diegoberaldin.raccoonforlemmy.core.navigation.DrawerCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.NavigationCoordinator import com.github.diegoberaldin.raccoonforlemmy.core.navigation.NavigationCoordinator
import org.koin.java.KoinJavaComponent.inject import org.koin.java.KoinJavaComponent.inject
@ -13,3 +14,8 @@ actual fun getDrawerCoordinator(): DrawerCoordinator {
val res: DrawerCoordinator by inject(DrawerCoordinator::class.java) val res: DrawerCoordinator by inject(DrawerCoordinator::class.java)
return res return res
} }
actual fun getBottomNavItemsRepository(): BottomNavItemsRepository {
val res: BottomNavItemsRepository by inject(BottomNavItemsRepository::class.java)
return res
}

View File

@ -79,8 +79,18 @@ class DefaultBottomNavItemsRepositoryTest {
runTest { runTest {
val otherAccountId = 1L val otherAccountId = 1L
val accountId = 2L val accountId = 2L
every { keyStore.get("BottomNavItemsRepository.$otherAccountId.items", any<List<String>>()) } returns ITEMS_IDS every {
every { keyStore.get("BottomNavItemsRepository.$accountId.items", any<List<String>>()) } returns emptyList() keyStore.get(
"BottomNavItemsRepository.$otherAccountId.items",
any<List<String>>(),
)
} returns ITEMS_IDS
every {
keyStore.get(
"BottomNavItemsRepository.$accountId.items",
any<List<String>>(),
)
} returns emptyList()
val res = sut.get(accountId) val res = sut.get(accountId)
@ -94,8 +104,18 @@ class DefaultBottomNavItemsRepositoryTest {
fun givenDataForOtherUser_whenGetForAnonymousAccount_thenResultAndInteractionsIsAsExpected() = fun givenDataForOtherUser_whenGetForAnonymousAccount_thenResultAndInteractionsIsAsExpected() =
runTest { runTest {
val otherAccountId = 1 val otherAccountId = 1
every { keyStore.get("BottomNavItemsRepository.$otherAccountId.items", any<List<String>>()) } returns ITEMS_IDS every {
every { keyStore.get("BottomNavItemsRepository.items", any<List<String>>()) } returns emptyList() keyStore.get(
"BottomNavItemsRepository.$otherAccountId.items",
any<List<String>>(),
)
} returns ITEMS_IDS
every {
keyStore.get(
"BottomNavItemsRepository.items",
any<List<String>>(),
)
} returns emptyList()
val res = sut.get(null) val res = sut.get(null)
@ -105,6 +125,28 @@ class DefaultBottomNavItemsRepositoryTest {
} }
} }
@Test
fun whenUpdateAnonymousUser_thenInteractionsAreAsExpected() =
runTest {
sut.update(accountId = null, items = ITEMS)
coVerify {
keyStore.save("BottomNavItemsRepository.items", ITEMS_IDS)
}
}
@Test
fun whenUpdateLoggedUser_thenInteractionsAreAsExpected() =
runTest {
val accountId = 1L
sut.update(accountId = accountId, items = ITEMS)
coVerify {
keyStore.save("BottomNavItemsRepository.1.items", ITEMS_IDS)
}
}
companion object { companion object {
private val ITEMS_IDS = listOf("0", "1", "3", "2", "4") private val ITEMS_IDS = listOf("0", "1", "3", "2", "4")
private val ITEMS = private val ITEMS =

View File

@ -11,7 +11,7 @@ internal class DefaultBottomNavItemsRepository(
override suspend fun get(accountId: Long?): List<TabNavigationSection> = override suspend fun get(accountId: Long?): List<TabNavigationSection> =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val key = getKey(accountId) val key = getKey(accountId)
val itemIds = keyStore.get(key, emptyList()) val itemIds = keyStore.get(key, emptyList()).mapNotNull { it.toIntOrNull() }
val res = itemIds.mapNotNull { it.toTabNavigationSection() }.takeUnless { it.isEmpty() } val res = itemIds.mapNotNull { it.toTabNavigationSection() }.takeUnless { it.isEmpty() }
res ?: BottomNavItemsRepository.DEFAULT_ITEMS res ?: BottomNavItemsRepository.DEFAULT_ITEMS
} }
@ -21,7 +21,7 @@ internal class DefaultBottomNavItemsRepository(
items: List<TabNavigationSection>, items: List<TabNavigationSection>,
) = withContext(Dispatchers.IO) { ) = withContext(Dispatchers.IO) {
val key = getKey(accountId) val key = getKey(accountId)
val itemIds = items.map { it.toTabNavigationId() } val itemIds = items.map { it.toInt().toString() }
keyStore.save(key, itemIds) keyStore.save(key, itemIds)
} }
@ -35,24 +35,3 @@ internal class DefaultBottomNavItemsRepository(
append(".items") append(".items")
} }
} }
private fun String.toTabNavigationSection(): TabNavigationSection? =
when (this) {
"0" -> TabNavigationSection.Home
"1" -> TabNavigationSection.Explore
"2" -> TabNavigationSection.Inbox
"3" -> TabNavigationSection.Profile
"4" -> TabNavigationSection.Settings
"5" -> TabNavigationSection.Bookmarks
else -> null
}
private fun TabNavigationSection.toTabNavigationId(): String =
when (this) {
TabNavigationSection.Home -> "0"
TabNavigationSection.Explore -> "1"
TabNavigationSection.Inbox -> "2"
TabNavigationSection.Profile -> "3"
TabNavigationSection.Settings -> "4"
TabNavigationSection.Bookmarks -> "5"
}

View File

@ -11,20 +11,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlin.time.Duration import kotlin.time.Duration
sealed interface TabNavigationSection {
data object Home : TabNavigationSection
data object Explore : TabNavigationSection
data object Profile : TabNavigationSection
data object Inbox : TabNavigationSection
data object Settings : TabNavigationSection
data object Bookmarks : TabNavigationSection
}
sealed interface ComposeEvent { sealed interface ComposeEvent {
data class WithUrl( data class WithUrl(
val url: String, val url: String,

View File

@ -0,0 +1,54 @@
package com.github.diegoberaldin.raccoonforlemmy.core.navigation
import androidx.compose.runtime.Composable
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings
sealed interface TabNavigationSection {
data object Home : TabNavigationSection
data object Explore : TabNavigationSection
data object Profile : TabNavigationSection
data object Inbox : TabNavigationSection
data object Settings : TabNavigationSection
data object Bookmarks : TabNavigationSection
}
@Composable
fun TabNavigationSection.toReadableName(): String =
when (this) {
TabNavigationSection.Bookmarks -> LocalStrings.current.navigationDrawerTitleBookmarks
TabNavigationSection.Explore -> LocalStrings.current.navigationSearch
TabNavigationSection.Home -> LocalStrings.current.navigationHome
TabNavigationSection.Inbox -> LocalStrings.current.navigationInbox
TabNavigationSection.Profile -> LocalStrings.current.navigationProfile
TabNavigationSection.Settings -> LocalStrings.current.navigationSettings
}
fun Int.toTabNavigationSection(): TabNavigationSection? =
when (this) {
0 -> TabNavigationSection.Home
1 -> TabNavigationSection.Explore
2 -> TabNavigationSection.Inbox
3 -> TabNavigationSection.Profile
4 -> TabNavigationSection.Settings
5 -> TabNavigationSection.Bookmarks
else -> null
}
fun TabNavigationSection.toInt(): Int =
when (this) {
TabNavigationSection.Home -> 0
TabNavigationSection.Explore -> 1
TabNavigationSection.Inbox -> 2
TabNavigationSection.Profile -> 3
TabNavigationSection.Settings -> 4
TabNavigationSection.Bookmarks -> 5
}
fun List<Int>.toTabNavigationSections(): List<TabNavigationSection> = mapNotNull { it.toTabNavigationSection() }
fun List<TabNavigationSection>.toInts(): List<Int> = map { it.toInt() }

View File

@ -1,8 +1,11 @@
package com.github.diegoberaldin.raccoonforlemmy.core.navigation.di package com.github.diegoberaldin.raccoonforlemmy.core.navigation.di
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.BottomNavItemsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.DrawerCoordinator import com.github.diegoberaldin.raccoonforlemmy.core.navigation.DrawerCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.NavigationCoordinator import com.github.diegoberaldin.raccoonforlemmy.core.navigation.NavigationCoordinator
expect fun getNavigationCoordinator(): NavigationCoordinator expect fun getNavigationCoordinator(): NavigationCoordinator
expect fun getDrawerCoordinator(): DrawerCoordinator expect fun getDrawerCoordinator(): DrawerCoordinator
expect fun getBottomNavItemsRepository(): BottomNavItemsRepository

View File

@ -1,5 +1,6 @@
package com.github.diegoberaldin.raccoonforlemmy.core.navigation.di package com.github.diegoberaldin.raccoonforlemmy.core.navigation.di
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.BottomNavItemsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.DrawerCoordinator import com.github.diegoberaldin.raccoonforlemmy.core.navigation.DrawerCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.NavigationCoordinator import com.github.diegoberaldin.raccoonforlemmy.core.navigation.NavigationCoordinator
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
@ -9,7 +10,10 @@ actual fun getNavigationCoordinator() = CoreNavigationHelper.navigationCoordinat
actual fun getDrawerCoordinator() = CoreNavigationHelper.drawerCoordinator actual fun getDrawerCoordinator() = CoreNavigationHelper.drawerCoordinator
actual fun getBottomNavItemsRepository() = CoreNavigationHelper.bottomNavItemsRepository
object CoreNavigationHelper : KoinComponent { object CoreNavigationHelper : KoinComponent {
val navigationCoordinator: NavigationCoordinator by inject() val navigationCoordinator: NavigationCoordinator by inject()
val drawerCoordinator: DrawerCoordinator by inject() val drawerCoordinator: DrawerCoordinator by inject()
val bottomNavItemsRepository: BottomNavItemsRepository by inject()
} }

View File

@ -245,4 +245,8 @@ sealed interface NotificationCenterEvent {
data object FavoritesUpdated : NotificationCenterEvent data object FavoritesUpdated : NotificationCenterEvent
data object OpenSearchInExplore : NotificationCenterEvent data object OpenSearchInExplore : NotificationCenterEvent
data class TabNavigationSectionSelected(
val sectionId: Int,
) : NotificationCenterEvent
} }

View File

@ -90,6 +90,15 @@ class DefaultSettingsRepositoryTest {
assertEquals(model, value) assertEquals(model, value)
} }
@Test
fun whenChangeCurrentBottomBarSections_thenValueIsUpdated() =
runTest {
val sectionIds = listOf(0, 1, 2)
sut.changeCurrentBottomBarSections(sectionIds)
val value = sut.currentBottomBarSections.value
assertEquals(sectionIds, value)
}
@Test @Test
fun whenCreateSettings_thenResultIsAsExpected() = fun whenCreateSettings_thenResultIsAsExpected() =
runTest { runTest {

View File

@ -13,6 +13,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.preferences.TemporaryKeySto
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO import kotlinx.coroutines.IO
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.minutes
@ -77,6 +78,7 @@ internal class DefaultSettingsRepository(
private val db = provider.getDatabase() private val db = provider.getDatabase()
override val currentSettings = MutableStateFlow(SettingsModel()) override val currentSettings = MutableStateFlow(SettingsModel())
override val currentBottomBarSections = MutableStateFlow<List<Int>>(emptyList())
override suspend fun createSettings( override suspend fun createSettings(
settings: SettingsModel, settings: SettingsModel,
@ -375,7 +377,10 @@ internal class DefaultSettingsRepository(
settings.defaultExploreResultType, settings.defaultExploreResultType,
) )
keyStore.save(KeyStoreKeys.RANDOM_THEME_COLOR, settings.randomThemeColor) keyStore.save(KeyStoreKeys.RANDOM_THEME_COLOR, settings.randomThemeColor)
keyStore.save(KeyStoreKeys.OPEN_POST_WEB_PAGE_ON_IMAGE_CLICK, settings.openPostWebPageOnImageClick) keyStore.save(
KeyStoreKeys.OPEN_POST_WEB_PAGE_ON_IMAGE_CLICK,
settings.openPostWebPageOnImageClick,
)
} else { } else {
db.settingsQueries.update( db.settingsQueries.update(
theme = settings.theme?.toLong(), theme = settings.theme?.toLong(),
@ -466,7 +471,11 @@ internal class DefaultSettingsRepository(
} }
override fun changeCurrentSettings(settings: SettingsModel) { override fun changeCurrentSettings(settings: SettingsModel) {
currentSettings.value = settings currentSettings.update { settings }
}
override fun changeCurrentBottomBarSections(sectionIds: List<Int>) {
currentBottomBarSections.update { sectionIds }
} }
} }

View File

@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.StateFlow
@Stable @Stable
interface SettingsRepository { interface SettingsRepository {
val currentSettings: StateFlow<SettingsModel> val currentSettings: StateFlow<SettingsModel>
val currentBottomBarSections: StateFlow<List<Int>>
suspend fun createSettings( suspend fun createSettings(
settings: SettingsModel, settings: SettingsModel,
@ -21,4 +22,6 @@ interface SettingsRepository {
) )
fun changeCurrentSettings(settings: SettingsModel) fun changeCurrentSettings(settings: SettingsModel)
fun changeCurrentBottomBarSections(sectionIds: List<Int>)
} }

View File

@ -1,3 +1,3 @@
{ {
"alternateMarkdownRenderingSettingsItemEnabled": true "alternateMarkdownRenderingSettingsItemEnabled": false
} }

View File

@ -40,6 +40,7 @@ kotlin {
implementation(projects.core.utils) implementation(projects.core.utils)
implementation(projects.core.appearance) implementation(projects.core.appearance)
implementation(projects.core.persistence) implementation(projects.core.persistence)
implementation(projects.core.navigation)
implementation(projects.core.notifications) implementation(projects.core.notifications)
implementation(projects.domain.lemmy.repository) implementation(projects.domain.lemmy.repository)
implementation(projects.domain.lemmy.data) implementation(projects.domain.lemmy.data)
@ -58,8 +59,14 @@ kotlin {
android { android {
namespace = "com.github.diegoberaldin.raccoonforlemmy.domain.identity" namespace = "com.github.diegoberaldin.raccoonforlemmy.domain.identity"
compileSdk = libs.versions.android.targetSdk.get().toInt() compileSdk =
libs.versions.android.targetSdk
.get()
.toInt()
defaultConfig { defaultConfig {
minSdk = libs.versions.android.minSdk.get().toInt() minSdk =
libs.versions.android.minSdk
.get()
.toInt()
} }
} }

View File

@ -1,6 +1,7 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.identity.usecase package com.github.diegoberaldin.raccoonforlemmy.domain.identity.usecase
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.LoginResponse import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.LoginResponse
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.BottomNavItemsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.AccountModel import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.AccountModel
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.SettingsModel import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.SettingsModel
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.AccountRepository import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.AccountRepository
@ -40,6 +41,10 @@ class DefaultLoginUseCaseTest {
private val communitySortRepository = mockk<CommunitySortRepository>(relaxUnitFun = true) private val communitySortRepository = mockk<CommunitySortRepository>(relaxUnitFun = true)
private val communityPreferredLanguageRepository = private val communityPreferredLanguageRepository =
mockk<CommunityPreferredLanguageRepository>(relaxUnitFun = true) mockk<CommunityPreferredLanguageRepository>(relaxUnitFun = true)
private val bottomNavItemsRepository =
mockk<BottomNavItemsRepository>(relaxUnitFun = true) {
coEvery { get(accountId = any()) } returns BottomNavItemsRepository.DEFAULT_ITEMS
}
private val lemmyValueCache = mockk<LemmyValueCache>(relaxUnitFun = true) private val lemmyValueCache = mockk<LemmyValueCache>(relaxUnitFun = true)
private val sut = private val sut =
DefaultLoginUseCase( DefaultLoginUseCase(
@ -51,6 +56,7 @@ class DefaultLoginUseCaseTest {
siteRepository = siteRepository, siteRepository = siteRepository,
communitySortRepository = communitySortRepository, communitySortRepository = communitySortRepository,
communityPreferredLanguageRepository = communityPreferredLanguageRepository, communityPreferredLanguageRepository = communityPreferredLanguageRepository,
bottomNavItemsRepository = bottomNavItemsRepository,
lemmyValueCache = lemmyValueCache, lemmyValueCache = lemmyValueCache,
) )

View File

@ -1,5 +1,6 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.identity.usecase package com.github.diegoberaldin.raccoonforlemmy.domain.identity.usecase
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.BottomNavItemsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.AccountModel import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.AccountModel
@ -27,6 +28,10 @@ class DefaultLogoutUseCaseTest {
private val notificationCenter = mockk<NotificationCenter>(relaxUnitFun = true) private val notificationCenter = mockk<NotificationCenter>(relaxUnitFun = true)
private val communitySortRepository = mockk<CommunitySortRepository>(relaxUnitFun = true) private val communitySortRepository = mockk<CommunitySortRepository>(relaxUnitFun = true)
private val lemmyValueCache = mockk<LemmyValueCache>(relaxUnitFun = true) private val lemmyValueCache = mockk<LemmyValueCache>(relaxUnitFun = true)
private val bottomNavItemsRepository =
mockk<BottomNavItemsRepository>(relaxUnitFun = true) {
coEvery { get(accountId = any()) } returns BottomNavItemsRepository.DEFAULT_ITEMS
}
private val sut = private val sut =
DefaultLogoutUseCase( DefaultLogoutUseCase(
identityRepository = identityRepository, identityRepository = identityRepository,
@ -34,6 +39,7 @@ class DefaultLogoutUseCaseTest {
settingsRepository = settingsRepository, settingsRepository = settingsRepository,
notificationCenter = notificationCenter, notificationCenter = notificationCenter,
communitySortRepository = communitySortRepository, communitySortRepository = communitySortRepository,
bottomNavItemsRepository = bottomNavItemsRepository,
lemmyValueCache = lemmyValueCache, lemmyValueCache = lemmyValueCache,
) )

View File

@ -1,6 +1,7 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.identity.usecase package com.github.diegoberaldin.raccoonforlemmy.domain.identity.usecase
import com.github.diegoberaldin.raccoonforlemmy.core.api.provider.ServiceProvider import com.github.diegoberaldin.raccoonforlemmy.core.api.provider.ServiceProvider
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.BottomNavItemsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.AccountModel import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.AccountModel
@ -32,6 +33,10 @@ class DefaultSwitchAccountUseCaseTest {
private val communityPreferredLanguageRepository = private val communityPreferredLanguageRepository =
mockk<CommunityPreferredLanguageRepository>(relaxUnitFun = true) mockk<CommunityPreferredLanguageRepository>(relaxUnitFun = true)
private val lemmyValueCache = mockk<LemmyValueCache>(relaxUnitFun = true) private val lemmyValueCache = mockk<LemmyValueCache>(relaxUnitFun = true)
private val bottomNavItemsRepository =
mockk<BottomNavItemsRepository>(relaxUnitFun = true) {
coEvery { get(accountId = any()) } returns BottomNavItemsRepository.DEFAULT_ITEMS
}
private val sut = private val sut =
DefaultSwitchAccountUseCase( DefaultSwitchAccountUseCase(
identityRepository = identityRepository, identityRepository = identityRepository,
@ -41,6 +46,7 @@ class DefaultSwitchAccountUseCaseTest {
notificationCenter = notificationCenter, notificationCenter = notificationCenter,
communitySortRepository = communitySortRepository, communitySortRepository = communitySortRepository,
communityPreferredLanguageRepository = communityPreferredLanguageRepository, communityPreferredLanguageRepository = communityPreferredLanguageRepository,
bottomNavItemsRepository = bottomNavItemsRepository,
lemmyValueCache = lemmyValueCache, lemmyValueCache = lemmyValueCache,
) )

View File

@ -47,6 +47,7 @@ val coreIdentityModule =
siteRepository = get(), siteRepository = get(),
communitySortRepository = get(), communitySortRepository = get(),
communityPreferredLanguageRepository = get(), communityPreferredLanguageRepository = get(),
bottomNavItemsRepository = get(),
lemmyValueCache = get(), lemmyValueCache = get(),
) )
} }
@ -57,6 +58,7 @@ val coreIdentityModule =
notificationCenter = get(), notificationCenter = get(),
settingsRepository = get(), settingsRepository = get(),
communitySortRepository = get(), communitySortRepository = get(),
bottomNavItemsRepository = get(),
lemmyValueCache = get(), lemmyValueCache = get(),
) )
} }
@ -69,6 +71,7 @@ val coreIdentityModule =
notificationCenter = get(), notificationCenter = get(),
communitySortRepository = get(), communitySortRepository = get(),
communityPreferredLanguageRepository = get(), communityPreferredLanguageRepository = get(),
bottomNavItemsRepository = get(),
lemmyValueCache = get(), lemmyValueCache = get(),
) )
} }

View File

@ -1,6 +1,8 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.identity.usecase package com.github.diegoberaldin.raccoonforlemmy.domain.identity.usecase
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.VoteFormat import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.VoteFormat
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.BottomNavItemsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toInts
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.AccountModel import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.AccountModel
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.AccountRepository import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.AccountRepository
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.CommunityPreferredLanguageRepository import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.CommunityPreferredLanguageRepository
@ -21,6 +23,7 @@ internal class DefaultLoginUseCase(
private val siteRepository: SiteRepository, private val siteRepository: SiteRepository,
private val communitySortRepository: CommunitySortRepository, private val communitySortRepository: CommunitySortRepository,
private val communityPreferredLanguageRepository: CommunityPreferredLanguageRepository, private val communityPreferredLanguageRepository: CommunityPreferredLanguageRepository,
private val bottomNavItemsRepository: BottomNavItemsRepository,
private val lemmyValueCache: LemmyValueCache, private val lemmyValueCache: LemmyValueCache,
) : LoginUseCase { ) : LoginUseCase {
override suspend operator fun invoke( override suspend operator fun invoke(
@ -106,6 +109,9 @@ internal class DefaultLoginUseCase(
val newSettings = settingsRepository.getSettings(accountId) val newSettings = settingsRepository.getSettings(accountId)
settingsRepository.changeCurrentSettings(newSettings) settingsRepository.changeCurrentSettings(newSettings)
val bottomBarSections = bottomNavItemsRepository.get(accountId)
settingsRepository.changeCurrentBottomBarSections(bottomBarSections.toInts())
} }
} }
} }

View File

@ -1,5 +1,7 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.identity.usecase package com.github.diegoberaldin.raccoonforlemmy.domain.identity.usecase
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.BottomNavItemsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toInts
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.AccountRepository import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.AccountRepository
@ -14,6 +16,7 @@ internal class DefaultLogoutUseCase(
private val notificationCenter: NotificationCenter, private val notificationCenter: NotificationCenter,
private val settingsRepository: SettingsRepository, private val settingsRepository: SettingsRepository,
private val communitySortRepository: CommunitySortRepository, private val communitySortRepository: CommunitySortRepository,
private val bottomNavItemsRepository: BottomNavItemsRepository,
private val lemmyValueCache: LemmyValueCache, private val lemmyValueCache: LemmyValueCache,
) : LogoutUseCase { ) : LogoutUseCase {
override suspend operator fun invoke() { override suspend operator fun invoke() {
@ -31,5 +34,8 @@ internal class DefaultLogoutUseCase(
} }
val anonSettings = settingsRepository.getSettings(null) val anonSettings = settingsRepository.getSettings(null)
settingsRepository.changeCurrentSettings(anonSettings) settingsRepository.changeCurrentSettings(anonSettings)
val bottomBarSections = bottomNavItemsRepository.get(null)
settingsRepository.changeCurrentBottomBarSections(bottomBarSections.toInts())
} }
} }

View File

@ -1,6 +1,8 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.identity.usecase package com.github.diegoberaldin.raccoonforlemmy.domain.identity.usecase
import com.github.diegoberaldin.raccoonforlemmy.core.api.provider.ServiceProvider import com.github.diegoberaldin.raccoonforlemmy.core.api.provider.ServiceProvider
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.BottomNavItemsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toInts
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.AccountModel import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.AccountModel
@ -19,6 +21,7 @@ internal class DefaultSwitchAccountUseCase(
private val settingsRepository: SettingsRepository, private val settingsRepository: SettingsRepository,
private val communitySortRepository: CommunitySortRepository, private val communitySortRepository: CommunitySortRepository,
private val communityPreferredLanguageRepository: CommunityPreferredLanguageRepository, private val communityPreferredLanguageRepository: CommunityPreferredLanguageRepository,
private val bottomNavItemsRepository: BottomNavItemsRepository,
private val lemmyValueCache: LemmyValueCache, private val lemmyValueCache: LemmyValueCache,
) : SwitchAccountUseCase { ) : SwitchAccountUseCase {
override suspend fun invoke(account: AccountModel) { override suspend fun invoke(account: AccountModel) {
@ -44,5 +47,8 @@ internal class DefaultSwitchAccountUseCase(
val newSettings = settingsRepository.getSettings(accountId) val newSettings = settingsRepository.getSettings(accountId)
settingsRepository.changeCurrentSettings(newSettings) settingsRepository.changeCurrentSettings(newSettings)
val bottomBarSections = bottomNavItemsRepository.get(accountId)
settingsRepository.changeCurrentBottomBarSections(bottomBarSections.toInts())
} }
} }

View File

@ -8,6 +8,8 @@ import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.tab.Tab import cafe.adriel.voyager.navigator.tab.Tab
import cafe.adriel.voyager.navigator.tab.TabOptions import cafe.adriel.voyager.navigator.tab.TabOptions
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.TabNavigationSection
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toInt
import com.github.diegoberaldin.raccoonforlemmy.unit.postlist.PostListScreen import com.github.diegoberaldin.raccoonforlemmy.unit.postlist.PostListScreen
object HomeTab : Tab { object HomeTab : Tab {
@ -18,7 +20,7 @@ object HomeTab : Tab {
val title = LocalStrings.current.navigationHome val title = LocalStrings.current.navigationHome
return TabOptions( return TabOptions(
index = 0u, index = TabNavigationSection.Home.toInt().toUShort(),
title = title, title = title,
icon = icon, icon = icon,
) )

View File

@ -8,6 +8,8 @@ import cafe.adriel.voyager.navigator.tab.Tab
import cafe.adriel.voyager.navigator.tab.TabNavigator import cafe.adriel.voyager.navigator.tab.TabNavigator
import cafe.adriel.voyager.navigator.tab.TabOptions import cafe.adriel.voyager.navigator.tab.TabOptions
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.TabNavigationSection
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toInt
import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.main.InboxScreen import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.main.InboxScreen
object InboxTab : Tab { object InboxTab : Tab {
@ -17,7 +19,7 @@ object InboxTab : Tab {
val icon = rememberVectorPainter(Icons.Default.Inbox) val icon = rememberVectorPainter(Icons.Default.Inbox)
val title = LocalStrings.current.navigationInbox val title = LocalStrings.current.navigationInbox
return TabOptions( return TabOptions(
index = 3u, index = TabNavigationSection.Inbox.toInt().toUShort(),
title = title, title = title,
icon = icon, icon = icon,
) )

View File

@ -31,6 +31,7 @@ val profileTabModule =
} }
factory<ProfileSideMenuMviModel> { factory<ProfileSideMenuMviModel> {
ProfileSideMenuViewModel( ProfileSideMenuViewModel(
settingsRepository = get(),
lemmyValueCache = get(), lemmyValueCache = get(),
) )
} }

View File

@ -27,6 +27,7 @@ internal fun ProfileMenuContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
isModerator: Boolean = false, isModerator: Boolean = false,
canCreateCommunity: Boolean = false, canCreateCommunity: Boolean = false,
isBookmarksVisible: Boolean = true,
) { ) {
val notificationCenter = remember { getNotificationCenter() } val notificationCenter = remember { getNotificationCenter() }
@ -42,14 +43,16 @@ internal fun ProfileMenuContent(
notificationCenter.send(NotificationCenterEvent.ProfileSideMenuAction.ManageSubscriptions) notificationCenter.send(NotificationCenterEvent.ProfileSideMenuAction.ManageSubscriptions)
}, },
) )
SettingsRow( if (isBookmarksVisible) {
title = LocalStrings.current.navigationDrawerTitleBookmarks, SettingsRow(
icon = Icons.Default.Bookmark, title = LocalStrings.current.navigationDrawerTitleBookmarks,
onTap = icon = Icons.Default.Bookmark,
rememberCallback { onTap =
notificationCenter.send(NotificationCenterEvent.ProfileSideMenuAction.Bookmarks) rememberCallback {
}, notificationCenter.send(NotificationCenterEvent.ProfileSideMenuAction.Bookmarks)
) },
)
}
SettingsRow( SettingsRow(
title = LocalStrings.current.navigationDrawerTitleDrafts, title = LocalStrings.current.navigationDrawerTitleDrafts,
icon = Icons.Default.Drafts, icon = Icons.Default.Drafts,

View File

@ -11,6 +11,7 @@ interface ProfileSideMenuMviModel :
data class State( data class State(
val isModerator: Boolean = false, val isModerator: Boolean = false,
val canCreateCommunity: Boolean = false, val canCreateCommunity: Boolean = false,
val isBookmarksVisible: Boolean = true,
) )
sealed interface Effect sealed interface Effect

View File

@ -70,6 +70,7 @@ class ProfileSideMenuScreen : Screen {
), ),
isModerator = uiState.isModerator, isModerator = uiState.isModerator,
canCreateCommunity = uiState.canCreateCommunity, canCreateCommunity = uiState.canCreateCommunity,
isBookmarksVisible = uiState.isBookmarksVisible,
) )
} }
} }

View File

@ -2,6 +2,9 @@ package com.github.diegoberaldin.raccoonforlemmy.feature.profile.menu
import cafe.adriel.voyager.core.model.screenModelScope import cafe.adriel.voyager.core.model.screenModelScope
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.TabNavigationSection
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toTabNavigationSections
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.LemmyValueCache import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.LemmyValueCache
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@ -9,6 +12,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class ProfileSideMenuViewModel( class ProfileSideMenuViewModel(
private val settingsRepository: SettingsRepository,
private val lemmyValueCache: LemmyValueCache, private val lemmyValueCache: LemmyValueCache,
) : DefaultMviModel<ProfileSideMenuMviModel.Intent, ProfileSideMenuMviModel.State, ProfileSideMenuMviModel.Effect>( ) : DefaultMviModel<ProfileSideMenuMviModel.Intent, ProfileSideMenuMviModel.State, ProfileSideMenuMviModel.Effect>(
ProfileSideMenuMviModel.State(), ProfileSideMenuMviModel.State(),
@ -39,6 +43,16 @@ class ProfileSideMenuViewModel(
) )
} }
}.launchIn(this) }.launchIn(this)
settingsRepository.currentBottomBarSections
.onEach { sectionIds ->
val isBookmarksInBottomBar =
sectionIds
.toTabNavigationSections()
.contains(TabNavigationSection.Bookmarks)
updateState {
it.copy(isBookmarksVisible = !isBookmarksInBottomBar)
}
}.launchIn(this)
} }
} }
} }

View File

@ -8,6 +8,8 @@ import cafe.adriel.voyager.navigator.tab.Tab
import cafe.adriel.voyager.navigator.tab.TabNavigator import cafe.adriel.voyager.navigator.tab.TabNavigator
import cafe.adriel.voyager.navigator.tab.TabOptions import cafe.adriel.voyager.navigator.tab.TabOptions
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.TabNavigationSection
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toInt
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.main.ProfileMainScreen import com.github.diegoberaldin.raccoonforlemmy.feature.profile.main.ProfileMainScreen
object ProfileTab : Tab { object ProfileTab : Tab {
@ -16,7 +18,7 @@ object ProfileTab : Tab {
val icon = rememberVectorPainter(Icons.Default.AccountCircle) val icon = rememberVectorPainter(Icons.Default.AccountCircle)
val title = LocalStrings.current.navigationProfile val title = LocalStrings.current.navigationProfile
return TabOptions( return TabOptions(
index = 2u, index = TabNavigationSection.Profile.toInt().toUShort(),
title = title, title = title,
icon = icon, icon = icon,
) )

View File

@ -8,6 +8,8 @@ import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.tab.Tab import cafe.adriel.voyager.navigator.tab.Tab
import cafe.adriel.voyager.navigator.tab.TabOptions import cafe.adriel.voyager.navigator.tab.TabOptions
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.TabNavigationSection
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toInt
import com.github.diegoberaldin.raccoonforlemmy.unit.explore.ExploreScreen import com.github.diegoberaldin.raccoonforlemmy.unit.explore.ExploreScreen
object ExploreTab : Tab { object ExploreTab : Tab {
@ -16,7 +18,7 @@ object ExploreTab : Tab {
val icon = rememberVectorPainter(Icons.Default.Explore) val icon = rememberVectorPainter(Icons.Default.Explore)
val title = LocalStrings.current.navigationSearch val title = LocalStrings.current.navigationSearch
return TabOptions( return TabOptions(
index = 1u, index = TabNavigationSection.Explore.toInt().toUShort(),
title = title, title = title,
icon = icon, icon = icon,
) )

View File

@ -67,6 +67,7 @@ kotlin {
implementation(projects.unit.choosecolor) implementation(projects.unit.choosecolor)
implementation(projects.unit.choosefont) implementation(projects.unit.choosefont)
implementation(projects.unit.configureswipeactions) implementation(projects.unit.configureswipeactions)
implementation(projects.unit.configurenavbar)
implementation(projects.unit.configurecontentview) implementation(projects.unit.configurecontentview)
implementation(projects.unit.filteredcontents) implementation(projects.unit.filteredcontents)
implementation(projects.unit.medialist) implementation(projects.unit.medialist)
@ -82,8 +83,14 @@ kotlin {
android { android {
namespace = "com.github.diegoberaldin.raccoonforlemmy.feature.settings" namespace = "com.github.diegoberaldin.raccoonforlemmy.feature.settings"
compileSdk = libs.versions.android.targetSdk.get().toInt() compileSdk =
libs.versions.android.targetSdk
.get()
.toInt()
defaultConfig { defaultConfig {
minSdk = libs.versions.android.minSdk.get().toInt() minSdk =
libs.versions.android.minSdk
.get()
.toInt()
} }
} }

View File

@ -8,6 +8,8 @@ import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.tab.Tab import cafe.adriel.voyager.navigator.tab.Tab
import cafe.adriel.voyager.navigator.tab.TabOptions import cafe.adriel.voyager.navigator.tab.TabOptions
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.TabNavigationSection
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toInt
import com.github.diegoberaldin.raccoonforlemmy.feature.settings.main.SettingsScreen import com.github.diegoberaldin.raccoonforlemmy.feature.settings.main.SettingsScreen
object SettingsTab : Tab { object SettingsTab : Tab {
@ -18,7 +20,7 @@ object SettingsTab : Tab {
val title = LocalStrings.current.navigationSettings val title = LocalStrings.current.navigationSettings
return TabOptions( return TabOptions(
index = 4u, index = TabNavigationSection.Settings.toInt().toUShort(),
title = title, title = title,
icon = icon, icon = icon,
) )

View File

@ -69,6 +69,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.utils.datetime.getPrettyDur
import com.github.diegoberaldin.raccoonforlemmy.core.utils.fs.getFileSystemManager import com.github.diegoberaldin.raccoonforlemmy.core.utils.fs.getFileSystemManager
import com.github.diegoberaldin.raccoonforlemmy.core.utils.toLocalDp import com.github.diegoberaldin.raccoonforlemmy.core.utils.toLocalDp
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toReadableName import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toReadableName
import com.github.diegoberaldin.raccoonforlemmy.unit.configurenavbar.ConfigureNavBarScreen
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -159,6 +160,7 @@ class AdvancedSettingsScreen : Screen {
Modifier Modifier
.padding( .padding(
top = padding.calculateTopPadding(), top = padding.calculateTopPadding(),
bottom = padding.calculateBottomPadding(),
).nestedScroll(scrollBehavior.nestedScrollConnection), ).nestedScroll(scrollBehavior.nestedScrollConnection),
) { ) {
Column( Column(
@ -495,6 +497,14 @@ class AdvancedSettingsScreen : Screen {
title = LocalStrings.current.settingsTitleExperimental, title = LocalStrings.current.settingsTitleExperimental,
icon = Icons.Default.Science, icon = Icons.Default.Science,
) )
SettingsRow(
title = LocalStrings.current.settingsItemConfigureBottomNavigationBar,
disclosureIndicator = true,
onTap =
rememberCallback {
navigationCoordinator.pushScreen(ConfigureNavBarScreen())
},
)
if (uiState.alternateMarkdownRenderingItemVisible) { if (uiState.alternateMarkdownRenderingItemVisible) {
// alternate Markdown rendering // alternate Markdown rendering
SettingsSwitchRow( SettingsSwitchRow(

View File

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
@ -154,8 +155,8 @@ class SettingsScreen : Screen {
Modifier Modifier
.padding( .padding(
top = padding.calculateTopPadding(), top = padding.calculateTopPadding(),
bottom = padding.calculateBottomPadding(), ).navigationBarsPadding()
).nestedScroll(scrollBehavior.nestedScrollConnection), .nestedScroll(scrollBehavior.nestedScrollConnection),
) { ) {
Column( Column(
modifier = Modifier.fillMaxSize().verticalScroll(scrollState), modifier = Modifier.fillMaxSize().verticalScroll(scrollState),

View File

@ -61,6 +61,7 @@ include(":unit:choosefont")
include(":unit:communitydetail") include(":unit:communitydetail")
include(":unit:communityinfo") include(":unit:communityinfo")
include(":unit:configurecontentview") include(":unit:configurecontentview")
include(":unit:configurenavbar")
include(":unit:configureswipeactions") include(":unit:configureswipeactions")
include(":unit:createcomment") include(":unit:createcomment")
include(":unit:createpost") include(":unit:createpost")

View File

@ -76,6 +76,7 @@ kotlin {
implementation(projects.unit.communitydetail) implementation(projects.unit.communitydetail)
implementation(projects.unit.communityinfo) implementation(projects.unit.communityinfo)
implementation(projects.unit.configurecontentview) implementation(projects.unit.configurecontentview)
implementation(projects.unit.configurenavbar)
implementation(projects.unit.configureswipeactions) implementation(projects.unit.configureswipeactions)
implementation(projects.unit.createcomment) implementation(projects.unit.createcomment)
implementation(projects.unit.createpost) implementation(projects.unit.createpost)

View File

@ -35,6 +35,7 @@ import com.github.diegoberaldin.raccoonforlemmy.unit.chat.di.chatModule
import com.github.diegoberaldin.raccoonforlemmy.unit.communitydetail.di.communityDetailModule import com.github.diegoberaldin.raccoonforlemmy.unit.communitydetail.di.communityDetailModule
import com.github.diegoberaldin.raccoonforlemmy.unit.communityinfo.di.communityInfoModule import com.github.diegoberaldin.raccoonforlemmy.unit.communityinfo.di.communityInfoModule
import com.github.diegoberaldin.raccoonforlemmy.unit.configurecontentview.di.configureContentViewModule import com.github.diegoberaldin.raccoonforlemmy.unit.configurecontentview.di.configureContentViewModule
import com.github.diegoberaldin.raccoonforlemmy.unit.configurenavbar.di.configureNavBarModule
import com.github.diegoberaldin.raccoonforlemmy.unit.configureswipeactions.di.configureSwipeActionsModule import com.github.diegoberaldin.raccoonforlemmy.unit.configureswipeactions.di.configureSwipeActionsModule
import com.github.diegoberaldin.raccoonforlemmy.unit.createcomment.di.createCommentModule import com.github.diegoberaldin.raccoonforlemmy.unit.createcomment.di.createCommentModule
import com.github.diegoberaldin.raccoonforlemmy.unit.createpost.di.createPostModule import com.github.diegoberaldin.raccoonforlemmy.unit.createpost.di.createPostModule
@ -119,5 +120,6 @@ val sharedHelperModule =
moderateWithReasonModule, moderateWithReasonModule,
acknowledgementsModule, acknowledgementsModule,
mediaListModule, mediaListModule,
configureNavBarModule,
) )
} }

View File

@ -57,8 +57,10 @@ import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.ProvideString
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.ComposeEvent import com.github.diegoberaldin.raccoonforlemmy.core.navigation.ComposeEvent
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.DrawerEvent import com.github.diegoberaldin.raccoonforlemmy.core.navigation.DrawerEvent
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.SideMenuEvents import com.github.diegoberaldin.raccoonforlemmy.core.navigation.SideMenuEvents
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getBottomNavItemsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getDrawerCoordinator import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getDrawerCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigationCoordinator import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigationCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toInts
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.di.getAccountRepository import com.github.diegoberaldin.raccoonforlemmy.core.persistence.di.getAccountRepository
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.di.getSettingsRepository import com.github.diegoberaldin.raccoonforlemmy.core.persistence.di.getSettingsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.di.getAppConfigStore import com.github.diegoberaldin.raccoonforlemmy.core.preferences.di.getAppConfigStore
@ -108,9 +110,12 @@ fun App(onLoadingFinished: () -> Unit = {}) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val subscriptionsCache = remember { getSubscriptionsCache() } val subscriptionsCache = remember { getSubscriptionsCache() }
val appConfigStore = remember { getAppConfigStore() } val appConfigStore = remember { getAppConfigStore() }
val bottomNavItemsRepository = remember { getBottomNavItemsRepository() }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
val accountId = accountRepository.getActive()?.id val lastActiveAccount = accountRepository.getActive()
val lastInstance = lastActiveAccount?.instance?.takeIf { it.isNotEmpty() }
val accountId = lastActiveAccount?.id
val currentSettings = settingsRepository.getSettings(accountId) val currentSettings = settingsRepository.getSettings(accountId)
val seedColor = val seedColor =
if (currentSettings.randomThemeColor) { if (currentSettings.randomThemeColor) {
@ -122,8 +127,8 @@ fun App(onLoadingFinished: () -> Unit = {}) {
currentSettings.customSeedColor currentSettings.customSeedColor
} }
settingsRepository.changeCurrentSettings(currentSettings.copy(customSeedColor = seedColor)) settingsRepository.changeCurrentSettings(currentSettings.copy(customSeedColor = seedColor))
val lastActiveAccount = accountRepository.getActive() val bottomBarSections = bottomNavItemsRepository.get(accountId)
val lastInstance = lastActiveAccount?.instance?.takeIf { it.isNotEmpty() } settingsRepository.changeCurrentBottomBarSections(bottomBarSections.toInts())
if (lastInstance != null) { if (lastInstance != null) {
apiConfigurationRepository.changeInstance(lastInstance) apiConfigurationRepository.changeInstance(lastInstance)
} }
@ -132,6 +137,7 @@ fun App(onLoadingFinished: () -> Unit = {}) {
appConfigStore.initialize() appConfigStore.initialize()
hasBeenInitialized = true hasBeenInitialized = true
launch { launch {
delay(50) delay(50)
onLoadingFinished() onLoadingFinished()

View File

@ -43,10 +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.navigation.di.getNavigationCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs
import com.github.diegoberaldin.raccoonforlemmy.feature.home.ui.HomeTab import com.github.diegoberaldin.raccoonforlemmy.feature.home.ui.HomeTab
import com.github.diegoberaldin.raccoonforlemmy.feature.settings.main.SettingsScreen import com.github.diegoberaldin.raccoonforlemmy.feature.settings.main.SettingsScreen
import com.github.diegoberaldin.raccoonforlemmy.navigation.TabNavigationItem import com.github.diegoberaldin.raccoonforlemmy.navigation.TabNavigationItem
import com.github.diegoberaldin.raccoonforlemmy.navigation.toTab
import com.github.diegoberaldin.raccoonforlemmy.unit.manageaccounts.ManageAccountsScreen import com.github.diegoberaldin.raccoonforlemmy.unit.manageaccounts.ManageAccountsScreen
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.drop
@ -277,7 +277,8 @@ internal object MainScreen : Screen {
), ),
tonalElevation = 0.dp, tonalElevation = 0.dp,
) { ) {
for (section in uiState.bottomBarSections) { // it must be done so (indexed), otherwise section gets remembered in tap callbacks
uiState.bottomBarSections.forEachIndexed { idx, section ->
TabNavigationItem( TabNavigationItem(
section = section, section = section,
withText = titleVisible, withText = titleVisible,
@ -287,15 +288,17 @@ internal object MainScreen : Screen {
} else { } else {
null null
}, },
onClick = onClick = {
rememberCallbackArgs { tab -> val section = uiState.bottomBarSections[idx]
tabNavigator.current = tab val tab = section.toTab()
navigationCoordinator.setCurrentSection(section) tabNavigator.current = tab
}, navigationCoordinator.setCurrentSection(section)
onLongPress = },
rememberCallbackArgs { tab -> onLongPress = {
handleOnLongPress(tab, section) val section = uiState.bottomBarSections[idx]
}, val tab = section.toTab()
handleOnLongPress(tab, section)
},
) )
} }
} }

View File

@ -2,8 +2,7 @@ package com.github.diegoberaldin.raccoonforlemmy
import cafe.adriel.voyager.core.model.screenModelScope import cafe.adriel.voyager.core.model.screenModelScope
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.BottomNavItemsRepository import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toTabNavigationSections
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.AccountRepository
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.inbox.InboxCoordinator import com.github.diegoberaldin.raccoonforlemmy.domain.inbox.InboxCoordinator
@ -21,8 +20,6 @@ class MainViewModel(
private val identityRepository: IdentityRepository, private val identityRepository: IdentityRepository,
private val settingRepository: SettingsRepository, private val settingRepository: SettingsRepository,
private val userRepository: UserRepository, private val userRepository: UserRepository,
private val accountRepository: AccountRepository,
private val bottomNavItemsRepository: BottomNavItemsRepository,
private val notificationChecker: InboxNotificationChecker, private val notificationChecker: InboxNotificationChecker,
private val lemmyValueCache: LemmyValueCache, private val lemmyValueCache: LemmyValueCache,
) : DefaultMviModel<MainMviModel.Intent, MainMviModel.UiState, MainMviModel.Effect>( ) : DefaultMviModel<MainMviModel.Intent, MainMviModel.UiState, MainMviModel.Effect>(
@ -53,17 +50,20 @@ class MainViewModel(
notificationChecker.stop() notificationChecker.stop()
} }
}.launchIn(this) }.launchIn(this)
settingRepository.currentSettings settingRepository.currentSettings
.onEach { .onEach {
updateCustomProfileIcon() updateCustomProfileIcon()
}.launchIn(this) }.launchIn(this)
settingRepository.currentBottomBarSections
.onEach { sectionIds ->
val sections = sectionIds.toTabNavigationSections()
updateState { it.copy(bottomBarSections = sections) }
}.launchIn(this)
identityRepository.isLogged identityRepository.isLogged
.onEach { isLogged -> .onEach { isLogged ->
updateState { it.copy(isLogged = isLogged ?: false) } updateState { it.copy(isLogged = isLogged ?: false) }
updateCustomProfileIcon() updateCustomProfileIcon()
refreshBottomNavigationItems()
}.launchIn(this) }.launchIn(this)
} }
} }
@ -106,12 +106,4 @@ class MainViewModel(
emitEffect(MainMviModel.Effect.ReadAllInboxSuccess) emitEffect(MainMviModel.Effect.ReadAllInboxSuccess)
} }
} }
private suspend fun refreshBottomNavigationItems() {
val accountId = accountRepository.getActive()?.id
val sections = bottomNavItemsRepository.get(accountId)
updateState {
it.copy(bottomBarSections = sections)
}
}
} }

View File

@ -14,8 +14,6 @@ internal val internalSharedModule =
identityRepository = get(), identityRepository = get(),
settingRepository = get(), settingRepository = get(),
userRepository = get(), userRepository = get(),
accountRepository = get(),
bottomNavItemsRepository = get(),
notificationChecker = get(), notificationChecker = get(),
lemmyValueCache = get(), lemmyValueCache = get(),
) )

View File

@ -8,6 +8,8 @@ import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.tab.Tab import cafe.adriel.voyager.navigator.tab.Tab
import cafe.adriel.voyager.navigator.tab.TabOptions import cafe.adriel.voyager.navigator.tab.TabOptions
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.TabNavigationSection
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toInt
import com.github.diegoberaldin.raccoonforlemmy.unit.filteredcontents.FilteredContentsScreen import com.github.diegoberaldin.raccoonforlemmy.unit.filteredcontents.FilteredContentsScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.filteredcontents.FilteredContentsType import com.github.diegoberaldin.raccoonforlemmy.unit.filteredcontents.FilteredContentsType
import com.github.diegoberaldin.raccoonforlemmy.unit.filteredcontents.toInt import com.github.diegoberaldin.raccoonforlemmy.unit.filteredcontents.toInt
@ -20,7 +22,7 @@ object BookmarksTab : Tab {
val title = LocalStrings.current.navigationDrawerTitleBookmarks val title = LocalStrings.current.navigationDrawerTitleBookmarks
return TabOptions( return TabOptions(
index = 5u, index = TabNavigationSection.Bookmarks.toInt().toUShort(),
title = title, title = title,
icon = icon, icon = icon,
) )

View File

@ -42,8 +42,8 @@ internal fun RowScope.TabNavigationItem(
section: TabNavigationSection, section: TabNavigationSection,
withText: Boolean = true, withText: Boolean = true,
customIconUrl: String? = null, customIconUrl: String? = null,
onClick: ((Tab) -> Unit)? = null, onClick: (() -> Unit)? = null,
onLongPress: ((Tab) -> Unit)? = null, onLongPress: (() -> Unit)? = null,
) { ) {
val navigationCoordinator = remember { getNavigationCoordinator() } val navigationCoordinator = remember { getNavigationCoordinator() }
val unread by navigationCoordinator.inboxUnread.collectAsState() val unread by navigationCoordinator.inboxUnread.collectAsState()
@ -51,10 +51,6 @@ internal fun RowScope.TabNavigationItem(
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val tab = section.toTab() val tab = section.toTab()
fun handleClick() {
onClick?.invoke(tab)
}
val pointerInputModifier = val pointerInputModifier =
Modifier.pointerInput(Unit) { Modifier.pointerInput(Unit) {
detectTapGestures( detectTapGestures(
@ -65,16 +61,18 @@ internal fun RowScope.TabNavigationItem(
interactionSource.emit(PressInteraction.Release(press)) interactionSource.emit(PressInteraction.Release(press))
}, },
onTap = { onTap = {
handleClick() onClick?.invoke()
}, },
onLongPress = { onLongPress = {
onLongPress?.invoke(tab) onLongPress?.invoke()
}, },
) )
} }
NavigationBarItem( NavigationBarItem(
onClick = ::handleClick, onClick = {
onClick?.invoke()
},
selected = section == currentSection, selected = section == currentSection,
interactionSource = interactionSource, interactionSource = interactionSource,
icon = { icon = {
@ -136,7 +134,7 @@ internal fun RowScope.TabNavigationItem(
) )
} }
private fun TabNavigationSection.toTab(): Tab = internal fun TabNavigationSection.toTab(): Tab =
when (this) { when (this) {
TabNavigationSection.Explore -> ExploreTab TabNavigationSection.Explore -> ExploreTab
TabNavigationSection.Profile -> ProfileTab TabNavigationSection.Profile -> ProfileTab

View File

@ -36,6 +36,7 @@ import com.github.diegoberaldin.raccoonforlemmy.unit.chat.di.chatModule
import com.github.diegoberaldin.raccoonforlemmy.unit.communitydetail.di.communityDetailModule import com.github.diegoberaldin.raccoonforlemmy.unit.communitydetail.di.communityDetailModule
import com.github.diegoberaldin.raccoonforlemmy.unit.communityinfo.di.communityInfoModule import com.github.diegoberaldin.raccoonforlemmy.unit.communityinfo.di.communityInfoModule
import com.github.diegoberaldin.raccoonforlemmy.unit.configurecontentview.di.configureContentViewModule import com.github.diegoberaldin.raccoonforlemmy.unit.configurecontentview.di.configureContentViewModule
import com.github.diegoberaldin.raccoonforlemmy.unit.configurenavbar.di.configureNavBarModule
import com.github.diegoberaldin.raccoonforlemmy.unit.configureswipeactions.di.configureSwipeActionsModule import com.github.diegoberaldin.raccoonforlemmy.unit.configureswipeactions.di.configureSwipeActionsModule
import com.github.diegoberaldin.raccoonforlemmy.unit.createcomment.di.createCommentModule import com.github.diegoberaldin.raccoonforlemmy.unit.createcomment.di.createCommentModule
import com.github.diegoberaldin.raccoonforlemmy.unit.createpost.di.createPostModule import com.github.diegoberaldin.raccoonforlemmy.unit.createpost.di.createPostModule
@ -122,6 +123,7 @@ fun initKoin() {
moderateWithReasonModule, moderateWithReasonModule,
acknowledgementsModule, acknowledgementsModule,
mediaListModule, mediaListModule,
configureNavBarModule,
) )
} }

View File

@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
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.navigationBarsPadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
@ -110,17 +111,18 @@ class AccountSettingsScreen : Screen {
var confirmBackWithUnsavedChangesDialog by remember { mutableStateOf(false) } var confirmBackWithUnsavedChangesDialog by remember { mutableStateOf(false) }
LaunchedEffect(model) { LaunchedEffect(model) {
model.effects.onEach { evt -> model.effects
when (evt) { .onEach { evt ->
AccountSettingsMviModel.Effect.Failure -> { when (evt) {
snackbarHostState.showSnackbar(errorMessage) AccountSettingsMviModel.Effect.Failure -> {
} snackbarHostState.showSnackbar(errorMessage)
}
AccountSettingsMviModel.Effect.Success -> { AccountSettingsMviModel.Effect.Success -> {
snackbarHostState.showSnackbar(successMessage) snackbarHostState.showSnackbar(successMessage)
}
} }
} }.launchIn(this)
}.launchIn(this)
} }
DisposableEffect(key) { DisposableEffect(key) {
@ -214,8 +216,7 @@ class AccountSettingsScreen : Screen {
Modifier Modifier
.padding( .padding(
top = padding.calculateTopPadding(), top = padding.calculateTopPadding(),
bottom = Spacing.m, ).navigationBarsPadding()
)
.then( .then(
if (settings.hideNavigationBarWhileScrolling) { if (settings.hideNavigationBarWhileScrolling) {
Modifier.nestedScroll(scrollBehavior.nestedScrollConnection) Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)

View File

@ -0,0 +1,78 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.android.library)
alias(libs.plugins.jetbrains.compose)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.detekt)
}
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
kotlin {
applyDefaultHierarchyTemplate()
androidTarget {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_1_8)
}
}
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64(),
).forEach {
it.binaries.framework {
baseName = "unit.configurenavbar"
isStatic = true
}
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.material3)
implementation(compose.materialIconsExtended)
implementation(libs.koin.core)
implementation(libs.reorderable)
implementation(libs.voyager.navigator)
implementation(libs.voyager.screenmodel)
implementation(libs.voyager.koin)
implementation(projects.core.appearance)
implementation(projects.core.architecture)
implementation(projects.core.commonui.components)
implementation(projects.core.commonui.modals)
implementation(projects.core.l10n)
implementation(projects.core.navigation)
implementation(projects.core.notifications)
implementation(projects.core.persistence)
implementation(projects.core.utils)
implementation(projects.domain.identity)
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
}
}
android {
namespace = "com.github.diegoberaldin.raccoonforlemmy.unit.configurenavbar"
compileSdk =
libs.versions.android.targetSdk
.get()
.toInt()
defaultConfig {
minSdk =
libs.versions.android.minSdk
.get()
.toInt()
}
}

View File

@ -0,0 +1,36 @@
package com.github.diegoberaldin.raccoonforlemmy.unit.configurenavbar
import androidx.compose.runtime.Stable
import cafe.adriel.voyager.core.model.ScreenModel
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.TabNavigationSection
@Stable
interface ConfigureNavBarMviModel :
MviModel<ConfigureNavBarMviModel.Intent, ConfigureNavBarMviModel.UiState, ConfigureNavBarMviModel.Effect>,
ScreenModel {
sealed interface Intent {
data object Reset : Intent
data object HapticFeedback : Intent
data class SwapItems(
val from: Int,
val to: Int,
) : Intent
data class Delete(
val section: TabNavigationSection,
) : Intent
data object Save : Intent
}
data class UiState(
val sections: List<TabNavigationSection> = emptyList(),
val availableSections: List<TabNavigationSection> = emptyList(),
val hasUnsavedChanges: Boolean = false,
)
sealed interface Effect
}

View File

@ -0,0 +1,279 @@
package com.github.diegoberaldin.raccoonforlemmy.unit.configurenavbar
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.koin.getScreenModel
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.SelectTabNavigationSectionBottomSheet
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigationCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toInt
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toReadableName
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.di.getSettingsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
import com.github.diegoberaldin.raccoonforlemmy.unit.configurenavbar.composable.ConfigureAddAction
import com.github.diegoberaldin.raccoonforlemmy.unit.configurenavbar.composable.ConfigureNavBarItem
import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyListState
class ConfigureNavBarScreen : Screen {
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
override fun Content() {
val model = getScreenModel<ConfigureNavBarMviModel>()
val uiState by model.uiState.collectAsState()
val navigationCoordinator = remember { getNavigationCoordinator() }
val topAppBarState = rememberTopAppBarState()
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(topAppBarState)
val settingsRepository = remember { getSettingsRepository() }
val settings by settingsRepository.currentSettings.collectAsState()
val lazyListState = rememberLazyListState()
val reorderableLazyColumnState =
rememberReorderableLazyListState(lazyListState) { from, to ->
model.reduce(
ConfigureNavBarMviModel.Intent.SwapItems(
from = from.index - 1,
to = to.index - 1,
),
)
}
var confirmBackWithUnsavedChangesDialog by remember { mutableStateOf(false) }
DisposableEffect(key) {
navigationCoordinator.setCanGoBackCallback {
if (uiState.hasUnsavedChanges) {
confirmBackWithUnsavedChangesDialog = true
return@setCanGoBackCallback false
}
true
}
onDispose {
navigationCoordinator.setCanGoBackCallback(null)
}
}
Scaffold(
modifier =
Modifier
.background(MaterialTheme.colorScheme.background)
.padding(Spacing.xs),
topBar = {
TopAppBar(
scrollBehavior = scrollBehavior,
title = {
Text(
modifier = Modifier.padding(horizontal = Spacing.s),
text = LocalStrings.current.settingsItemConfigureBottomNavigationBar,
style = MaterialTheme.typography.titleMedium,
)
},
navigationIcon = {
if (navigationCoordinator.canPop.value) {
Image(
modifier =
Modifier.onClick(
onClick = {
if (uiState.hasUnsavedChanges) {
confirmBackWithUnsavedChangesDialog = true
} else {
navigationCoordinator.popScreen()
}
},
),
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground),
)
}
},
actions = {
TextButton(
contentPadding =
PaddingValues(
horizontal = Spacing.xs,
vertical = Spacing.xxs,
),
onClick = {
model.reduce(ConfigureNavBarMviModel.Intent.Reset)
},
) {
Text(
text = LocalStrings.current.buttonReset,
style = MaterialTheme.typography.labelSmall,
)
}
},
)
},
) { padding ->
Column(
modifier =
Modifier
.padding(
top = padding.calculateTopPadding(),
).navigationBarsPadding()
.then(
if (settings.hideNavigationBarWhileScrolling) {
Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
} else {
Modifier
},
),
) {
LazyColumn(
modifier = Modifier.weight(1f).fillMaxWidth(),
state = lazyListState,
verticalArrangement = Arrangement.spacedBy(Spacing.xs),
) {
if (uiState.sections.isEmpty()) {
item {
Text(
modifier = Modifier.fillMaxWidth().padding(top = Spacing.xs),
textAlign = TextAlign.Center,
text = LocalStrings.current.messageEmptyList,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onBackground,
)
}
} else {
item {
// workaround for https://github.com/Calvin-LL/Reorderable/issues/4
ReorderableItem(
state = reorderableLazyColumnState,
key = "dummy",
enabled = false,
modifier = Modifier.fillMaxWidth().height(Dp.Hairline),
) {}
}
}
items(
items = uiState.sections,
key = { it.toInt().toString() },
) { section ->
ReorderableItem(
state = reorderableLazyColumnState,
key = section.toInt().toString(),
) { isDragging ->
val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp)
Surface(
shadowElevation = elevation,
) {
ConfigureNavBarItem(
reorderableScope = this,
title = section.toReadableName(),
onDragStarted =
rememberCallback(model) {
model.reduce(ConfigureNavBarMviModel.Intent.HapticFeedback)
},
onDelete =
rememberCallback(model) {
model.reduce(ConfigureNavBarMviModel.Intent.Delete(section))
},
)
}
}
}
if (uiState.availableSections.isNotEmpty() && uiState.sections.size < 5) {
item {
ConfigureAddAction(
onAdd =
rememberCallback {
val available = model.uiState.value.availableSections
val sheet =
SelectTabNavigationSectionBottomSheet(values = available)
navigationCoordinator.showBottomSheet(sheet)
},
)
}
}
}
Box(
modifier = Modifier.fillMaxWidth().padding(vertical = Spacing.m),
contentAlignment = Alignment.Center,
) {
Button(
enabled = uiState.hasUnsavedChanges,
onClick = {
model.reduce(ConfigureNavBarMviModel.Intent.Save)
},
) {
Text(text = LocalStrings.current.actionSave)
}
}
}
}
if (confirmBackWithUnsavedChangesDialog) {
AlertDialog(
onDismissRequest = {
confirmBackWithUnsavedChangesDialog = false
},
dismissButton = {
Button(
onClick = {
confirmBackWithUnsavedChangesDialog = false
},
) {
Text(text = LocalStrings.current.buttonNoStay)
}
},
confirmButton = {
Button(
onClick = {
confirmBackWithUnsavedChangesDialog = false
navigationCoordinator.popScreen()
},
) {
Text(text = LocalStrings.current.buttonYesQuit)
}
},
text = {
Text(text = LocalStrings.current.messageUnsavedChanges)
},
)
}
}
}

View File

@ -0,0 +1,150 @@
package com.github.diegoberaldin.raccoonforlemmy.unit.configurenavbar
import cafe.adriel.voyager.core.model.screenModelScope
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.BottomNavItemsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.TabNavigationSection
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toInts
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toTabNavigationSection
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.AccountRepository
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.utils.vibrate.HapticFeedback
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
internal class ConfigureNavBarViewModel(
private val accountRepository: AccountRepository,
private val identityRepository: IdentityRepository,
private val bottomNavItemsRepository: BottomNavItemsRepository,
private val settingsRepository: SettingsRepository,
private val hapticFeedback: HapticFeedback,
private val notificationCenter: NotificationCenter,
) : DefaultMviModel<ConfigureNavBarMviModel.Intent, ConfigureNavBarMviModel.UiState, ConfigureNavBarMviModel.Effect>(
initialState = ConfigureNavBarMviModel.UiState(),
),
ConfigureNavBarMviModel {
init {
screenModelScope.launch {
notificationCenter
.subscribe(NotificationCenterEvent.TabNavigationSectionSelected::class)
.onEach { evt ->
evt.sectionId.toTabNavigationSection()?.also { section ->
handleAdd(section)
}
}.launchIn(this)
refresh()
}
}
override fun reduce(intent: ConfigureNavBarMviModel.Intent) {
when (intent) {
ConfigureNavBarMviModel.Intent.HapticFeedback -> hapticFeedback.vibrate()
ConfigureNavBarMviModel.Intent.Reset -> handleReset()
is ConfigureNavBarMviModel.Intent.Delete -> handleDelete(intent.section)
is ConfigureNavBarMviModel.Intent.SwapItems -> handleSwap(intent.from, intent.to)
ConfigureNavBarMviModel.Intent.Save -> save()
}
}
private suspend fun refresh() {
val accountId = accountRepository.getActive()?.id
val currentSections = bottomNavItemsRepository.get(accountId)
updateState {
it.copy(
sections = currentSections,
availableSections = getAvailableSections(currentSections),
)
}
}
private fun getAvailableSections(excludeSections: List<TabNavigationSection> = emptyList()): List<TabNavigationSection> {
val availableSections =
buildList {
this += TabNavigationSection.Home
this += TabNavigationSection.Explore
this += TabNavigationSection.Inbox
this += TabNavigationSection.Profile
this += TabNavigationSection.Settings
if (identityRepository.isLogged.value == true) {
this += TabNavigationSection.Bookmarks
}
}
return availableSections.filter { section -> section !in excludeSections }
}
private fun handleReset() {
screenModelScope.launch {
val oldSections = uiState.value.sections
val newSections = BottomNavItemsRepository.DEFAULT_ITEMS
updateState {
it.copy(
sections = newSections,
availableSections = getAvailableSections(newSections),
hasUnsavedChanges = newSections != oldSections,
)
}
}
}
private fun handleAdd(section: TabNavigationSection) {
val newSections = uiState.value.sections + section
screenModelScope.launch {
updateState {
it.copy(
sections = newSections,
availableSections = getAvailableSections(newSections),
hasUnsavedChanges = true,
)
}
}
}
private fun handleDelete(section: TabNavigationSection) {
val newSections = uiState.value.sections - section
screenModelScope.launch {
updateState {
it.copy(
sections = newSections,
availableSections = getAvailableSections(newSections),
hasUnsavedChanges = true,
)
}
}
}
private fun handleSwap(
from: Int,
to: Int,
) {
val newSections =
uiState.value.sections.toMutableList().apply {
val element = removeAt(from)
add(to, element)
}
screenModelScope.launch {
updateState {
it.copy(
sections = newSections,
hasUnsavedChanges = true,
)
}
}
}
private fun save() {
screenModelScope.launch {
val currentSections = uiState.value.sections
val accountId = accountRepository.getActive()?.id
bottomNavItemsRepository.update(accountId, currentSections)
updateState {
it.copy(hasUnsavedChanges = false)
}
settingsRepository.changeCurrentBottomBarSections(currentSections.toInts())
}
}
}

View File

@ -0,0 +1,48 @@
package com.github.diegoberaldin.raccoonforlemmy.unit.configurenavbar.composable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Add
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.IconSize
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.ancillaryTextAlpha
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
@Composable
internal fun ConfigureAddAction(onAdd: () -> Unit) {
val ancillaryColor = MaterialTheme.colorScheme.onBackground.copy(alpha = ancillaryTextAlpha)
Row(
modifier =
Modifier
.padding(
horizontal = Spacing.s,
vertical = Spacing.xs,
).onClick(onClick = onAdd),
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
modifier = Modifier.size(IconSize.m),
imageVector = Icons.Outlined.Add,
contentDescription = null,
tint = ancillaryColor,
)
Spacer(modifier = Modifier.width(Spacing.xs))
Text(
text = LocalStrings.current.buttonAdd,
style = MaterialTheme.typography.labelMedium,
)
}
}

View File

@ -0,0 +1,115 @@
package com.github.diegoberaldin.raccoonforlemmy.unit.configurenavbar.composable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInParent
import androidx.compose.ui.unit.DpOffset
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.IconSize
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.ancillaryTextAlpha
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CustomDropDown
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.toLocalDp
import sh.calvin.reorderable.ReorderableCollectionItemScope
@Composable
internal fun ConfigureNavBarItem(
modifier: Modifier = Modifier,
reorderableScope: ReorderableCollectionItemScope,
title: String,
onDragStarted: (() -> Unit)? = null,
onDelete: (() -> Unit)? = null,
) {
var optionsOffset by remember { mutableStateOf(Offset.Zero) }
var optionsMenuOpen by remember { mutableStateOf(false) }
val fullColor = MaterialTheme.colorScheme.onBackground
val ancillaryColor = MaterialTheme.colorScheme.onBackground.copy(alpha = ancillaryTextAlpha)
Row(
modifier =
modifier
.fillMaxWidth()
.padding(
horizontal = Spacing.m,
vertical = Spacing.s,
),
horizontalArrangement = Arrangement.spacedBy(Spacing.s),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = title,
color = fullColor,
)
Spacer(modifier = Modifier.weight(1f))
Box {
Icon(
modifier =
Modifier
.size(IconSize.m)
.padding(Spacing.xs)
.then(
with(reorderableScope) {
Modifier.draggableHandle(
onDragStarted = {
onDragStarted?.invoke()
},
)
},
).onGloballyPositioned {
optionsOffset = it.positionInParent()
}.onClick(
onClick = {
optionsMenuOpen = true
},
),
imageVector = Icons.Default.MoreVert,
contentDescription = null,
tint = ancillaryColor,
)
CustomDropDown(
expanded = optionsMenuOpen,
onDismiss = {
optionsMenuOpen = false
},
offset =
DpOffset(
x = optionsOffset.x.toLocalDp(),
y = optionsOffset.y.toLocalDp(),
),
) {
DropdownMenuItem(
text = {
Text(LocalStrings.current.commentActionDelete)
},
onClick = {
optionsMenuOpen = false
onDelete?.invoke()
},
)
}
}
}
}

View File

@ -0,0 +1,19 @@
package com.github.diegoberaldin.raccoonforlemmy.unit.configurenavbar.di
import com.github.diegoberaldin.raccoonforlemmy.unit.configurenavbar.ConfigureNavBarMviModel
import com.github.diegoberaldin.raccoonforlemmy.unit.configurenavbar.ConfigureNavBarViewModel
import org.koin.dsl.module
val configureNavBarModule =
module {
single<ConfigureNavBarMviModel> {
ConfigureNavBarViewModel(
accountRepository = get(),
identityRepository = get(),
bottomNavItemsRepository = get(),
settingsRepository = get(),
hapticFeedback = get(),
notificationCenter = get(),
)
}
}

View File

@ -205,22 +205,24 @@ object ModalDrawerContent : Tab {
) )
} }
} }
item { if (uiState.isSettingsVisible) {
DrawerShortcut( item {
title = LocalStrings.current.navigationSettings, DrawerShortcut(
icon = Icons.Default.Settings, title = LocalStrings.current.navigationSettings,
onSelected = icon = Icons.Default.Settings,
rememberCallback(coordinator) { onSelected =
scope.launch { rememberCallback(coordinator) {
focusManager.clearFocus() scope.launch {
navigationCoordinator.popUntilRoot() focusManager.clearFocus()
coordinator.toggleDrawer() navigationCoordinator.popUntilRoot()
delay(50) coordinator.toggleDrawer()
delay(50)
coordinator.sendEvent(DrawerEvent.OpenSettings) coordinator.sendEvent(DrawerEvent.OpenSettings)
} }
}, },
) )
}
} }
} }

View File

@ -36,6 +36,7 @@ interface ModalDrawerMviModel :
val searchText: String = "", val searchText: String = "",
val isFiltering: Boolean = false, val isFiltering: Boolean = false,
val enableToggleFavorite: Boolean = false, val enableToggleFavorite: Boolean = false,
val isSettingsVisible: Boolean = true,
) )
sealed interface Effect sealed interface Effect

View File

@ -4,6 +4,8 @@ import cafe.adriel.voyager.core.model.screenModelScope
import com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination.CommunityPaginationManager import com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination.CommunityPaginationManager
import com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination.CommunityPaginationSpecification import com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination.CommunityPaginationSpecification
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.TabNavigationSection
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toTabNavigationSections
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.FavoriteCommunityModel import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.FavoriteCommunityModel
@ -98,6 +100,14 @@ class ModalDrawerViewModel(
) )
} }
}.launchIn(this) }.launchIn(this)
settingsRepository.currentBottomBarSections
.onEach { sectionIds ->
val isSettingsInBottomBar =
sectionIds.toTabNavigationSections().contains(TabNavigationSection.Settings)
updateState {
it.copy(isSettingsVisible = !isSettingsInBottomBar)
}
}.launchIn(this)
uiState uiState
.map { it.searchText } .map { it.searchText }