commit 537c897dbf5950bca1f11ef3ad02bd5027148ced
parent edd5fe7e2b064c307992455c1c880087d688298d
Author: Cristina Horotan <chorotan@mozilla.com>
Date: Mon, 6 Oct 2025 19:23:04 +0000
Bug 1984868 - Added a sort menu to the select folder screen r=android-reviewers,boek
Differential Revision: https://phabricator.services.mozilla.com/D265341
Diffstat:
7 files changed, 167 insertions(+), 20 deletions(-)
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/bookmarks/BookmarksAction.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/bookmarks/BookmarksAction.kt
@@ -118,6 +118,16 @@ internal sealed class SelectFolderAction : BookmarksAction {
data object ViewAppeared : SelectFolderAction()
data class FoldersLoaded(val folders: List<SelectFolderItem>) : SelectFolderAction()
data class ItemClicked(val folder: SelectFolderItem) : SelectFolderAction()
+
+ internal sealed class SortMenu : SelectFolderAction() {
+ data object SortMenuButtonClicked : SortMenu()
+ data object SortMenuDismissed : SortMenu()
+ data object CustomSortClicked : SortMenu()
+ data object NewestClicked : SortMenu()
+ data object OldestClicked : SortMenu()
+ data object AtoZClicked : SortMenu()
+ data object ZtoAClicked : SortMenu()
+ }
}
internal sealed class OpenTabsConfirmationDialogAction : BookmarksAction {
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/bookmarks/BookmarksMiddleware.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/bookmarks/BookmarksMiddleware.kt
@@ -369,6 +369,10 @@ internal class BookmarksMiddleware(
}
}
}
+ is SelectFolderAction.SortMenu -> scope.launch {
+ context.store.tryDispatchLoadFolders()
+ saveBookmarkSortOrder(context.store.state.sortOrder)
+ }
is InitEditLoaded,
SnackbarAction.Undo,
is OpenTabsConfirmationDialogAction.Present,
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/bookmarks/BookmarksReducer.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/bookmarks/BookmarksReducer.kt
@@ -103,6 +103,7 @@ private fun BookmarksState.handleSelectFolderAction(action: SelectFolderAction):
outerSelectionGuid = BookmarkRoot.Mobile.id,
),
)
+ is SelectFolderAction.SortMenu -> this.handleSortMenuAction(action)
SelectFolderAction.ViewAppeared -> this
}
@@ -308,27 +309,45 @@ private fun BookmarksState.respondToBackClick(): BookmarksState = when {
else -> this
}
-private fun BookmarksState.handleSortMenuAction(action: BookmarksListMenuAction.SortMenu): BookmarksState =
+private fun BookmarksState.handleSortMenuAction(action: BookmarksAction): BookmarksState =
when (action) {
- BookmarksListMenuAction.SortMenu.SortMenuButtonClicked -> copy(
+ BookmarksListMenuAction.SortMenu.SortMenuButtonClicked,
+ SelectFolderAction.SortMenu.SortMenuButtonClicked,
+ -> copy(
sortMenuShown = !sortMenuShown,
)
- BookmarksListMenuAction.SortMenu.SortMenuDismissed -> copy(
+ BookmarksListMenuAction.SortMenu.SortMenuDismissed,
+ SelectFolderAction.SortMenu.SortMenuDismissed,
+ -> copy(
sortMenuShown = false,
)
- BookmarksListMenuAction.SortMenu.CustomSortClicked -> copy(sortOrder = BookmarksListSortOrder.Positional)
- BookmarksListMenuAction.SortMenu.NewestClicked -> copy(
+ BookmarksListMenuAction.SortMenu.CustomSortClicked,
+ SelectFolderAction.SortMenu.CustomSortClicked,
+ -> copy(
+ sortOrder = BookmarksListSortOrder.Positional,
+ )
+ BookmarksListMenuAction.SortMenu.NewestClicked,
+ SelectFolderAction.SortMenu.NewestClicked,
+ -> copy(
sortOrder = BookmarksListSortOrder.Created(true),
)
- BookmarksListMenuAction.SortMenu.OldestClicked -> copy(
+ BookmarksListMenuAction.SortMenu.OldestClicked,
+ SelectFolderAction.SortMenu.OldestClicked,
+ -> copy(
sortOrder = BookmarksListSortOrder.Created(false),
)
- BookmarksListMenuAction.SortMenu.AtoZClicked -> copy(
+ BookmarksListMenuAction.SortMenu.AtoZClicked,
+ SelectFolderAction.SortMenu.AtoZClicked,
+ -> copy(
sortOrder = BookmarksListSortOrder.Alphabetical(true),
)
- BookmarksListMenuAction.SortMenu.ZtoAClicked -> copy(
+
+ BookmarksListMenuAction.SortMenu.ZtoAClicked,
+ SelectFolderAction.SortMenu.ZtoAClicked,
+ -> copy(
sortOrder = BookmarksListSortOrder.Alphabetical(false),
)
+ else -> copy(sortOrder = BookmarksListSortOrder.Positional)
}.let {
it.copy(
bookmarkItems = it.bookmarkItems.sortedWith(it.sortOrder.comparator),
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/bookmarks/BookmarksScreen.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/bookmarks/BookmarksScreen.kt
@@ -918,12 +918,7 @@ private fun SelectFolderScreen(
Scaffold(
topBar = {
- SelectFolderTopBar(
- onBackClick = { store.dispatch(BackClicked) },
- onNewFolderClick = showNewFolderButton.takeIf { it }?.let {
- { store.dispatch(AddFolderClicked) }
- },
- )
+ SelectFolderTopBar(store = store)
},
containerColor = FirefoxTheme.colors.layer1,
) { paddingValues ->
@@ -997,10 +992,10 @@ private fun NewFolderListItem(onClick: () -> Unit) {
}
@Composable
-private fun SelectFolderTopBar(
- onBackClick: () -> Unit,
- onNewFolderClick: (() -> Unit)?,
-) {
+private fun SelectFolderTopBar(store: BookmarksStore) {
+ val onNewFolderClick = store.state.showNewFolderButton.takeIf { it }?.let {
+ { store.dispatch(AddFolderClicked) }
+ }
TopAppBar(
colors = TopAppBarDefaults.topAppBarColors(containerColor = FirefoxTheme.colors.layer1),
title = {
@@ -1011,7 +1006,7 @@ private fun SelectFolderTopBar(
)
},
navigationIcon = {
- IconButton(onClick = onBackClick) {
+ IconButton(onClick = { store.dispatch(BackClicked) }) {
Icon(
painter = painterResource(iconsR.drawable.mozac_ic_back_24),
contentDescription = stringResource(R.string.bookmark_navigate_back_button_content_description),
@@ -1020,8 +1015,22 @@ private fun SelectFolderTopBar(
}
},
actions = {
+ Box {
+ IconButton(onClick = {
+ store.dispatch(BookmarksListMenuAction.SortMenu.SortMenuButtonClicked)
+ }) {
+ Icon(
+ painter = painterResource(iconsR.drawable.mozac_ic_filter),
+ contentDescription = stringResource(
+ R.string.bookmark_sort_menu_content_desc,
+ ),
+ )
+ }
+
+ SelectFolderSortOverflowMenu(store = store)
+ }
if (onNewFolderClick != null) {
- IconButton(onClick = onNewFolderClick) {
+ IconButton(onClick = { onNewFolderClick }) {
Icon(
painter = painterResource(iconsR.drawable.mozac_ic_folder_add_24),
contentDescription = stringResource(
@@ -1039,6 +1048,47 @@ private fun SelectFolderTopBar(
)
}
+@Composable
+private fun SelectFolderSortOverflowMenu(store: BookmarksStore) {
+ val showMenu by store.observeAsState(store.state.sortMenuShown) {
+ store.state.sortMenuShown
+ }
+ val sortOrder by store.observeAsState(store.state.sortOrder) { store.state.sortOrder }
+
+ val menuItems = listOf(
+ MenuItem.CheckableItem(
+ text = Text.Resource(R.string.bookmark_sort_menu_custom),
+ isChecked = sortOrder is BookmarksListSortOrder.Positional,
+ onClick = { store.dispatch(SelectFolderAction.SortMenu.CustomSortClicked) },
+ ),
+ MenuItem.CheckableItem(
+ text = Text.Resource(R.string.bookmark_sort_menu_newest),
+ isChecked = sortOrder == BookmarksListSortOrder.Created(ascending = true),
+ onClick = { store.dispatch(SelectFolderAction.SortMenu.NewestClicked) },
+ ),
+ MenuItem.CheckableItem(
+ text = Text.Resource(R.string.bookmark_sort_menu_oldest),
+ isChecked = sortOrder == BookmarksListSortOrder.Created(ascending = false),
+ onClick = { store.dispatch(SelectFolderAction.SortMenu.OldestClicked) },
+ ),
+ MenuItem.CheckableItem(
+ text = Text.Resource(R.string.bookmark_sort_menu_a_to_z),
+ isChecked = sortOrder == BookmarksListSortOrder.Alphabetical(ascending = true),
+ onClick = { store.dispatch(SelectFolderAction.SortMenu.AtoZClicked) },
+ ),
+ MenuItem.CheckableItem(
+ text = Text.Resource(R.string.bookmark_sort_menu_z_to_a),
+ isChecked = sortOrder == BookmarksListSortOrder.Alphabetical(ascending = false),
+ onClick = { store.dispatch(SelectFolderAction.SortMenu.ZtoAClicked) },
+ ),
+ )
+ DropdownMenu(
+ menuItems = menuItems,
+ expanded = showMenu,
+ onDismissRequest = { store.dispatch(SelectFolderAction.SortMenu.SortMenuDismissed) },
+ )
+}
+
private sealed class EmptyListState {
data object NotAuthenticated : EmptyListState()
data object Authenticated : EmptyListState()
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/bookmarks/BookmarksTelemetryMiddleware.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/bookmarks/BookmarksTelemetryMiddleware.kt
@@ -117,6 +117,7 @@ internal class BookmarksTelemetryMiddleware : Middleware<BookmarksState, Bookmar
is BookmarksListMenuAction.Folder.DeleteClicked,
CloseClicked,
AddFolderClicked,
+ is SelectFolderAction.SortMenu,
is BookmarkLongClicked,
is BookmarksListMenuAction.Bookmark.DeleteClicked,
is BookmarksListMenuAction.Bookmark.EditClicked,
diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/bookmarks/BookmarksMiddlewareTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/bookmarks/BookmarksMiddlewareTest.kt
@@ -227,6 +227,41 @@ class BookmarksMiddlewareTest {
}
@Test
+ fun `GIVEN SelectFolderScreen WHEN SortMenuItem is clicked THEN Save the new sort order`() = runTestOnMain {
+ val tree = generateBookmarkTree()
+ `when`(bookmarksStorage.countBookmarksInTrees(listOf(BookmarkRoot.Menu.id, BookmarkRoot.Toolbar.id, BookmarkRoot.Unfiled.id))).thenReturn(0u)
+ `when`(bookmarksStorage.getTree(BookmarkRoot.Mobile.id)).thenReturn(Result.success(tree))
+ var newSortOrder = BookmarksListSortOrder.default
+ saveSortOrder = {
+ newSortOrder = it
+ }
+ val bookmark = tree.children?.last { it.type == BookmarkNodeType.ITEM }!!
+ val bookmarkItem = BookmarkItem.Bookmark(
+ title = bookmark.title!!,
+ guid = bookmark.guid,
+ url = bookmark.url!!,
+ previewImageUrl = "",
+ position = bookmark.position,
+ dateAdded = bookmark.dateAdded,
+ )
+ val middleware = buildMiddleware()
+
+ val store = middleware.makeStore()
+ store.dispatch(EditBookmarkClicked(bookmarkItem))
+ store.dispatch(EditBookmarkAction.FolderClicked)
+ store.dispatch(SelectFolderAction.ViewAppeared)
+ assertEquals(false, store.state.sortMenuShown)
+
+ store.dispatch(SelectFolderAction.SortMenu.SortMenuButtonClicked)
+
+ assertEquals(true, store.state.sortMenuShown)
+
+ store.dispatch(SelectFolderAction.SortMenu.NewestClicked)
+ store.waitUntilIdle()
+ assertEquals(BookmarksListSortOrder.Created(true), newSortOrder)
+ }
+
+ @Test
fun `GIVEN bookmarks in storage and user has a desktop bookmark WHEN store is initialized THEN bookmarks, including desktop will be loaded as display format`() = runTestOnMain {
`when`(bookmarksStorage.countBookmarksInTrees(listOf(BookmarkRoot.Menu.id, BookmarkRoot.Toolbar.id, BookmarkRoot.Unfiled.id))).thenReturn(1u)
`when`(bookmarksStorage.getTree(BookmarkRoot.Mobile.id)).thenReturn(Result.success(generateBookmarkTree()))
diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/bookmarks/BookmarksReducerTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/bookmarks/BookmarksReducerTest.kt
@@ -212,6 +212,34 @@ class BookmarksReducerTest {
}
@Test
+ fun `GIVEN the select folder screen WHEN the sort menu items are clicked THEN resort the bookmark list`() {
+ val b1 = generateFolder(1, title = "z", dateAdded = 1, position = 0u)
+ val b2 = generateFolder(2, title = "a", dateAdded = 2, position = 1u)
+ val b3 = generateFolder(3, title = "z", dateAdded = 3, position = 1u)
+ val b4 = generateFolder(4, title = "b", dateAdded = 4, position = 1u)
+ val b5 = generateFolder(5, title = "d", dateAdded = 5, position = 2u)
+ val b6 = generateFolder(6, title = "j", dateAdded = 6, position = 3u)
+
+ val items = listOf(b1, b2, b3, b4, b5, b6)
+ val state = BookmarksState.default.copy(bookmarkItems = items)
+
+ val positional = bookmarksReducer(state, SelectFolderAction.SortMenu.CustomSortClicked)
+ assertEquals(listOf(b1, b2, b3, b4, b5, b6), positional.bookmarkItems)
+
+ val zToA = bookmarksReducer(state, BookmarksListMenuAction.SortMenu.ZtoAClicked)
+ assertEquals(listOf(b1, b3, b6, b5, b4, b2), zToA.bookmarkItems)
+
+ val aToZ = bookmarksReducer(zToA, BookmarksListMenuAction.SortMenu.AtoZClicked)
+ assertEquals(listOf(b2, b4, b5, b6, b1, b3), aToZ.bookmarkItems)
+
+ val newest = bookmarksReducer(aToZ, BookmarksListMenuAction.SortMenu.NewestClicked)
+ assertEquals(listOf(b6, b5, b4, b3, b2, b1), newest.bookmarkItems)
+
+ val oldest = bookmarksReducer(newest, BookmarksListMenuAction.SortMenu.OldestClicked)
+ assertEquals(listOf(b1, b2, b3, b4, b5, b6), oldest.bookmarkItems)
+ }
+
+ @Test
fun `GIVEN there are already selected items WHEN clicking an unselected folder THEN it is added to selected items`() {
val folder1 = generateFolder(1)
val folder2 = generateFolder(2)