Skip to content

Commit

Permalink
For mozilla-mobile#2165 - Add swipe to refresh gesture to bookmarks v…
Browse files Browse the repository at this point in the history
…iew.
  • Loading branch information
person808 committed Jun 25, 2020
1 parent c77ddd8 commit 7af4ae7
Show file tree
Hide file tree
Showing 9 changed files with 374 additions and 132 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,19 @@ import android.content.res.Resources
import androidx.core.content.getSystemService
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.service.fxa.sync.SyncReason
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.bookmarkStorage
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.nav

/**
Expand All @@ -26,23 +32,31 @@ import org.mozilla.fenix.ext.nav
*/
@SuppressWarnings("TooManyFunctions")
interface BookmarkController {
fun handleBookmarkChanged(item: BookmarkNode)
fun handleBookmarkTapped(item: BookmarkNode)
fun handleBookmarkExpand(folder: BookmarkNode)
fun handleSelectionModeSwitch()
fun handleBookmarkEdit(node: BookmarkNode)
fun handleBookmarkSelected(node: BookmarkNode)
fun handleBookmarkDeselected(node: BookmarkNode)
fun handleAllBookmarksDeselected()
fun handleCopyUrl(item: BookmarkNode)
fun handleBookmarkSharing(item: BookmarkNode)
fun handleOpeningBookmark(item: BookmarkNode, mode: BrowsingMode)
fun handleBookmarkDeletion(nodes: Set<BookmarkNode>, eventType: Event)
fun handleBookmarkFolderDeletion(node: BookmarkNode)
fun handleRequestSync()
fun handleBackPressed()
}

