Skip to content
This repository has been archived by the owner on Feb 20, 2023. It is now read-only.

Commit

Permalink
For #11404 - Create open all function
Browse files Browse the repository at this point in the history
- Create element to be displayed

- Update the interface and bind in the view holder
Set the filter to limit this action to FOLDER.

- Create core function
Main logic is done on the controller (has it should be done). The whole
process is done in one coroutine to be non-blocking as many
(sub)folders & links can be present. If folder is empty, a toast is
displayed. Else iterate on items. When item is:
- a FOLDER -> restart process (without toast) on the folder
- a ITEM -> open it
- a SEPARATOR -> do nothing
Once finished, show the tabs tray.

Toast message is defined in fragment to have access to context.

- Create androidTest for openAllInTabs
- Create tests for handleBookmarkFolderOpening
- Display 'open all' options only if folder has at least on child:
A coroutine and suspended functions have to be used, since `getTree`
is async.
  • Loading branch information
Taknok authored and rvandermeulen committed Sep 19, 2022
1 parent 400a2a6 commit 9cf42cb
Show file tree
Hide file tree
Showing 15 changed files with 370 additions and 53 deletions.
45 changes: 45 additions & 0 deletions app/src/androidTest/java/org/mozilla/fenix/ui/BookmarksTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,51 @@ class BookmarksTest {
}
}

@Test
fun openAllInTabsTest() {
val nbPages = 4
val webPages = List(nbPages) {
TestAssetHelper.getGenericAsset(mockWebServer, it + 1)
}

homeScreen {
}.openThreeDotMenu {
}.openBookmarks {
createFolder("root")
createFolder("sub", "root")
createFolder("empty", "root")
}.closeMenu {
}

browserScreen {
createBookmark(webPages[0].url, "root")
createBookmark(webPages[1].url, "root")
createBookmark(webPages[2].url, "sub")
// out of folder and should not be opened
createBookmark(webPages[3].url)
}.openTabDrawer {
closeTab()
}

browserScreen {
}.openThreeDotMenu {
}.openBookmarks {
}.openThreeDotMenu("root") {
}.clickOpenAllInTabs {
verifyTabTrayIsOpened()
verifyNormalModeSelected()

verifyExistingOpenTabs("Test_Page_1")
verifyExistingOpenTabs("Test_Page_2")
verifyExistingOpenTabs("Test_Page_3")
closeTab()
closeTab()
closeTab()
// no more tabs should be presents and auto close tab tray
verifyTabTrayIsClosed()
}
}

@Test
fun openBookmarkInPrivateTabTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,21 @@ class BookmarksRobot {
.click()
}

