From 88b6e64e98dd5fac15f2cfcddbae3495583aa233 Mon Sep 17 00:00:00 2001 From: Gabriel Luong Date: Thu, 11 Apr 2024 20:46:36 +0000 Subject: [PATCH] Bug 1885602 - Part 4: Implement navigating to the settings from the menu header for the menu redesign r=android-reviewers,007,matt-tighe - Introduces a MenuStore with a `isBookmarked` placeholder state. - Introduces a MenuNavigationMiddleware to handle menu navigation actions. - Implements navigating to the settings from the settings button in the menu header. Differential Revision: https://phabricator.services.mozilla.com/D207055 --- .../fenix/components/menu/MenuDialogFragment.kt | 22 +++++++- .../menu/middleware/MenuNavigationMiddleware.kt | 47 ++++++++++++++++++ .../fenix/components/menu/store/MenuAction.kt | 31 ++++++++++++ .../fenix/components/menu/store/MenuState.kt | 16 ++++++ .../fenix/components/menu/store/MenuStore.kt | 28 +++++++++++ .../menu/MenuNavigationMiddlewareTest.kt | 58 ++++++++++++++++++++++ .../mozilla/fenix/components/menu/MenuStoreTest.kt | 37 ++++++++++++++ 7 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/middleware/MenuNavigationMiddleware.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuAction.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuState.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuStore.kt create mode 100644 mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/menu/MenuNavigationMiddlewareTest.kt create mode 100644 mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/menu/MenuStoreTest.kt diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/MenuDialogFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/MenuDialogFragment.kt index 4072e188bb84..baed16e791c4 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/MenuDialogFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/MenuDialogFragment.kt @@ -11,12 +11,18 @@ import android.view.View import android.view.ViewGroup import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.navigation.fragment.findNavController import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialogFragment import org.mozilla.fenix.R import org.mozilla.fenix.components.accounts.AccountState +import org.mozilla.fenix.components.lazyStore import org.mozilla.fenix.components.menu.compose.MenuDialog import org.mozilla.fenix.components.menu.compose.MenuDialogBottomSheet +import org.mozilla.fenix.components.menu.middleware.MenuNavigationMiddleware +import org.mozilla.fenix.components.menu.store.MenuAction +import org.mozilla.fenix.components.menu.store.MenuState +import org.mozilla.fenix.components.menu.store.MenuStore import org.mozilla.fenix.theme.FirefoxTheme /** @@ -24,6 +30,18 @@ import org.mozilla.fenix.theme.FirefoxTheme */ class MenuDialogFragment : BottomSheetDialogFragment() { + private val store by lazyStore { viewModelScope -> + MenuStore( + initialState = MenuState(), + middleware = listOf( + MenuNavigationMiddleware( + navController = findNavController(), + scope = viewModelScope, + ), + ), + ) + } + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = super.onCreateDialog(savedInstanceState).apply { setOnShowListener { @@ -50,7 +68,9 @@ class MenuDialogFragment : BottomSheetDialogFragment() { accountState = AccountState.NO_ACCOUNT, onSignInButtonClick = {}, onHelpButtonClick = {}, - onSettingsButtonClick = {}, + onSettingsButtonClick = { + store.dispatch(MenuAction.Navigate.Settings) + }, ) } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/middleware/MenuNavigationMiddleware.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/middleware/MenuNavigationMiddleware.kt new file mode 100644 index 000000000000..0f2e5add6efb --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/middleware/MenuNavigationMiddleware.kt @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.components.menu.middleware + +import androidx.navigation.NavController +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import mozilla.components.lib.state.Middleware +import mozilla.components.lib.state.MiddlewareContext +import org.mozilla.fenix.R +import org.mozilla.fenix.components.menu.MenuDialogFragmentDirections +import org.mozilla.fenix.components.menu.store.MenuAction +import org.mozilla.fenix.components.menu.store.MenuState +import org.mozilla.fenix.components.menu.store.MenuStore +import org.mozilla.fenix.ext.nav + +/** + * [Middleware] implementation for handling navigating events based on [MenuAction]s that are + * dispatched to the [MenuStore]. + */ +class MenuNavigationMiddleware( + private val navController: NavController, + private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main), +) : Middleware { + + override fun invoke( + context: MiddlewareContext, + next: (MenuAction) -> Unit, + action: MenuAction, + ) { + next(action) + + scope.launch { + when (action) { + is MenuAction.Navigate.Settings -> navController.nav( + R.id.menuDialogFragment, + MenuDialogFragmentDirections.actionGlobalSettingsFragment(), + ) + + else -> Unit + } + } + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuAction.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuAction.kt new file mode 100644 index 000000000000..ca289b8e4d08 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuAction.kt @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.components.menu.store + +import mozilla.components.lib.state.Action + +/** + * Actions to dispatch through the [MenuStore] to modify the [MenuState]. + */ +sealed class MenuAction : Action { + + /** + * Updates whether or not the current selected tab is bookmarked. + * + * @property isBookmarked Whether or not the current selected is bookmarked. + */ + data class UpdateBookmarked(val isBookmarked: Boolean) : MenuAction() + + /** + * [MenuAction] dispatched when a navigation event occurs for a specific destination. + */ + sealed class Navigate : MenuAction() { + + /** + * [Navigate] action dispatched when navigating to the settings. + */ + data object Settings : Navigate() + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuState.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuState.kt new file mode 100644 index 000000000000..0f5b7a61e32c --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuState.kt @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.components.menu.store + +import mozilla.components.lib.state.State + +/** + * Value type that represents the state of the menu. + * + * @property isBookmarked Whether or not the current selected tab is bookmarked. + */ +data class MenuState( + val isBookmarked: Boolean = false, +) : State diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuStore.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuStore.kt new file mode 100644 index 000000000000..14cca54e61fb --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuStore.kt @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.components.menu.store + +import mozilla.components.lib.state.Middleware +import mozilla.components.lib.state.Store + +/** + * The [Store] for holding the [MenuState] and applying [MenuAction]s. + */ +class MenuStore( + initialState: MenuState, + middleware: List> = listOf(), +) : + Store( + initialState = initialState, + reducer = ::reducer, + middleware = middleware, + ) + +private fun reducer(state: MenuState, action: MenuAction): MenuState { + return when (action) { + is MenuAction.UpdateBookmarked -> state.copy(isBookmarked = action.isBookmarked) + is MenuAction.Navigate -> state + } +} diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/menu/MenuNavigationMiddlewareTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/menu/MenuNavigationMiddlewareTest.kt new file mode 100644 index 000000000000..99cde563088f --- /dev/null +++ b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/menu/MenuNavigationMiddlewareTest.kt @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.components.menu + +import androidx.navigation.NavController +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.test.runTest +import mozilla.components.support.test.rule.MainCoroutineRule +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mozilla.fenix.R +import org.mozilla.fenix.components.menu.middleware.MenuNavigationMiddleware +import org.mozilla.fenix.components.menu.store.MenuAction +import org.mozilla.fenix.components.menu.store.MenuState +import org.mozilla.fenix.components.menu.store.MenuStore +import org.mozilla.fenix.ext.nav + +class MenuNavigationMiddlewareTest { + + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val scope = coroutinesTestRule.scope + + private val navController: NavController = mockk(relaxed = true) + + private lateinit var store: MenuStore + private lateinit var middleware: MenuNavigationMiddleware + + @Before + fun setup() { + middleware = MenuNavigationMiddleware( + navController = navController, + scope = scope, + ) + store = MenuStore( + initialState = MenuState(), + middleware = listOf( + middleware, + ), + ) + } + + @Test + fun `WHEN navigate to settings action is dispatched THEN navigate to settings`() = runTest { + store.dispatch(MenuAction.Navigate.Settings).join() + + verify { + navController.nav( + R.id.menuDialogFragment, + MenuDialogFragmentDirections.actionGlobalSettingsFragment(), + ) + } + } +} diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/menu/MenuStoreTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/menu/MenuStoreTest.kt new file mode 100644 index 000000000000..6c6bd73b50ec --- /dev/null +++ b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/menu/MenuStoreTest.kt @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.components.menu + +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.mozilla.fenix.components.menu.store.MenuAction +import org.mozilla.fenix.components.menu.store.MenuState +import org.mozilla.fenix.components.menu.store.MenuStore + +class MenuStoreTest { + + private lateinit var state: MenuState + private lateinit var store: MenuStore + + @Before + fun setup() { + state = MenuState() + store = MenuStore(initialState = state) + } + + @Test + fun `WHEN update bookmarked action is dispatched THEN bookmarked state is updated`() = runTest { + assertFalse(store.state.isBookmarked) + + store.dispatch(MenuAction.UpdateBookmarked(isBookmarked = true)).join() + assertTrue(store.state.isBookmarked) + + store.dispatch(MenuAction.UpdateBookmarked(isBookmarked = false)).join() + assertFalse(store.state.isBookmarked) + } +} -- 2.11.4.GIT