1
0
Fork 0

For #3203: Updates edit bookmarks to have user friendly names (#3372)

Co-authored-by: Colin Lee <mncolinlee@gmail.com>
Co-authored-by: Sawyer Blatz <sdblatz@gmail.com>"
master
Colin Lee 2019-06-12 14:10:43 -05:00 committed by GitHub
parent 2868e376a6
commit f1088222b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 166 additions and 124 deletions

View File

@ -0,0 +1,129 @@
/* 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.ext
import android.content.Context
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.browser.storage.sync.PlacesBookmarksStorage
import mozilla.components.concept.storage.BookmarkNode
import org.mozilla.fenix.R
var rootTitles: Map<String, String> = emptyMap()
@SuppressWarnings("ReturnCount")
suspend fun BookmarkNode?.withOptionalDesktopFolders(
context: Context?
): BookmarkNode? {
// No-op if node is missing.
if (this == null) {
return null
}
// If we're in the mobile root and logged in, add-in a synthetic "Desktop Bookmarks" folder.
if (this.guid == BookmarkRoot.Mobile.id &&
context?.components?.backgroundServices?.accountManager?.authenticatedAccount() != null
) {
// We're going to make a copy of the mobile node, and add-in a synthetic child folder to the top of the
// children's list that contains all of the desktop roots.
val childrenWithVirtualFolder: MutableList<BookmarkNode> = mutableListOf()
virtualDesktopFolder(context)?.let { childrenWithVirtualFolder.add(it) }
this.children?.let { children ->
childrenWithVirtualFolder.addAll(children)
}
return BookmarkNode(
type = this.type,
guid = this.guid,
parentGuid = this.parentGuid,
position = this.position,
title = this.title,
url = this.url,
children = childrenWithVirtualFolder
)
// If we're looking at the root, that means we're in the "Desktop Bookmarks" folder.
// Rename its child roots and remove the mobile root.
} else if (this.guid == BookmarkRoot.Root.id) {
return BookmarkNode(
type = this.type,
guid = this.guid,
parentGuid = this.parentGuid,
position = this.position,
title = rootTitles[this.title],
url = this.url,
children = processDesktopRoots(this.children)
)
// If we're looking at one of the desktop roots, change their titles to friendly names.
} else if (this.guid in listOf(BookmarkRoot.Menu.id, BookmarkRoot.Toolbar.id, BookmarkRoot.Unfiled.id)) {
return BookmarkNode(
type = this.type,
guid = this.guid,
parentGuid = this.parentGuid,
position = this.position,
title = rootTitles[this.title],
url = this.url,
children = this.children
)
}
// Otherwise, just return the node as-is.
return this
}
private suspend fun virtualDesktopFolder(context: Context?): BookmarkNode? {
val rootNode = context?.bookmarkStorage()?.getTree(BookmarkRoot.Root.id, false) ?: return null
return BookmarkNode(
type = rootNode.type,
guid = rootNode.guid,
parentGuid = rootNode.parentGuid,
position = rootNode.position,
title = rootTitles[rootNode.title],
url = rootNode.url,
children = rootNode.children
)
}
/**
* Removes 'mobile' root (to avoid a cyclical bookmarks tree in the UI) and renames other roots to friendly titles.
*/
private fun processDesktopRoots(roots: List<BookmarkNode>?): List<BookmarkNode>? {
if (roots == null) {
return null
}
return roots.filter { rootTitles.containsKey(it.title) }.map {
BookmarkNode(
type = it.type,
guid = it.guid,
parentGuid = it.parentGuid,
position = it.position,
title = rootTitles[it.title],
url = it.url,
children = it.children
)
}
}
fun Context?.bookmarkStorage(): PlacesBookmarksStorage? {
return this?.components?.core?.bookmarksStorage
}
fun setRootTitles(context: Context) {
rootTitles = mapOf(
"root" to context.getString(R.string.library_desktop_bookmarks_root),
"menu" to context.getString(R.string.library_desktop_bookmarks_menu),
"toolbar" to context.getString(R.string.library_desktop_bookmarks_toolbar),
"unfiled" to context.getString(R.string.library_desktop_bookmarks_unfiled)
)
}
operator fun BookmarkNode?.minus(child: String): BookmarkNode {
return this!!.copy(children = this.children?.filter { it.guid != child })
}
operator fun BookmarkNode?.minus(children: Set<BookmarkNode>): BookmarkNode {
return this!!.copy(children = this.children?.filter { it !in children })
}

