tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/bookmarks/BookmarksAction.kt | 10++++++++++
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/bookmarks/BookmarksMiddleware.kt | 4++++
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/bookmarks/BookmarksReducer.kt | 35+++++++++++++++++++++++++++--------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/bookmarks/BookmarksScreen.kt | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/bookmarks/BookmarksTelemetryMiddleware.kt | 1+
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/bookmarks/BookmarksMiddlewareTest.kt | 35+++++++++++++++++++++++++++++++++++
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/bookmarks/BookmarksReducerTest.kt | 28++++++++++++++++++++++++++++
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)