@SuppressWarnings("TooManyFunctions")
class DefaultBookmarkController(
private val context: Context,
private val navController: NavController,
private val scope: CoroutineScope,
private val store: BookmarkFragmentStore,
private val sharedViewModel: BookmarksSharedViewModel,
private val loadBookmarkNode: suspend (String) -> BookmarkNode?,
private val showSnackbar: (String) -> Unit,
private val deleteBookmarkNodes: (Set<BookmarkNode>, Event) -> Unit,
private val deleteBookmarkFolder: (BookmarkNode) -> Unit,
Expand All @@ -52,12 +66,23 @@ class DefaultBookmarkController(
private val activity: HomeActivity = context as HomeActivity
private val resources: Resources = context.resources

override fun handleBookmarkChanged(item: BookmarkNode) {
sharedViewModel.selectedFolder = item
store.dispatch(BookmarkFragmentAction.Change(item))
}

override fun handleBookmarkTapped(item: BookmarkNode) {
openInNewTab(item.url!!, true, BrowserDirection.FromBookmarks, activity.browsingModeManager.mode)
}

override fun handleBookmarkExpand(folder: BookmarkNode) {
navigate(BookmarkFragmentDirections.actionBookmarkFragmentSelf(folder.guid))
handleAllBookmarksDeselected()
invokePendingDeletion.invoke()
scope.launch {
val node = loadBookmarkNode.invoke(folder.guid) ?: return@launch
sharedViewModel.selectedFolder = node
store.dispatch(BookmarkFragmentAction.Change(node))
}
}

override fun handleSelectionModeSwitch() {
Expand All @@ -69,7 +94,23 @@ class DefaultBookmarkController(
}

override fun handleBookmarkSelected(node: BookmarkNode) {
showSnackbar(resources.getString(R.string.bookmark_cannot_edit_root))
if (store.state.mode is BookmarkFragmentState.Mode.Syncing) {
return
}

if (node.inRoots()) {
showSnackbar(resources.getString(R.string.bookmark_cannot_edit_root))
} else {
store.dispatch(BookmarkFragmentAction.Select(node))
}
}

override fun handleBookmarkDeselected(node: BookmarkNode) {
store.dispatch(BookmarkFragmentAction.Deselect(node))
}

override fun handleAllBookmarksDeselected() {
store.dispatch(BookmarkFragmentAction.DeselectAll)
}

override fun handleCopyUrl(item: BookmarkNode) {
Expand Down Expand Up @@ -98,9 +139,36 @@ class DefaultBookmarkController(
deleteBookmarkFolder(node)
}

override fun handleRequestSync() {
scope.launch {
store.dispatch(BookmarkFragmentAction.StartSync)
invokePendingDeletion()
context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.User).await()
// The current bookmark node we are viewing may be made invalid after syncing so we
// check if the current node is valid and if it isn't we find the nearest valid ancestor
// and open it
val validAncestorGuid = store.state.guidBackstack.findLast { guid ->
context.bookmarkStorage.getBookmark(guid) != null
} ?: BookmarkRoot.Mobile.id
val node = context.bookmarkStorage.getBookmark(validAncestorGuid)!!
handleBookmarkExpand(node)
store.dispatch(BookmarkFragmentAction.FinishSync)
}
}

override fun handleBackPressed() {
invokePendingDeletion.invoke()
navController.popBackStack()
scope.launch {
val parentGuid = store.state.guidBackstack.findLast { guid ->
store.state.tree?.guid != guid && context.bookmarkStorage.getBookmark(guid) != null
}
if (parentGuid == null) {
navController.popBackStack()
} else {
val parent = context.bookmarkStorage.getBookmark(parentGuid)!!
handleBookmarkExpand(parent)
}
}
}

private fun openInNewTab(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,19 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavDirections
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.component_bookmark.view.*
import kotlinx.android.synthetic.main.fragment_bookmark.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.isActive
Expand Down Expand Up @@ -59,15 +60,14 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), UserInteractionHan
private lateinit var bookmarkStore: BookmarkFragmentStore
private lateinit var bookmarkView: BookmarkView
private var _bookmarkInteractor: BookmarkFragmentInteractor? = null
protected val bookmarkInteractor: BookmarkFragmentInteractor
private val bookmarkInteractor: BookmarkFragmentInteractor
get() = _bookmarkInteractor!!

private val sharedViewModel: BookmarksSharedViewModel by activityViewModels {
ViewModelProvider.NewInstanceFactory() // this is a workaround for #4652
}
private val desktopFolders by lazy { DesktopFolders(requireContext(), showMobileRoot = false) }

lateinit var initialJob: Job
private var pendingBookmarkDeletionJob: (suspend () -> Unit)? = null
private var pendingBookmarksToDelete: MutableSet<BookmarkNode> = mutableSetOf()

Expand All @@ -84,11 +84,13 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), UserInteractionHan
}

_bookmarkInteractor = BookmarkFragmentInteractor(
bookmarkStore = bookmarkStore,
viewModel = sharedViewModel,
bookmarksController = DefaultBookmarkController(
context = requireContext(),
navController = findNavController(),
scope = viewLifecycleOwner.lifecycleScope,
store = bookmarkStore,
sharedViewModel = sharedViewModel,
loadBookmarkNode = ::loadBookmarkNode,
showSnackbar = ::showSnackBarWithText,
deleteBookmarkNodes = ::deleteMulti,
deleteBookmarkFolder = ::showRemoveFolderDialog,
Expand Down Expand Up @@ -124,8 +126,16 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), UserInteractionHan
@ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val accountManager = requireComponents.backgroundServices.accountManager
consumeFrom(bookmarkStore) {
bookmarkView.update(it)

// Only display the sign-in prompt if we're inside of the virtual "Desktop Bookmarks" node.
// Don't want to pester user too much with it, and if there are lots of bookmarks present,
// it'll just get visually lost. Inside of the "Desktop Bookmarks" node, it'll nicely stand-out,
// since there are always only three other items in there. It's also the right place contextually.
bookmarkView.view.bookmark_folders_sign_in.isVisible =
it.tree?.guid == BookmarkRoot.Root.id && accountManager.authenticatedAccount() == null
}
}

Expand All @@ -138,36 +148,24 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), UserInteractionHan
super.onResume()

(activity as HomeActivity).getSupportActionBarAndInflateIfNecessary().show()
val currentGuid = BookmarkFragmentArgs.fromBundle(requireArguments()).currentRoot.ifEmpty {
BookmarkRoot.Mobile.id
}