View File

@ -30,7 +30,6 @@ import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import mozilla.appservices.places.BookmarkRoot import mozilla.appservices.places.BookmarkRoot
import mozilla.components.browser.storage.sync.PlacesBookmarksStorage
import mozilla.components.concept.storage.BookmarkNode import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType import mozilla.components.concept.storage.BookmarkNodeType
import mozilla.components.concept.sync.AccountObserver import mozilla.components.concept.sync.AccountObserver
@ -45,9 +44,13 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.bookmarkStorage
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.minus
import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.setRootTitles
import org.mozilla.fenix.ext.urlToTrimmedHost import org.mozilla.fenix.ext.urlToTrimmedHost
import org.mozilla.fenix.ext.withOptionalDesktopFolders
import org.mozilla.fenix.mvi.ActionBusFactory import org.mozilla.fenix.mvi.ActionBusFactory
import org.mozilla.fenix.mvi.getAutoDisposeObservable import org.mozilla.fenix.mvi.getAutoDisposeObservable
import org.mozilla.fenix.mvi.getManagedEmitter import org.mozilla.fenix.mvi.getManagedEmitter
@ -74,9 +77,6 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = Main + job get() = Main + job
// Map of internal "root titles" to user friendly labels.
private lateinit var rootTitles: Map<String, String>
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_bookmark, container, false) val view = inflater.inflate(R.layout.fragment_bookmark, container, false)
bookmarkComponent = BookmarkComponent( bookmarkComponent = BookmarkComponent(
@ -104,13 +104,7 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve
// Fill out our title map once we have context. // Fill out our title map once we have context.
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
super.onAttach(context) super.onAttach(context)
setRootTitles(context)
rootTitles = mapOf(
"root" to context.getString(R.string.library_desktop_bookmarks_root),
"menu" to context.getString(R.string.library_desktop_bookmarks_menu),
"toolbar" to context.getString(R.string.library_desktop_bookmarks_toolbar),
"unfiled" to context.getString(R.string.library_desktop_bookmarks_unfiled)
)
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -133,7 +127,8 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve
private fun loadInitialBookmarkFolder(currentGuid: String): Job { private fun loadInitialBookmarkFolder(currentGuid: String): Job {
return launch(IO) { return launch(IO) {
currentRoot = bookmarkStorage()?.getTree(currentGuid).withOptionalDesktopFolders() as BookmarkNode currentRoot =
context?.bookmarkStorage()?.getTree(currentGuid).withOptionalDesktopFolders(context) as BookmarkNode
launch(Main) { launch(Main) {
getManagedEmitter<BookmarkChange>().onNext(BookmarkChange.Change(currentRoot!!)) getManagedEmitter<BookmarkChange>().onNext(BookmarkChange.Change(currentRoot!!))
@ -267,11 +262,13 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve
allowUndo( allowUndo(
view!!, view!!,
getString(R.string.bookmark_deletion_snackbar_message, getString(
it.item.url?.urlToTrimmedHost(context)), R.string.bookmark_deletion_snackbar_message,
it.item.url?.urlToTrimmedHost(context)
),
getString(R.string.bookmark_undo_deletion), { refreshBookmarks() } getString(R.string.bookmark_undo_deletion), { refreshBookmarks() }
) { ) {
bookmarkStorage()?.deleteNode(it.item.guid) context.bookmarkStorage()?.deleteNode(it.item.guid)
metrics()?.track(Event.RemoveBookmark) metrics()?.track(Event.RemoveBookmark)
refreshBookmarks() refreshBookmarks()
} }
@ -385,12 +382,12 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve
private suspend fun deleteSelectedBookmarks(selected: Set<BookmarkNode> = getSelectedBookmarks()) { private suspend fun deleteSelectedBookmarks(selected: Set<BookmarkNode> = getSelectedBookmarks()) {
selected.forEach { selected.forEach {
bookmarkStorage()?.deleteNode(it.guid) context?.bookmarkStorage()?.deleteNode(it.guid)
} }
} }
private suspend fun refreshBookmarks() { private suspend fun refreshBookmarks() {
bookmarkStorage()?.getTree(currentRoot!!.guid, false).withOptionalDesktopFolders() context?.bookmarkStorage()?.getTree(currentRoot!!.guid, false).withOptionalDesktopFolders(context)
?.let { node -> ?.let { node ->
getManagedEmitter<BookmarkChange>().onNext(BookmarkChange.Change(node)) getManagedEmitter<BookmarkChange>().onNext(BookmarkChange.Change(node))
} }
@ -402,111 +399,7 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve
} }
} }
@SuppressWarnings("ReturnCount")
private suspend fun BookmarkNode?.withOptionalDesktopFolders(): BookmarkNode? {
// No-op if node is missing.
if (this == null) {
return null
}
// If we're in the mobile root and logged in, add-in a synthetic "Desktop Bookmarks" folder.
if (this.guid == BookmarkRoot.Mobile.id &&
activity?.components?.backgroundServices?.accountManager?.authenticatedAccount() != null) {
// We're going to make a copy of the mobile node, and add-in a synthetic child folder to the top of the
// children's list that contains all of the desktop roots.
val childrenWithVirtualFolder: MutableList<BookmarkNode> = mutableListOf()
virtualDesktopFolder()?.let { childrenWithVirtualFolder.add(it) }
this.children?.let { children ->
childrenWithVirtualFolder.addAll(children)
}
return BookmarkNode(
type = this.type,
guid = this.guid,
parentGuid = this.parentGuid,
position = this.position,
title = this.title,
url = this.url,
children = childrenWithVirtualFolder
)
// If we're looking at the root, that means we're in the "Desktop Bookmarks" folder.
// Rename its child roots and remove the mobile root.
} else if (this.guid == BookmarkRoot.Root.id) {
return BookmarkNode(
type = this.type,
guid = this.guid,
parentGuid = this.parentGuid,
position = this.position,
title = rootTitles[this.title],
url = this.url,
children = processDesktopRoots(this.children)
)
// If we're looking at one of the desktop roots, change their titles to friendly names.
} else if (this.guid in listOf(BookmarkRoot.Menu.id, BookmarkRoot.Toolbar.id, BookmarkRoot.Unfiled.id)) {
return BookmarkNode(
type = this.type,
guid = this.guid,
parentGuid = this.parentGuid,
position = this.position,
title = rootTitles[this.title],
url = this.url,
children = this.children
)
}
// Otherwise, just return the node as-is.
return this
}
private suspend fun virtualDesktopFolder(): BookmarkNode? {
val rootNode = bookmarkStorage()?.getTree(BookmarkRoot.Root.id, false) ?: return null
return BookmarkNode(
type = rootNode.type,
guid = rootNode.guid,
parentGuid = rootNode.parentGuid,
position = rootNode.position,
title = rootTitles[rootNode.title],
url = rootNode.url,
children = rootNode.children
)
}
/**
* Removes 'mobile' root (to avoid a cyclical bookmarks tree in the UI) and renames other roots to friendly titles.
*/
private fun processDesktopRoots(roots: List<BookmarkNode>?): List<BookmarkNode>? {
if (roots == null) {
return null
}
return roots.filter { rootTitles.containsKey(it.title) }.map {
BookmarkNode(
type = it.type,
guid = it.guid,
parentGuid = it.parentGuid,
position = it.position,
title = rootTitles[it.title],
url = it.url,
children = it.children
)
}
}
private fun metrics(): MetricController? { private fun metrics(): MetricController? {
return context?.components?.analytics?.metrics return context?.components?.analytics?.metrics
} }
private fun bookmarkStorage(): PlacesBookmarksStorage? {
return context?.components?.core?.bookmarksStorage
}
}
operator fun BookmarkNode?.minus(child: String): BookmarkNode {
return this!!.copy(children = this.children?.filter { it.guid != child })
}
operator fun BookmarkNode?.minus(children: Set<BookmarkNode>): BookmarkNode {
return this!!.copy(children = this.children?.filter { it !in children })
} }

View File

@ -7,6 +7,7 @@ package org.mozilla.fenix.library.bookmarks.selectfolder
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer import kotlinx.android.extensions.LayoutContainer
@ -79,6 +80,7 @@ class SelectBookmarkFolderAdapter(private val sharedViewModel: BookmarksSharedVi
init { init {
bookmark_favicon.visibility = View.VISIBLE bookmark_favicon.visibility = View.VISIBLE
bookmark_title.visibility = View.VISIBLE bookmark_title.visibility = View.VISIBLE
bookmark_url.visibility = View.GONE
bookmark_separator.visibility = View.GONE bookmark_separator.visibility = View.GONE
bookmark_layout.isClickable = true bookmark_layout.isClickable = true
} }
@ -91,6 +93,14 @@ class SelectBookmarkFolderAdapter(private val sharedViewModel: BookmarksSharedVi
R.attr.neutral.getColorIntFromAttr(containerView!!.context) R.attr.neutral.getColorIntFromAttr(containerView!!.context)
} }
// Center the bookmark title since we don't have a url
val constraintSet = ConstraintSet()
constraintSet.clone(bookmark_layout)
constraintSet.connect(
bookmark_title.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM
)
constraintSet.applyTo(bookmark_layout)
val backgroundTintList = ContextCompat.getColorStateList(containerView.context, backgroundTint) val backgroundTintList = ContextCompat.getColorStateList(containerView.context, backgroundTint)
bookmark_favicon.backgroundTintList = backgroundTintList bookmark_favicon.backgroundTintList = backgroundTintList
val res = if (selected) R.drawable.mozac_ic_check else R.drawable.ic_folder_icon val res = if (selected) R.drawable.mozac_ic_check else R.drawable.ic_folder_icon

View File

@ -4,6 +4,7 @@
package org.mozilla.fenix.library.bookmarks.selectfolder package org.mozilla.fenix.library.bookmarks.selectfolder
import android.content.Context
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter import android.graphics.PorterDuffColorFilter
import android.os.Bundle import android.os.Bundle
@ -19,7 +20,6 @@ import androidx.lifecycle.ViewModelProviders
import kotlinx.android.synthetic.main.fragment_select_bookmark_folder.* import kotlinx.android.synthetic.main.fragment_select_bookmark_folder.*
import kotlinx.android.synthetic.main.fragment_select_bookmark_folder.view.* import kotlinx.android.synthetic.main.fragment_select_bookmark_folder.view.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -36,6 +36,8 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getColorFromAttr import org.mozilla.fenix.ext.getColorFromAttr
import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.setRootTitles
import org.mozilla.fenix.ext.withOptionalDesktopFolders
import org.mozilla.fenix.library.bookmarks.BookmarksSharedViewModel import org.mozilla.fenix.library.bookmarks.BookmarksSharedViewModel
import org.mozilla.fenix.library.bookmarks.SignInAction import org.mozilla.fenix.library.bookmarks.SignInAction
import org.mozilla.fenix.library.bookmarks.SignInChange import org.mozilla.fenix.library.bookmarks.SignInChange
@ -58,7 +60,13 @@ class SelectBookmarkFolderFragment : Fragment(), CoroutineScope, AccountObserver
private lateinit var signInComponent: SignInComponent private lateinit var signInComponent: SignInComponent
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job get() = Main + job
// Fill out our title map once we have context.
override fun onAttach(context: Context) {
super.onAttach(context)
setRootTitles(context)
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -109,7 +117,8 @@ class SelectBookmarkFolderFragment : Fragment(), CoroutineScope, AccountObserver
checkIfSignedIn() checkIfSignedIn()
launch(IO) { launch(IO) {
bookmarkNode = requireComponents.core.bookmarksStorage.getTree(folderGuid!!, true) bookmarkNode =
requireComponents.core.bookmarksStorage.getTree(folderGuid!!, true).withOptionalDesktopFolders(context)
launch(Main) { launch(Main) {
(activity as HomeActivity).title = bookmarkNode?.title ?: getString(R.string.library_bookmarks) (activity as HomeActivity).title = bookmarkNode?.title ?: getString(R.string.library_bookmarks)
val adapter = SelectBookmarkFolderAdapter(sharedViewModel) val adapter = SelectBookmarkFolderAdapter(sharedViewModel)

View File

@ -14,6 +14,7 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.TestUtils import org.mozilla.fenix.TestUtils
import org.mozilla.fenix.TestUtils.bus import org.mozilla.fenix.TestUtils.bus
import org.mozilla.fenix.ext.minus
import org.mozilla.fenix.mvi.getManagedEmitter import org.mozilla.fenix.mvi.getManagedEmitter
class BookmarkViewModelTest { class BookmarkViewModelTest {