diff --git a/core/commonui/modals/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/modals/SelectTabNavigationSectionBottomSheet.kt b/core/commonui/modals/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/modals/SelectTabNavigationSectionBottomSheet.kt new file mode 100644 index 000000000..f97b1017e --- /dev/null +++ b/core/commonui/modals/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/modals/SelectTabNavigationSectionBottomSheet.kt @@ -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, +) : 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, + ) + } + } + } + } + } +} diff --git a/core/l10n/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/l10n/messages/DefaultStrings.kt b/core/l10n/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/l10n/messages/DefaultStrings.kt index 9c4cdec54..19871f4cb 100644 --- a/core/l10n/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/l10n/messages/DefaultStrings.kt +++ b/core/l10n/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/l10n/messages/DefaultStrings.kt @@ -429,4 +429,6 @@ internal open class DefaultStrings : Strings { override val settingsSubtitleOpenPostWebPageOnImageClick = "If a post has an URL, open web page on image click" override val settingsItemAlternateMarkdownRendering = "Enable alternate Markdown rendering" + override val settingsItemConfigureBottomNavigationBar = "Configure navigation bar" + override val selectTabNavigationTitle = "Select a section" } diff --git a/core/l10n/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/l10n/messages/Strings.kt b/core/l10n/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/l10n/messages/Strings.kt index c994b7264..e47e05a21 100644 --- a/core/l10n/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/l10n/messages/Strings.kt +++ b/core/l10n/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/l10n/messages/Strings.kt @@ -427,6 +427,8 @@ interface Strings { val settingsItemOpenPostWebPageOnImageClick: String val settingsSubtitleOpenPostWebPageOnImageClick: String val settingsItemAlternateMarkdownRendering: String + val settingsItemConfigureBottomNavigationBar: String + val selectTabNavigationTitle: String } object Locales { diff --git a/core/navigation/build.gradle.kts b/core/navigation/build.gradle.kts index a75ed4816..3d9888ab9 100644 --- a/core/navigation/build.gradle.kts +++ b/core/navigation/build.gradle.kts @@ -42,6 +42,7 @@ kotlin { implementation(libs.voyager.screenmodel) implementation(libs.voyager.koin) + implementation(projects.core.l10n) implementation(projects.core.persistence) implementation(projects.core.preferences) implementation(projects.domain.lemmy.data) diff --git a/core/navigation/src/androidMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/di/Utils.kt b/core/navigation/src/androidMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/di/Utils.kt index e7f5f1d2a..aae9064ec 100644 --- a/core/navigation/src/androidMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/di/Utils.kt +++ b/core/navigation/src/androidMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/di/Utils.kt @@ -1,5 +1,6 @@ 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.NavigationCoordinator import org.koin.java.KoinJavaComponent.inject @@ -13,3 +14,8 @@ actual fun getDrawerCoordinator(): DrawerCoordinator { val res: DrawerCoordinator by inject(DrawerCoordinator::class.java) return res } + +actual fun getBottomNavItemsRepository(): BottomNavItemsRepository { + val res: BottomNavItemsRepository by inject(BottomNavItemsRepository::class.java) + return res +} diff --git a/core/navigation/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/DefaultBottomNavItemsRepositoryTest.kt b/core/navigation/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/DefaultBottomNavItemsRepositoryTest.kt index 719deb1c2..cf6e42429 100644 --- a/core/navigation/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/DefaultBottomNavItemsRepositoryTest.kt +++ b/core/navigation/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/DefaultBottomNavItemsRepositoryTest.kt @@ -79,8 +79,18 @@ class DefaultBottomNavItemsRepositoryTest { runTest { val otherAccountId = 1L val accountId = 2L - every { keyStore.get("BottomNavItemsRepository.$otherAccountId.items", any>()) } returns ITEMS_IDS - every { keyStore.get("BottomNavItemsRepository.$accountId.items", any>()) } returns emptyList() + every { + keyStore.get( + "BottomNavItemsRepository.$otherAccountId.items", + any>(), + ) + } returns ITEMS_IDS + every { + keyStore.get( + "BottomNavItemsRepository.$accountId.items", + any>(), + ) + } returns emptyList() val res = sut.get(accountId) @@ -94,8 +104,18 @@ class DefaultBottomNavItemsRepositoryTest { fun givenDataForOtherUser_whenGetForAnonymousAccount_thenResultAndInteractionsIsAsExpected() = runTest { val otherAccountId = 1 - every { keyStore.get("BottomNavItemsRepository.$otherAccountId.items", any>()) } returns ITEMS_IDS - every { keyStore.get("BottomNavItemsRepository.items", any>()) } returns emptyList() + every { + keyStore.get( + "BottomNavItemsRepository.$otherAccountId.items", + any>(), + ) + } returns ITEMS_IDS + every { + keyStore.get( + "BottomNavItemsRepository.items", + any>(), + ) + } returns emptyList() 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 { private val ITEMS_IDS = listOf("0", "1", "3", "2", "4") private val ITEMS = diff --git a/core/navigation/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/DefaultBottomNavItemsRepository.kt b/core/navigation/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/DefaultBottomNavItemsRepository.kt index 3fac3ce90..1612f8699 100644 --- a/core/navigation/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/DefaultBottomNavItemsRepository.kt +++ b/core/navigation/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/DefaultBottomNavItemsRepository.kt @@ -11,7 +11,7 @@ internal class DefaultBottomNavItemsRepository( override suspend fun get(accountId: Long?): List = withContext(Dispatchers.IO) { 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() } res ?: BottomNavItemsRepository.DEFAULT_ITEMS } @@ -21,7 +21,7 @@ internal class DefaultBottomNavItemsRepository( items: List, ) = withContext(Dispatchers.IO) { val key = getKey(accountId) - val itemIds = items.map { it.toTabNavigationId() } + val itemIds = items.map { it.toInt().toString() } keyStore.save(key, itemIds) } @@ -35,24 +35,3 @@ internal class DefaultBottomNavItemsRepository( 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" - } diff --git a/core/navigation/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/NavigationCoordinator.kt b/core/navigation/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/NavigationCoordinator.kt index 393e704da..94f564920 100644 --- a/core/navigation/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/NavigationCoordinator.kt +++ b/core/navigation/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/NavigationCoordinator.kt @@ -11,20 +11,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow 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 { data class WithUrl( val url: String, diff --git a/core/navigation/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/TabNavigationSection.kt b/core/navigation/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/TabNavigationSection.kt new file mode 100644 index 000000000..e242b0ee9 --- /dev/null +++ b/core/navigation/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/TabNavigationSection.kt @@ -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.toTabNavigationSections(): List = mapNotNull { it.toTabNavigationSection() } + +fun List.toInts(): List = map { it.toInt() } diff --git a/core/navigation/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/di/Utils.kt b/core/navigation/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/di/Utils.kt index 401fca681..8c97f63c2 100644 --- a/core/navigation/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/di/Utils.kt +++ b/core/navigation/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/di/Utils.kt @@ -1,8 +1,11 @@ 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.NavigationCoordinator expect fun getNavigationCoordinator(): NavigationCoordinator expect fun getDrawerCoordinator(): DrawerCoordinator + +expect fun getBottomNavItemsRepository(): BottomNavItemsRepository diff --git a/core/navigation/src/iosMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/di/Utils.kt b/core/navigation/src/iosMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/di/Utils.kt index 23a59a5b2..06819ec2c 100644 --- a/core/navigation/src/iosMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/di/Utils.kt +++ b/core/navigation/src/iosMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/navigation/di/Utils.kt @@ -1,5 +1,6 @@ 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.NavigationCoordinator import org.koin.core.component.KoinComponent @@ -9,7 +10,10 @@ actual fun getNavigationCoordinator() = CoreNavigationHelper.navigationCoordinat actual fun getDrawerCoordinator() = CoreNavigationHelper.drawerCoordinator +actual fun getBottomNavItemsRepository() = CoreNavigationHelper.bottomNavItemsRepository + object CoreNavigationHelper : KoinComponent { val navigationCoordinator: NavigationCoordinator by inject() val drawerCoordinator: DrawerCoordinator by inject() + val bottomNavItemsRepository: BottomNavItemsRepository by inject() } diff --git a/core/notifications/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/notifications/NotificationCenterEvent.kt b/core/notifications/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/notifications/NotificationCenterEvent.kt index 9c9de7b83..9b0406bfa 100644 --- a/core/notifications/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/notifications/NotificationCenterEvent.kt +++ b/core/notifications/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/notifications/NotificationCenterEvent.kt @@ -245,4 +245,8 @@ sealed interface NotificationCenterEvent { data object FavoritesUpdated : NotificationCenterEvent data object OpenSearchInExplore : NotificationCenterEvent + + data class TabNavigationSectionSelected( + val sectionId: Int, + ) : NotificationCenterEvent } diff --git a/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultSettingsRepositoryTest.kt b/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultSettingsRepositoryTest.kt index 8569237b4..61d96d682 100644 --- a/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultSettingsRepositoryTest.kt +++ b/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultSettingsRepositoryTest.kt @@ -90,6 +90,15 @@ class DefaultSettingsRepositoryTest { 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 fun whenCreateSettings_thenResultIsAsExpected() = runTest { diff --git a/core/persistence/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultSettingsRepository.kt b/core/persistence/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultSettingsRepository.kt index e452fede1..7297222be 100644 --- a/core/persistence/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultSettingsRepository.kt +++ b/core/persistence/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultSettingsRepository.kt @@ -13,6 +13,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.preferences.TemporaryKeySto import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.withContext import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.minutes @@ -77,6 +78,7 @@ internal class DefaultSettingsRepository( private val db = provider.getDatabase() override val currentSettings = MutableStateFlow(SettingsModel()) + override val currentBottomBarSections = MutableStateFlow>(emptyList()) override suspend fun createSettings( settings: SettingsModel, @@ -375,7 +377,10 @@ internal class DefaultSettingsRepository( settings.defaultExploreResultType, ) 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 { db.settingsQueries.update( theme = settings.theme?.toLong(), @@ -466,7 +471,11 @@ internal class DefaultSettingsRepository( } override fun changeCurrentSettings(settings: SettingsModel) { - currentSettings.value = settings + currentSettings.update { settings } + } + + override fun changeCurrentBottomBarSections(sectionIds: List) { + currentBottomBarSections.update { sectionIds } } } diff --git a/core/persistence/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/SettingsRepository.kt b/core/persistence/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/SettingsRepository.kt index 76a785aa7..2e2fa177c 100644 --- a/core/persistence/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/SettingsRepository.kt +++ b/core/persistence/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/SettingsRepository.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.StateFlow @Stable interface SettingsRepository { val currentSettings: StateFlow + val currentBottomBarSections: StateFlow> suspend fun createSettings( settings: SettingsModel, @@ -21,4 +22,6 @@ interface SettingsRepository { ) fun changeCurrentSettings(settings: SettingsModel) + + fun changeCurrentBottomBarSections(sectionIds: List) } diff --git a/docs/app_config.json b/docs/app_config.json index 50b43b1e2..f0d35a6d9 100644 --- a/docs/app_config.json +++ b/docs/app_config.json @@ -1,3 +1,3 @@ { - "alternateMarkdownRenderingSettingsItemEnabled": true + "alternateMarkdownRenderingSettingsItemEnabled": false } diff --git a/domain/identity/build.gradle.kts b/domain/identity/build.gradle.kts index 2f9144c95..d8ca1f6a2 100644 --- a/domain/identity/build.gradle.kts +++ b/domain/identity/build.gradle.kts @@ -40,6 +40,7 @@ kotlin { implementation(projects.core.utils) implementation(projects.core.appearance) implementation(projects.core.persistence) + implementation(projects.core.navigation) implementation(projects.core.notifications) implementation(projects.domain.lemmy.repository) implementation(projects.domain.lemmy.data) @@ -58,8 +59,14 @@ kotlin { android { namespace = "com.github.diegoberaldin.raccoonforlemmy.domain.identity" - compileSdk = libs.versions.android.targetSdk.get().toInt() + compileSdk = + libs.versions.android.targetSdk + .get() + .toInt() defaultConfig { - minSdk = libs.versions.android.minSdk.get().toInt() + minSdk = + libs.versions.android.minSdk + .get() + .toInt() } } diff --git a/domain/identity/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultLoginUseCaseTest.kt b/domain/identity/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultLoginUseCaseTest.kt index 7359ebcf4..923790ef3 100644 --- a/domain/identity/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultLoginUseCaseTest.kt +++ b/domain/identity/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultLoginUseCaseTest.kt @@ -1,6 +1,7 @@ package com.github.diegoberaldin.raccoonforlemmy.domain.identity.usecase 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.SettingsModel import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.AccountRepository @@ -40,6 +41,10 @@ class DefaultLoginUseCaseTest { private val communitySortRepository = mockk(relaxUnitFun = true) private val communityPreferredLanguageRepository = mockk(relaxUnitFun = true) + private val bottomNavItemsRepository = + mockk(relaxUnitFun = true) { + coEvery { get(accountId = any()) } returns BottomNavItemsRepository.DEFAULT_ITEMS + } private val lemmyValueCache = mockk(relaxUnitFun = true) private val sut = DefaultLoginUseCase( @@ -51,6 +56,7 @@ class DefaultLoginUseCaseTest { siteRepository = siteRepository, communitySortRepository = communitySortRepository, communityPreferredLanguageRepository = communityPreferredLanguageRepository, + bottomNavItemsRepository = bottomNavItemsRepository, lemmyValueCache = lemmyValueCache, ) diff --git a/domain/identity/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultLogoutUseCaseTest.kt b/domain/identity/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultLogoutUseCaseTest.kt index 08c0659f1..40fcff625 100644 --- a/domain/identity/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultLogoutUseCaseTest.kt +++ b/domain/identity/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultLogoutUseCaseTest.kt @@ -1,5 +1,6 @@ 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.NotificationCenterEvent import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.AccountModel @@ -27,6 +28,10 @@ class DefaultLogoutUseCaseTest { private val notificationCenter = mockk(relaxUnitFun = true) private val communitySortRepository = mockk(relaxUnitFun = true) private val lemmyValueCache = mockk(relaxUnitFun = true) + private val bottomNavItemsRepository = + mockk(relaxUnitFun = true) { + coEvery { get(accountId = any()) } returns BottomNavItemsRepository.DEFAULT_ITEMS + } private val sut = DefaultLogoutUseCase( identityRepository = identityRepository, @@ -34,6 +39,7 @@ class DefaultLogoutUseCaseTest { settingsRepository = settingsRepository, notificationCenter = notificationCenter, communitySortRepository = communitySortRepository, + bottomNavItemsRepository = bottomNavItemsRepository, lemmyValueCache = lemmyValueCache, ) diff --git a/domain/identity/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultSwitchAccountUseCaseTest.kt b/domain/identity/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultSwitchAccountUseCaseTest.kt index e023ca5a7..8d6fe6b0d 100644 --- a/domain/identity/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultSwitchAccountUseCaseTest.kt +++ b/domain/identity/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultSwitchAccountUseCaseTest.kt @@ -1,6 +1,7 @@ package com.github.diegoberaldin.raccoonforlemmy.domain.identity.usecase 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.NotificationCenterEvent import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.AccountModel @@ -32,6 +33,10 @@ class DefaultSwitchAccountUseCaseTest { private val communityPreferredLanguageRepository = mockk(relaxUnitFun = true) private val lemmyValueCache = mockk(relaxUnitFun = true) + private val bottomNavItemsRepository = + mockk(relaxUnitFun = true) { + coEvery { get(accountId = any()) } returns BottomNavItemsRepository.DEFAULT_ITEMS + } private val sut = DefaultSwitchAccountUseCase( identityRepository = identityRepository, @@ -41,6 +46,7 @@ class DefaultSwitchAccountUseCaseTest { notificationCenter = notificationCenter, communitySortRepository = communitySortRepository, communityPreferredLanguageRepository = communityPreferredLanguageRepository, + bottomNavItemsRepository = bottomNavItemsRepository, lemmyValueCache = lemmyValueCache, ) diff --git a/domain/identity/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/di/IdentityModule.kt b/domain/identity/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/di/IdentityModule.kt index d6a36bd29..2296fa747 100644 --- a/domain/identity/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/di/IdentityModule.kt +++ b/domain/identity/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/di/IdentityModule.kt @@ -47,6 +47,7 @@ val coreIdentityModule = siteRepository = get(), communitySortRepository = get(), communityPreferredLanguageRepository = get(), + bottomNavItemsRepository = get(), lemmyValueCache = get(), ) } @@ -57,6 +58,7 @@ val coreIdentityModule = notificationCenter = get(), settingsRepository = get(), communitySortRepository = get(), + bottomNavItemsRepository = get(), lemmyValueCache = get(), ) } @@ -69,6 +71,7 @@ val coreIdentityModule = notificationCenter = get(), communitySortRepository = get(), communityPreferredLanguageRepository = get(), + bottomNavItemsRepository = get(), lemmyValueCache = get(), ) } diff --git a/domain/identity/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultLoginUseCase.kt b/domain/identity/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultLoginUseCase.kt index c24328bfe..325d26976 100644 --- a/domain/identity/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultLoginUseCase.kt +++ b/domain/identity/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultLoginUseCase.kt @@ -1,6 +1,8 @@ package com.github.diegoberaldin.raccoonforlemmy.domain.identity.usecase 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.repository.AccountRepository import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.CommunityPreferredLanguageRepository @@ -21,6 +23,7 @@ internal class DefaultLoginUseCase( private val siteRepository: SiteRepository, private val communitySortRepository: CommunitySortRepository, private val communityPreferredLanguageRepository: CommunityPreferredLanguageRepository, + private val bottomNavItemsRepository: BottomNavItemsRepository, private val lemmyValueCache: LemmyValueCache, ) : LoginUseCase { override suspend operator fun invoke( @@ -106,6 +109,9 @@ internal class DefaultLoginUseCase( val newSettings = settingsRepository.getSettings(accountId) settingsRepository.changeCurrentSettings(newSettings) + + val bottomBarSections = bottomNavItemsRepository.get(accountId) + settingsRepository.changeCurrentBottomBarSections(bottomBarSections.toInts()) } } } diff --git a/domain/identity/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultLogoutUseCase.kt b/domain/identity/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultLogoutUseCase.kt index 3c1db380b..861317e03 100644 --- a/domain/identity/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultLogoutUseCase.kt +++ b/domain/identity/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultLogoutUseCase.kt @@ -1,5 +1,7 @@ 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.NotificationCenterEvent import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.AccountRepository @@ -14,6 +16,7 @@ internal class DefaultLogoutUseCase( private val notificationCenter: NotificationCenter, private val settingsRepository: SettingsRepository, private val communitySortRepository: CommunitySortRepository, + private val bottomNavItemsRepository: BottomNavItemsRepository, private val lemmyValueCache: LemmyValueCache, ) : LogoutUseCase { override suspend operator fun invoke() { @@ -31,5 +34,8 @@ internal class DefaultLogoutUseCase( } val anonSettings = settingsRepository.getSettings(null) settingsRepository.changeCurrentSettings(anonSettings) + + val bottomBarSections = bottomNavItemsRepository.get(null) + settingsRepository.changeCurrentBottomBarSections(bottomBarSections.toInts()) } } diff --git a/domain/identity/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultSwitchAccountUseCase.kt b/domain/identity/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultSwitchAccountUseCase.kt index 9c972456b..70d07e6c2 100644 --- a/domain/identity/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultSwitchAccountUseCase.kt +++ b/domain/identity/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/identity/usecase/DefaultSwitchAccountUseCase.kt @@ -1,6 +1,8 @@ package com.github.diegoberaldin.raccoonforlemmy.domain.identity.usecase 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.NotificationCenterEvent import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.AccountModel @@ -19,6 +21,7 @@ internal class DefaultSwitchAccountUseCase( private val settingsRepository: SettingsRepository, private val communitySortRepository: CommunitySortRepository, private val communityPreferredLanguageRepository: CommunityPreferredLanguageRepository, + private val bottomNavItemsRepository: BottomNavItemsRepository, private val lemmyValueCache: LemmyValueCache, ) : SwitchAccountUseCase { override suspend fun invoke(account: AccountModel) { @@ -44,5 +47,8 @@ internal class DefaultSwitchAccountUseCase( val newSettings = settingsRepository.getSettings(accountId) settingsRepository.changeCurrentSettings(newSettings) + + val bottomBarSections = bottomNavItemsRepository.get(accountId) + settingsRepository.changeCurrentBottomBarSections(bottomBarSections.toInts()) } } diff --git a/feature/home/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/home/ui/HomeTab.kt b/feature/home/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/home/ui/HomeTab.kt index b4987666d..14319578f 100644 --- a/feature/home/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/home/ui/HomeTab.kt +++ b/feature/home/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/home/ui/HomeTab.kt @@ -8,6 +8,8 @@ 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.core.navigation.TabNavigationSection +import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toInt import com.github.diegoberaldin.raccoonforlemmy.unit.postlist.PostListScreen object HomeTab : Tab { @@ -18,7 +20,7 @@ object HomeTab : Tab { val title = LocalStrings.current.navigationHome return TabOptions( - index = 0u, + index = TabNavigationSection.Home.toInt().toUShort(), title = title, icon = icon, ) diff --git a/feature/inbox/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/inbox/ui/InboxTab.kt b/feature/inbox/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/inbox/ui/InboxTab.kt index b5d972007..bd5d4a632 100644 --- a/feature/inbox/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/inbox/ui/InboxTab.kt +++ b/feature/inbox/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/inbox/ui/InboxTab.kt @@ -8,6 +8,8 @@ import cafe.adriel.voyager.navigator.tab.Tab import cafe.adriel.voyager.navigator.tab.TabNavigator import cafe.adriel.voyager.navigator.tab.TabOptions 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 object InboxTab : Tab { @@ -17,7 +19,7 @@ object InboxTab : Tab { val icon = rememberVectorPainter(Icons.Default.Inbox) val title = LocalStrings.current.navigationInbox return TabOptions( - index = 3u, + index = TabNavigationSection.Inbox.toInt().toUShort(), title = title, icon = icon, ) diff --git a/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/di/ProfileModule.kt b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/di/ProfileModule.kt index b16cd2cd2..f4a2b6d93 100644 --- a/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/di/ProfileModule.kt +++ b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/di/ProfileModule.kt @@ -31,6 +31,7 @@ val profileTabModule = } factory { ProfileSideMenuViewModel( + settingsRepository = get(), lemmyValueCache = get(), ) } diff --git a/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/menu/ProfileMenuContent.kt b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/menu/ProfileMenuContent.kt index 970397ff9..34f352180 100644 --- a/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/menu/ProfileMenuContent.kt +++ b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/menu/ProfileMenuContent.kt @@ -27,6 +27,7 @@ internal fun ProfileMenuContent( modifier: Modifier = Modifier, isModerator: Boolean = false, canCreateCommunity: Boolean = false, + isBookmarksVisible: Boolean = true, ) { val notificationCenter = remember { getNotificationCenter() } @@ -42,14 +43,16 @@ internal fun ProfileMenuContent( notificationCenter.send(NotificationCenterEvent.ProfileSideMenuAction.ManageSubscriptions) }, ) - SettingsRow( - title = LocalStrings.current.navigationDrawerTitleBookmarks, - icon = Icons.Default.Bookmark, - onTap = - rememberCallback { - notificationCenter.send(NotificationCenterEvent.ProfileSideMenuAction.Bookmarks) - }, - ) + if (isBookmarksVisible) { + SettingsRow( + title = LocalStrings.current.navigationDrawerTitleBookmarks, + icon = Icons.Default.Bookmark, + onTap = + rememberCallback { + notificationCenter.send(NotificationCenterEvent.ProfileSideMenuAction.Bookmarks) + }, + ) + } SettingsRow( title = LocalStrings.current.navigationDrawerTitleDrafts, icon = Icons.Default.Drafts, diff --git a/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/menu/ProfileSideMenuMviModel.kt b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/menu/ProfileSideMenuMviModel.kt index db4f8cde4..8beae4573 100644 --- a/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/menu/ProfileSideMenuMviModel.kt +++ b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/menu/ProfileSideMenuMviModel.kt @@ -11,6 +11,7 @@ interface ProfileSideMenuMviModel : data class State( val isModerator: Boolean = false, val canCreateCommunity: Boolean = false, + val isBookmarksVisible: Boolean = true, ) sealed interface Effect diff --git a/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/menu/ProfileSideMenuScreen.kt b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/menu/ProfileSideMenuScreen.kt index 4aace359d..4bb15e770 100644 --- a/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/menu/ProfileSideMenuScreen.kt +++ b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/menu/ProfileSideMenuScreen.kt @@ -70,6 +70,7 @@ class ProfileSideMenuScreen : Screen { ), isModerator = uiState.isModerator, canCreateCommunity = uiState.canCreateCommunity, + isBookmarksVisible = uiState.isBookmarksVisible, ) } } diff --git a/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/menu/ProfileSideMenuViewModel.kt b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/menu/ProfileSideMenuViewModel.kt index 3f25bff2e..a66cc1b4c 100644 --- a/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/menu/ProfileSideMenuViewModel.kt +++ b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/menu/ProfileSideMenuViewModel.kt @@ -2,6 +2,9 @@ package com.github.diegoberaldin.raccoonforlemmy.feature.profile.menu import cafe.adriel.voyager.core.model.screenModelScope 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 kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn @@ -9,6 +12,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch class ProfileSideMenuViewModel( + private val settingsRepository: SettingsRepository, private val lemmyValueCache: LemmyValueCache, ) : DefaultMviModel( ProfileSideMenuMviModel.State(), @@ -39,6 +43,16 @@ class ProfileSideMenuViewModel( ) } }.launchIn(this) + settingsRepository.currentBottomBarSections + .onEach { sectionIds -> + val isBookmarksInBottomBar = + sectionIds + .toTabNavigationSections() + .contains(TabNavigationSection.Bookmarks) + updateState { + it.copy(isBookmarksVisible = !isBookmarksInBottomBar) + } + }.launchIn(this) } } } diff --git a/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/ui/ProfileTab.kt b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/ui/ProfileTab.kt index fe9dd182a..a607a9057 100644 --- a/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/ui/ProfileTab.kt +++ b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/ui/ProfileTab.kt @@ -8,6 +8,8 @@ import cafe.adriel.voyager.navigator.tab.Tab import cafe.adriel.voyager.navigator.tab.TabNavigator import cafe.adriel.voyager.navigator.tab.TabOptions 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 object ProfileTab : Tab { @@ -16,7 +18,7 @@ object ProfileTab : Tab { val icon = rememberVectorPainter(Icons.Default.AccountCircle) val title = LocalStrings.current.navigationProfile return TabOptions( - index = 2u, + index = TabNavigationSection.Profile.toInt().toUShort(), title = title, icon = icon, ) diff --git a/feature/search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/ui/ExploreTab.kt b/feature/search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/ui/ExploreTab.kt index a5a320a3e..550b9e901 100644 --- a/feature/search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/ui/ExploreTab.kt +++ b/feature/search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/ui/ExploreTab.kt @@ -8,6 +8,8 @@ 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.core.navigation.TabNavigationSection +import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toInt import com.github.diegoberaldin.raccoonforlemmy.unit.explore.ExploreScreen object ExploreTab : Tab { @@ -16,7 +18,7 @@ object ExploreTab : Tab { val icon = rememberVectorPainter(Icons.Default.Explore) val title = LocalStrings.current.navigationSearch return TabOptions( - index = 1u, + index = TabNavigationSection.Explore.toInt().toUShort(), title = title, icon = icon, ) diff --git a/feature/settings/build.gradle.kts b/feature/settings/build.gradle.kts index 48d4ab88f..124e9749c 100644 --- a/feature/settings/build.gradle.kts +++ b/feature/settings/build.gradle.kts @@ -67,6 +67,7 @@ kotlin { implementation(projects.unit.choosecolor) implementation(projects.unit.choosefont) implementation(projects.unit.configureswipeactions) + implementation(projects.unit.configurenavbar) implementation(projects.unit.configurecontentview) implementation(projects.unit.filteredcontents) implementation(projects.unit.medialist) @@ -82,8 +83,14 @@ kotlin { android { namespace = "com.github.diegoberaldin.raccoonforlemmy.feature.settings" - compileSdk = libs.versions.android.targetSdk.get().toInt() + compileSdk = + libs.versions.android.targetSdk + .get() + .toInt() defaultConfig { - minSdk = libs.versions.android.minSdk.get().toInt() + minSdk = + libs.versions.android.minSdk + .get() + .toInt() } } diff --git a/feature/settings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/settings/SettingsTab.kt b/feature/settings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/settings/SettingsTab.kt index 65fc72a4c..45d97ef36 100644 --- a/feature/settings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/settings/SettingsTab.kt +++ b/feature/settings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/settings/SettingsTab.kt @@ -8,6 +8,8 @@ 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.core.navigation.TabNavigationSection +import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toInt import com.github.diegoberaldin.raccoonforlemmy.feature.settings.main.SettingsScreen object SettingsTab : Tab { @@ -18,7 +20,7 @@ object SettingsTab : Tab { val title = LocalStrings.current.navigationSettings return TabOptions( - index = 4u, + index = TabNavigationSection.Settings.toInt().toUShort(), title = title, icon = icon, ) diff --git a/feature/settings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/settings/advanced/AdvancedSettingsScreen.kt b/feature/settings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/settings/advanced/AdvancedSettingsScreen.kt index 2ccae04c4..174e1e375 100644 --- a/feature/settings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/settings/advanced/AdvancedSettingsScreen.kt +++ b/feature/settings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/settings/advanced/AdvancedSettingsScreen.kt @@ -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.toLocalDp 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.onEach import kotlinx.coroutines.launch @@ -159,6 +160,7 @@ class AdvancedSettingsScreen : Screen { Modifier .padding( top = padding.calculateTopPadding(), + bottom = padding.calculateBottomPadding(), ).nestedScroll(scrollBehavior.nestedScrollConnection), ) { Column( @@ -495,6 +497,14 @@ class AdvancedSettingsScreen : Screen { title = LocalStrings.current.settingsTitleExperimental, icon = Icons.Default.Science, ) + SettingsRow( + title = LocalStrings.current.settingsItemConfigureBottomNavigationBar, + disclosureIndicator = true, + onTap = + rememberCallback { + navigationCoordinator.pushScreen(ConfigureNavBarScreen()) + }, + ) if (uiState.alternateMarkdownRenderingItemVisible) { // alternate Markdown rendering SettingsSwitchRow( diff --git a/feature/settings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/settings/main/SettingsScreen.kt b/feature/settings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/settings/main/SettingsScreen.kt index fdba49cb0..12a0fd99e 100644 --- a/feature/settings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/settings/main/SettingsScreen.kt +++ b/feature/settings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/settings/main/SettingsScreen.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -154,8 +155,8 @@ class SettingsScreen : Screen { Modifier .padding( top = padding.calculateTopPadding(), - bottom = padding.calculateBottomPadding(), - ).nestedScroll(scrollBehavior.nestedScrollConnection), + ).navigationBarsPadding() + .nestedScroll(scrollBehavior.nestedScrollConnection), ) { Column( modifier = Modifier.fillMaxSize().verticalScroll(scrollState), diff --git a/settings.gradle.kts b/settings.gradle.kts index 49387ba4a..23016a93b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -61,6 +61,7 @@ include(":unit:choosefont") include(":unit:communitydetail") include(":unit:communityinfo") include(":unit:configurecontentview") +include(":unit:configurenavbar") include(":unit:configureswipeactions") include(":unit:createcomment") include(":unit:createpost") diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 6c1c51a8e..0109d20b6 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -76,6 +76,7 @@ kotlin { implementation(projects.unit.communitydetail) implementation(projects.unit.communityinfo) implementation(projects.unit.configurecontentview) + implementation(projects.unit.configurenavbar) implementation(projects.unit.configureswipeactions) implementation(projects.unit.createcomment) implementation(projects.unit.createpost) diff --git a/shared/src/androidMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/di/DiHelper.kt b/shared/src/androidMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/di/DiHelper.kt index 0616632b9..f683804c4 100644 --- a/shared/src/androidMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/di/DiHelper.kt +++ b/shared/src/androidMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/di/DiHelper.kt @@ -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.communityinfo.di.communityInfoModule 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.createcomment.di.createCommentModule import com.github.diegoberaldin.raccoonforlemmy.unit.createpost.di.createPostModule @@ -119,5 +120,6 @@ val sharedHelperModule = moderateWithReasonModule, acknowledgementsModule, mediaListModule, + configureNavBarModule, ) } diff --git a/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/App.kt b/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/App.kt index e2548b50a..c3d22743d 100644 --- a/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/App.kt +++ b/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/App.kt @@ -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.DrawerEvent 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.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.getSettingsRepository import com.github.diegoberaldin.raccoonforlemmy.core.preferences.di.getAppConfigStore @@ -108,9 +110,12 @@ fun App(onLoadingFinished: () -> Unit = {}) { val scope = rememberCoroutineScope() val subscriptionsCache = remember { getSubscriptionsCache() } val appConfigStore = remember { getAppConfigStore() } + val bottomNavItemsRepository = remember { getBottomNavItemsRepository() } 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 seedColor = if (currentSettings.randomThemeColor) { @@ -122,8 +127,8 @@ fun App(onLoadingFinished: () -> Unit = {}) { currentSettings.customSeedColor } settingsRepository.changeCurrentSettings(currentSettings.copy(customSeedColor = seedColor)) - val lastActiveAccount = accountRepository.getActive() - val lastInstance = lastActiveAccount?.instance?.takeIf { it.isNotEmpty() } + val bottomBarSections = bottomNavItemsRepository.get(accountId) + settingsRepository.changeCurrentBottomBarSections(bottomBarSections.toInts()) if (lastInstance != null) { apiConfigurationRepository.changeInstance(lastInstance) } @@ -132,6 +137,7 @@ fun App(onLoadingFinished: () -> Unit = {}) { appConfigStore.initialize() hasBeenInitialized = true + launch { delay(50) onLoadingFinished() diff --git a/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/MainScreen.kt b/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/MainScreen.kt index f811fa5a3..761ba8eab 100644 --- a/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/MainScreen.kt +++ b/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/MainScreen.kt @@ -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.notifications.NotificationCenterEvent 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.settings.main.SettingsScreen import com.github.diegoberaldin.raccoonforlemmy.navigation.TabNavigationItem +import com.github.diegoberaldin.raccoonforlemmy.navigation.toTab import com.github.diegoberaldin.raccoonforlemmy.unit.manageaccounts.ManageAccountsScreen import kotlinx.coroutines.delay import kotlinx.coroutines.flow.drop @@ -277,7 +277,8 @@ internal object MainScreen : Screen { ), 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( section = section, withText = titleVisible, @@ -287,15 +288,17 @@ internal object MainScreen : Screen { } else { null }, - onClick = - rememberCallbackArgs { tab -> - tabNavigator.current = tab - navigationCoordinator.setCurrentSection(section) - }, - onLongPress = - rememberCallbackArgs { tab -> - handleOnLongPress(tab, section) - }, + onClick = { + val section = uiState.bottomBarSections[idx] + val tab = section.toTab() + tabNavigator.current = tab + navigationCoordinator.setCurrentSection(section) + }, + onLongPress = { + val section = uiState.bottomBarSections[idx] + val tab = section.toTab() + handleOnLongPress(tab, section) + }, ) } } diff --git a/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/MainViewModel.kt b/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/MainViewModel.kt index 6c14d57c8..1b627ceee 100644 --- a/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/MainViewModel.kt +++ b/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/MainViewModel.kt @@ -2,8 +2,7 @@ package com.github.diegoberaldin.raccoonforlemmy 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.persistence.repository.AccountRepository +import com.github.diegoberaldin.raccoonforlemmy.core.navigation.toTabNavigationSections import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository import com.github.diegoberaldin.raccoonforlemmy.domain.inbox.InboxCoordinator @@ -21,8 +20,6 @@ class MainViewModel( private val identityRepository: IdentityRepository, private val settingRepository: SettingsRepository, private val userRepository: UserRepository, - private val accountRepository: AccountRepository, - private val bottomNavItemsRepository: BottomNavItemsRepository, private val notificationChecker: InboxNotificationChecker, private val lemmyValueCache: LemmyValueCache, ) : DefaultMviModel( @@ -53,17 +50,20 @@ class MainViewModel( notificationChecker.stop() } }.launchIn(this) - settingRepository.currentSettings .onEach { updateCustomProfileIcon() }.launchIn(this) + settingRepository.currentBottomBarSections + .onEach { sectionIds -> + val sections = sectionIds.toTabNavigationSections() + updateState { it.copy(bottomBarSections = sections) } + }.launchIn(this) identityRepository.isLogged .onEach { isLogged -> updateState { it.copy(isLogged = isLogged ?: false) } updateCustomProfileIcon() - refreshBottomNavigationItems() }.launchIn(this) } } @@ -106,12 +106,4 @@ class MainViewModel( emitEffect(MainMviModel.Effect.ReadAllInboxSuccess) } } - - private suspend fun refreshBottomNavigationItems() { - val accountId = accountRepository.getActive()?.id - val sections = bottomNavItemsRepository.get(accountId) - updateState { - it.copy(bottomBarSections = sections) - } - } } diff --git a/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/di/SharedModule.kt b/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/di/SharedModule.kt index 045143c99..77109f762 100644 --- a/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/di/SharedModule.kt +++ b/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/di/SharedModule.kt @@ -14,8 +14,6 @@ internal val internalSharedModule = identityRepository = get(), settingRepository = get(), userRepository = get(), - accountRepository = get(), - bottomNavItemsRepository = get(), notificationChecker = get(), lemmyValueCache = get(), ) diff --git a/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/navigation/BookmarksTab.kt b/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/navigation/BookmarksTab.kt index 49759592c..1a5173ac1 100644 --- a/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/navigation/BookmarksTab.kt +++ b/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/navigation/BookmarksTab.kt @@ -8,6 +8,8 @@ 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.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.FilteredContentsType import com.github.diegoberaldin.raccoonforlemmy.unit.filteredcontents.toInt @@ -20,7 +22,7 @@ object BookmarksTab : Tab { val title = LocalStrings.current.navigationDrawerTitleBookmarks return TabOptions( - index = 5u, + index = TabNavigationSection.Bookmarks.toInt().toUShort(), title = title, icon = icon, ) diff --git a/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/navigation/TabNavigationItem.kt b/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/navigation/TabNavigationItem.kt index b61fe590b..c4ca78c0c 100644 --- a/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/navigation/TabNavigationItem.kt +++ b/shared/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/navigation/TabNavigationItem.kt @@ -42,8 +42,8 @@ internal fun RowScope.TabNavigationItem( section: TabNavigationSection, withText: Boolean = true, customIconUrl: String? = null, - onClick: ((Tab) -> Unit)? = null, - onLongPress: ((Tab) -> Unit)? = null, + onClick: (() -> Unit)? = null, + onLongPress: (() -> Unit)? = null, ) { val navigationCoordinator = remember { getNavigationCoordinator() } val unread by navigationCoordinator.inboxUnread.collectAsState() @@ -51,10 +51,6 @@ internal fun RowScope.TabNavigationItem( val interactionSource = remember { MutableInteractionSource() } val tab = section.toTab() - fun handleClick() { - onClick?.invoke(tab) - } - val pointerInputModifier = Modifier.pointerInput(Unit) { detectTapGestures( @@ -65,16 +61,18 @@ internal fun RowScope.TabNavigationItem( interactionSource.emit(PressInteraction.Release(press)) }, onTap = { - handleClick() + onClick?.invoke() }, onLongPress = { - onLongPress?.invoke(tab) + onLongPress?.invoke() }, ) } NavigationBarItem( - onClick = ::handleClick, + onClick = { + onClick?.invoke() + }, selected = section == currentSection, interactionSource = interactionSource, icon = { @@ -136,7 +134,7 @@ internal fun RowScope.TabNavigationItem( ) } -private fun TabNavigationSection.toTab(): Tab = +internal fun TabNavigationSection.toTab(): Tab = when (this) { TabNavigationSection.Explore -> ExploreTab TabNavigationSection.Profile -> ProfileTab diff --git a/shared/src/iosMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/di/DiHelper.kt b/shared/src/iosMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/di/DiHelper.kt index 07d96c00b..c194b4c7c 100644 --- a/shared/src/iosMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/di/DiHelper.kt +++ b/shared/src/iosMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/di/DiHelper.kt @@ -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.communityinfo.di.communityInfoModule 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.createcomment.di.createCommentModule import com.github.diegoberaldin.raccoonforlemmy.unit.createpost.di.createPostModule @@ -122,6 +123,7 @@ fun initKoin() { moderateWithReasonModule, acknowledgementsModule, mediaListModule, + configureNavBarModule, ) } diff --git a/unit/accountsettings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/accountsettings/AccountSettingsScreen.kt b/unit/accountsettings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/accountsettings/AccountSettingsScreen.kt index e1eaa9b97..1bfac9024 100644 --- a/unit/accountsettings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/accountsettings/AccountSettingsScreen.kt +++ b/unit/accountsettings/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/accountsettings/AccountSettingsScreen.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio 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.layout.size import androidx.compose.foundation.rememberScrollState @@ -110,17 +111,18 @@ class AccountSettingsScreen : Screen { var confirmBackWithUnsavedChangesDialog by remember { mutableStateOf(false) } LaunchedEffect(model) { - model.effects.onEach { evt -> - when (evt) { - AccountSettingsMviModel.Effect.Failure -> { - snackbarHostState.showSnackbar(errorMessage) - } + model.effects + .onEach { evt -> + when (evt) { + AccountSettingsMviModel.Effect.Failure -> { + snackbarHostState.showSnackbar(errorMessage) + } - AccountSettingsMviModel.Effect.Success -> { - snackbarHostState.showSnackbar(successMessage) + AccountSettingsMviModel.Effect.Success -> { + snackbarHostState.showSnackbar(successMessage) + } } - } - }.launchIn(this) + }.launchIn(this) } DisposableEffect(key) { @@ -214,8 +216,7 @@ class AccountSettingsScreen : Screen { Modifier .padding( top = padding.calculateTopPadding(), - bottom = Spacing.m, - ) + ).navigationBarsPadding() .then( if (settings.hideNavigationBarWhileScrolling) { Modifier.nestedScroll(scrollBehavior.nestedScrollConnection) diff --git a/unit/configurenavbar/build.gradle.kts b/unit/configurenavbar/build.gradle.kts new file mode 100644 index 000000000..bff8e7c30 --- /dev/null +++ b/unit/configurenavbar/build.gradle.kts @@ -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() + } +} diff --git a/unit/configurenavbar/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/configurenavbar/ConfigureNavBarMviModel.kt b/unit/configurenavbar/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/configurenavbar/ConfigureNavBarMviModel.kt new file mode 100644 index 000000000..c7b017403 --- /dev/null +++ b/unit/configurenavbar/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/configurenavbar/ConfigureNavBarMviModel.kt @@ -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, + 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 = emptyList(), + val availableSections: List = emptyList(), + val hasUnsavedChanges: Boolean = false, + ) + + sealed interface Effect +} diff --git a/unit/configurenavbar/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/configurenavbar/ConfigureNavBarScreen.kt b/unit/configurenavbar/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/configurenavbar/ConfigureNavBarScreen.kt new file mode 100644 index 000000000..62576360f --- /dev/null +++ b/unit/configurenavbar/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/configurenavbar/ConfigureNavBarScreen.kt @@ -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() + 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) + }, + ) + } + } +} diff --git a/unit/configurenavbar/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/configurenavbar/ConfigureNavBarViewModel.kt b/unit/configurenavbar/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/configurenavbar/ConfigureNavBarViewModel.kt new file mode 100644 index 000000000..001f30adf --- /dev/null +++ b/unit/configurenavbar/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/configurenavbar/ConfigureNavBarViewModel.kt @@ -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( + 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 = emptyList()): List { + 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()) + } + } +} diff --git a/unit/configurenavbar/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/configurenavbar/composable/ConfigureAddAction.kt b/unit/configurenavbar/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/configurenavbar/composable/ConfigureAddAction.kt new file mode 100644 index 000000000..80ff14cb3 --- /dev/null +++ b/unit/configurenavbar/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/configurenavbar/composable/ConfigureAddAction.kt @@ -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, + ) + } +} diff --git a/unit/configurenavbar/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/configurenavbar/composable/ConfigureNavBarItem.kt b/unit/configurenavbar/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/configurenavbar/composable/ConfigureNavBarItem.kt new file mode 100644 index 000000000..a326bae9b --- /dev/null +++ b/unit/configurenavbar/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/configurenavbar/composable/ConfigureNavBarItem.kt @@ -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() + }, + ) + } + } + } +} diff --git a/unit/configurenavbar/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/configurenavbar/di/ConfigureNavBarModule.kt b/unit/configurenavbar/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/configurenavbar/di/ConfigureNavBarModule.kt new file mode 100644 index 000000000..ca1323507 --- /dev/null +++ b/unit/configurenavbar/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/configurenavbar/di/ConfigureNavBarModule.kt @@ -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 { + ConfigureNavBarViewModel( + accountRepository = get(), + identityRepository = get(), + bottomNavItemsRepository = get(), + settingsRepository = get(), + hapticFeedback = get(), + notificationCenter = get(), + ) + } + } diff --git a/unit/drawer/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/drawer/ModalDrawerContent.kt b/unit/drawer/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/drawer/ModalDrawerContent.kt index a9f9d0ecb..6f268743e 100644 --- a/unit/drawer/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/drawer/ModalDrawerContent.kt +++ b/unit/drawer/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/drawer/ModalDrawerContent.kt @@ -205,22 +205,24 @@ object ModalDrawerContent : Tab { ) } } - item { - DrawerShortcut( - title = LocalStrings.current.navigationSettings, - icon = Icons.Default.Settings, - onSelected = - rememberCallback(coordinator) { - scope.launch { - focusManager.clearFocus() - navigationCoordinator.popUntilRoot() - coordinator.toggleDrawer() - delay(50) + if (uiState.isSettingsVisible) { + item { + DrawerShortcut( + title = LocalStrings.current.navigationSettings, + icon = Icons.Default.Settings, + onSelected = + rememberCallback(coordinator) { + scope.launch { + focusManager.clearFocus() + navigationCoordinator.popUntilRoot() + coordinator.toggleDrawer() + delay(50) - coordinator.sendEvent(DrawerEvent.OpenSettings) - } - }, - ) + coordinator.sendEvent(DrawerEvent.OpenSettings) + } + }, + ) + } } } diff --git a/unit/drawer/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/drawer/ModalDrawerMviModel.kt b/unit/drawer/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/drawer/ModalDrawerMviModel.kt index 51ca0747b..3342e5598 100644 --- a/unit/drawer/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/drawer/ModalDrawerMviModel.kt +++ b/unit/drawer/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/drawer/ModalDrawerMviModel.kt @@ -36,6 +36,7 @@ interface ModalDrawerMviModel : val searchText: String = "", val isFiltering: Boolean = false, val enableToggleFavorite: Boolean = false, + val isSettingsVisible: Boolean = true, ) sealed interface Effect diff --git a/unit/drawer/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/drawer/ModalDrawerViewModel.kt b/unit/drawer/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/drawer/ModalDrawerViewModel.kt index de574f7aa..5fa7437ff 100644 --- a/unit/drawer/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/drawer/ModalDrawerViewModel.kt +++ b/unit/drawer/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/drawer/ModalDrawerViewModel.kt @@ -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.CommunityPaginationSpecification 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.NotificationCenterEvent import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.FavoriteCommunityModel @@ -98,6 +100,14 @@ class ModalDrawerViewModel( ) } }.launchIn(this) + settingsRepository.currentBottomBarSections + .onEach { sectionIds -> + val isSettingsInBottomBar = + sectionIds.toTabNavigationSections().contains(TabNavigationSection.Settings) + updateState { + it.copy(isSettingsVisible = !isSettingsInBottomBar) + } + }.launchIn(this) uiState .map { it.searchText }