fun createFolder(name: String) {
fun createFolder(name: String, parent: String? = null) {
clickAddFolderButton()
addNewFolderName(name)
if (!parent.isNullOrBlank()) {
setParentFolder(parent)
}
saveNewFolder()
}

fun setParentFolder(parentName: String) {
clickParentFolderSelector()
selectFolder(parentName)
navigateUp()
}

fun clickAddFolderButton() {
mDevice.waitNotNull(
Until.findObject(By.desc("Add folder")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,13 +334,21 @@ class BrowserRobot {
menuSaveImage.click()
}

fun createBookmark(url: Uri) {
fun createBookmark(url: Uri, folder: String? = null) {
navigationToolbar {
}.enterURLAndEnterToBrowser(url) {
// needs to wait for the right url to load before saving a bookmark
verifyUrl(url.toString())
}.openThreeDotMenu {
}.bookmarkPage { }
}.bookmarkPage {
// continue only if a folder is defined
}.takeIf { !folder.isNullOrBlank() }?.let {
it.openThreeDotMenu {
}.editBookmarkPage {
setParentFolder(folder!!)
saveEditBookmark()
}
}
}

fun clickLinkMatchingText(expectedText: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ class ThreeDotMenuBookmarksRobot {
return TabDrawerRobot.Transition()
}

fun clickOpenAllInTabs(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
openAllInTabsButton().click()

TabDrawerRobot().interact()
return TabDrawerRobot.Transition()
}

fun clickDelete(interact: BookmarksRobot.() -> Unit): BookmarksRobot.Transition {
deleteButton().click()

Expand All @@ -71,4 +78,6 @@ private fun openInNewTabButton() = onView(withText("Open in new tab"))

private fun openInPrivateTabButton() = onView(withText("Open in private tab"))

private fun openAllInTabsButton() = onView(withText("Open All Bookmarks"))

private fun deleteButton() = onView(withText("Delete"))
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,14 @@ class ThreeDotMenuMainRobot {
return BrowserRobot.Transition()
}

fun editBookmarkPage(interact: BookmarksRobot.() -> Unit): BookmarksRobot.Transition {
mDevice.waitNotNull(Until.findObject(By.text("Bookmarks")), waitingTime)
editBookmarkButton().click()

BookmarksRobot().interact()
return BookmarksRobot.Transition()
}

fun openHelp(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
mDevice.waitNotNull(Until.findObject(By.text("Help")), waitingTime)
helpButton().click()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import mozilla.appservices.places.BookmarkRoot
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType
import mozilla.components.feature.tabs.TabsUseCases
import mozilla.components.service.fxa.sync.SyncReason
import org.mozilla.fenix.BrowserDirection
Expand Down Expand Up @@ -44,6 +45,7 @@ interface BookmarkController {
fun handleCopyUrl(item: BookmarkNode)
fun handleBookmarkSharing(item: BookmarkNode)
fun handleOpeningBookmark(item: BookmarkNode, mode: BrowsingMode)
fun handleBookmarkFolderOpening(folder: BookmarkNode)

/**
* Handle bookmark nodes deletion
Expand Down Expand Up @@ -75,6 +77,7 @@ class DefaultBookmarkController(
private val tabsUseCases: TabsUseCases?,
private val loadBookmarkNode: suspend (String) -> BookmarkNode?,
private val showSnackbar: (String) -> Unit,
private val onOpenAllInTabsEmpty: () -> Unit,
private val deleteBookmarkNodes: (Set<BookmarkNode>, BookmarkRemoveType) -> Unit,
private val deleteBookmarkFolder: (Set<BookmarkNode>) -> Unit,
private val showTabTray: () -> Unit,
Expand Down Expand Up @@ -158,6 +161,35 @@ class DefaultBookmarkController(
showTabTray()
}

private suspend fun recursiveBookmarkFolderOpening(folder: BookmarkNode, firstLaunch: Boolean = false) {
val node = loadBookmarkNode.invoke(folder.guid) ?: return
if (!node.children.isNullOrEmpty()) {
if (firstLaunch) invokePendingDeletion.invoke()

node.children!!.iterator().forEach {
when (it.type) {
BookmarkNodeType.FOLDER -> recursiveBookmarkFolderOpening(it, mode = mode)
BookmarkNodeType.ITEM -> {
it.url?.let { url -> tabsUseCases?.addTab?.invoke(url, private = (mode == BrowsingMode.Private)) }
}
BookmarkNodeType.SEPARATOR -> {}
}
}.also {
if (firstLaunch) {
activity.browsingModeManager.mode = BrowsingMode.fromBoolean(mode == BrowsingMode.Private)
showTabTray()
}
}
} else if (firstLaunch) onOpenAllInTabsEmpty.invoke()
}

override fun handleBookmarkFolderOpening(folder: BookmarkNode) {
// potentially heavy function with a lot of bookmarks to open => use a coroutine
scope.launch {
recursiveBookmarkFolderOpening(folder, true)
}
}

override fun handleBookmarkDeletion(nodes: Set<BookmarkNode>, removeType: BookmarkRemoveType) {
deleteBookmarkNodes(nodes, removeType)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), UserInteractionHan
tabsUseCases = activity?.components?.useCases?.tabsUseCases,
loadBookmarkNode = ::loadBookmarkNode,
showSnackbar = ::showSnackBarWithText,
onOpenAllInTabsEmpty = ::onOpenAllInTabsEmpty,
deleteBookmarkNodes = ::deleteMulti,
deleteBookmarkFolder = ::showRemoveFolderDialog,
showTabTray = ::showTabTray,
Expand Down Expand Up @@ -292,6 +293,27 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), UserInteractionHan
}
}

private fun alertHeavyOpen(n: Int, function: () -> (Unit)) {
AlertDialog.Builder(requireContext()).apply {
setTitle(R.string.open_all_warning_title)
setMessage(String.format(context.getString(R.string.open_all_warning_message), n))
setPositiveButton(
R.string.open_all_warning_confirm,
) { dialog, _ ->
function()
dialog.dismiss()
}
setNegativeButton(
R.string.open_all_warning_cancel,
) { dialog: DialogInterface, _ ->
dialog.dismiss()
}
setCancelable(false)
create()
show()
}
}

private fun deleteMulti(
selected: Set<BookmarkNode>,
eventType: BookmarkRemoveType = BookmarkRemoveType.MULTIPLE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ class BookmarkFragmentInteractor(
}
}

override fun onOpenAllInTabs(folder: BookmarkNode) {
require(folder.type == BookmarkNodeType.FOLDER)
bookmarksController.handleBookmarkFolderOpening(folder)
}

override fun onDelete(nodes: Set<BookmarkNode>) {
if (nodes.find { it.type == BookmarkNodeType.SEPARATOR } != null) {
throw IllegalStateException("Cannot delete separators")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import mozilla.components.concept.menu.candidate.TextStyle
import mozilla.components.concept.storage.BookmarkNodeType
import mozilla.components.support.ktx.android.content.getColorFromAttr
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.bookmarkStorage

class BookmarkItemMenu(
private val context: Context,
Expand All @@ -25,57 +26,87 @@ class BookmarkItemMenu(
Share,
OpenInNewTab,
OpenInPrivateTab,
OpenAllInTabs,
Delete,
;
}

val menuController: MenuController by lazy { BrowserMenuController() }

/**
* Check if the menu item has to be displayed or not for the type of bookmark.
* If wanted, return the item.
* Else, return null.
*/
private fun maybeCreateMenuItem(
itemType: BookmarkNodeType,
wantedType: BookmarkNodeType,
text: String,
action: Item,
): TextMenuCandidate? {
return maybeCreateMenuItem(itemType, listOf(wantedType), text, action)
}

private fun maybeCreateMenuItem(
itemType: BookmarkNodeType,
wantedTypes: List<BookmarkNodeType>,
text: String,
action: Item,
): TextMenuCandidate? {
return if (itemType in wantedTypes) {
TextMenuCandidate(
text = text,
) {
onItemTapped.invoke(action)
}
} else {
null
}
}

@VisibleForTesting
internal fun menuItems(itemType: BookmarkNodeType): List<TextMenuCandidate> {
internal suspend fun menuItems(itemType: BookmarkNodeType, itemId: String): List<TextMenuCandidate> {
// if have at least one child
val hasAtLeastOneChild = !context.bookmarkStorage.getTree(itemId)?.children.isNullOrEmpty()

return listOfNotNull(
if (itemType != BookmarkNodeType.SEPARATOR) {
TextMenuCandidate(
text = context.getString(R.string.bookmark_menu_edit_button),
) {
onItemTapped.invoke(Item.Edit)
}
} else {
null
},
if (itemType == BookmarkNodeType.ITEM) {
TextMenuCandidate(
text = context.getString(R.string.bookmark_menu_copy_button),
) {
onItemTapped.invoke(Item.Copy)
}
} else {
null
},
if (itemType == BookmarkNodeType.ITEM) {
TextMenuCandidate(
text = context.getString(R.string.bookmark_menu_share_button),
) {
onItemTapped.invoke(Item.Share)
}
} else {
null
},
if (itemType == BookmarkNodeType.ITEM) {
TextMenuCandidate(
text = context.getString(R.string.bookmark_menu_open_in_new_tab_button),
) {
onItemTapped.invoke(Item.OpenInNewTab)
}
} else {
null
},
if (itemType == BookmarkNodeType.ITEM) {
TextMenuCandidate(
text = context.getString(R.string.bookmark_menu_open_in_private_tab_button),
) {
onItemTapped.invoke(Item.OpenInPrivateTab)
}
maybeCreateMenuItem(
itemType,
listOf(BookmarkNodeType.ITEM, BookmarkNodeType.FOLDER),
context.getString(R.string.bookmark_menu_edit_button),
Item.Edit,
),
maybeCreateMenuItem(
itemType,
BookmarkNodeType.ITEM,
context.getString(R.string.bookmark_menu_copy_button),
Item.Copy,
),
maybeCreateMenuItem(
itemType,
BookmarkNodeType.ITEM,
context.getString(R.string.bookmark_menu_share_button),
Item.Share,
),
maybeCreateMenuItem(
itemType,
BookmarkNodeType.ITEM,
context.getString(R.string.bookmark_menu_open_in_new_tab_button),
Item.OpenInNewTab,
),
maybeCreateMenuItem(
itemType,
BookmarkNodeType.ITEM,
context.getString(R.string.bookmark_menu_open_in_private_tab_button),
Item.OpenInPrivateTab,
),
if (hasAtLeastOneChild) {
maybeCreateMenuItem(
itemType,
BookmarkNodeType.FOLDER,
context.getString(R.string.bookmark_menu_open_all_in_tabs_button),
Item.OpenAllInTabs,
)
} else {
null
},
Expand All @@ -88,7 +119,7 @@ class BookmarkItemMenu(
)
}

fun updateMenu(itemType: BookmarkNodeType) {
menuController.submitList(menuItems(itemType))
suspend fun updateMenu(itemType: BookmarkNodeType, itemId: String) {
menuController.submitList(menuItems(itemType, itemId))
}
}
Loading

0 comments on commit 9cf42cb

Please sign in to comment.