// Only display the sign-in prompt if we're inside of the virtual "Desktop Bookmarks" node.
// Don't want to pester user too much with it, and if there are lots of bookmarks present,
// it'll just get visually lost. Inside of the "Desktop Bookmarks" node, it'll nicely stand-out,
// since there are always only three other items in there. It's also the right place contextually.
if (currentGuid == BookmarkRoot.Root.id &&
requireComponents.backgroundServices.accountManager.authenticatedAccount() == null
) {
bookmarkView.view.bookmark_folders_sign_in.visibility = View.VISIBLE
} else {
bookmarkView.view.bookmark_folders_sign_in.visibility = View.GONE
}

initialJob = loadInitialBookmarkFolder(currentGuid)
// Reload bookmarks when returning to this fragment in case they have been edited
val args by navArgs<BookmarkFragmentArgs>()
val currentGuid = bookmarkStore.state.tree?.guid
?: if (args.currentRoot.isNotEmpty()) {
args.currentRoot
} else {
BookmarkRoot.Mobile.id
}
loadInitialBookmarkFolder(currentGuid)
}

private fun loadInitialBookmarkFolder(currentGuid: String): Job {
return viewLifecycleOwner.lifecycleScope.launch(Main) {
val currentRoot = withContext(IO) {
requireContext().bookmarkStorage
.getTree(currentGuid)
?.let { desktopFolders.withOptionalDesktopFolders(it) }!!
}
private fun loadInitialBookmarkFolder(currentGuid: String) {
viewLifecycleOwner.lifecycleScope.launch(Main) {
val currentRoot = loadBookmarkNode(currentGuid)

if (isActive) {
if (isActive && currentRoot != null) {
bookmarkInteractor.onBookmarksChanged(currentRoot)
sharedViewModel.selectedFolder = currentRoot
}
}
}
Expand Down Expand Up @@ -249,14 +247,18 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), UserInteractionHan
return bookmarkView.onBackPressed()
}

private suspend fun loadBookmarkNode(guid: String): BookmarkNode? = withContext(IO) {
requireContext().bookmarkStorage
.getTree(guid, false)
?.let { desktopFolders.withOptionalDesktopFolders(it) }
}

private suspend fun refreshBookmarks() {
// The bookmark tree in our 'state' can be null - meaning, no bookmark tree has been selected.
// If that's the case, we don't know what node to refresh, and so we bail out.
// See https://github.com/mozilla-mobile/fenix/issues/4671
val currentGuid = bookmarkStore.state.tree?.guid ?: return
context?.bookmarkStorage
?.getTree(currentGuid, false)
?.let { desktopFolders.withOptionalDesktopFolders(it) }
loadBookmarkNode(currentGuid)
?.let { node ->
val rootNode = node - pendingBookmarksToDelete
bookmarkInteractor.onBookmarksChanged(rootNode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,12 @@ import org.mozilla.fenix.utils.Do
*/
@SuppressWarnings("TooManyFunctions")
class BookmarkFragmentInteractor(
private val bookmarkStore: BookmarkFragmentStore,
private val viewModel: BookmarksSharedViewModel,
private val bookmarksController: BookmarkController,
private val metrics: MetricController
) : BookmarkViewInteractor {

override fun onBookmarksChanged(node: BookmarkNode) {
bookmarkStore.dispatch(BookmarkFragmentAction.Change(node))
bookmarksController.handleBookmarkChanged(node)
}

override fun onSelectionModeSwitch(mode: BookmarkFragmentState.Mode) {
Expand All @@ -41,7 +39,7 @@ class BookmarkFragmentInteractor(
}

override fun onAllBookmarksDeselected() {
bookmarkStore.dispatch(BookmarkFragmentAction.DeselectAll)
bookmarksController.handleAllBookmarksDeselected()
}

/**
Expand Down Expand Up @@ -112,13 +110,14 @@ class BookmarkFragmentInteractor(
}

override fun select(item: BookmarkNode) {
when (item.inRoots()) {
true -> bookmarksController.handleBookmarkSelected(item)
false -> bookmarkStore.dispatch(BookmarkFragmentAction.Select(item))
}
bookmarksController.handleBookmarkSelected(item)
}

override fun deselect(item: BookmarkNode) {
bookmarkStore.dispatch(BookmarkFragmentAction.Deselect(item))
bookmarksController.handleBookmarkDeselected(item)
}

override fun onRequestSync() {
bookmarksController.handleRequestSync()
}
}
Loading

0 comments on commit 7af4ae7

Please sign in to comment.