1
0
Fork 0

Closes #1312, #1236, #1237, #1238, #1239: Creating, Editing, and Deleting Bookmarks and Bookmark Folders

master
Colin Lee 2019-04-04 15:40:39 -05:00
parent 8f6dca99dc
commit b54d4d1d58
32 changed files with 1183 additions and 101 deletions

View File

@ -17,6 +17,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- #1195 - Adds telemetry for quick action sheet - #1195 - Adds telemetry for quick action sheet
- #627 - Sets engine preferred color scheme based on light/dark theme - #627 - Sets engine preferred color scheme based on light/dark theme
- #904 - Added tab counter in browser toolbar - #904 - Added tab counter in browser toolbar
- #1312 - Added the ability to edit bookmarks
- #1236 - Added the ability to create bookmark folders
- #1237 - Added the ability to delete bookmark folders
- #1238 - Added the ability to edit bookmark folders
- #1239 - Added the ability to move bookmark folders
### Changed ### Changed
- #1429 - Updated site permissions ui for MVP - #1429 - Updated site permissions ui for MVP
### Removed ### Removed

View File

@ -251,6 +251,11 @@ dependencies {
implementation Deps.rxAndroid implementation Deps.rxAndroid
implementation Deps.rxKotlin implementation Deps.rxKotlin
implementation Deps.rxBindings
implementation Deps.autodispose
implementation Deps.autodispose_android
implementation Deps.autodispose_android_aac
implementation Deps.anko_commons implementation Deps.anko_commons
implementation Deps.anko_sdk implementation Deps.anko_sdk
implementation Deps.anko_constraintlayout implementation Deps.anko_constraintlayout
@ -316,6 +321,7 @@ dependencies {
implementation Deps.androidx_navigation_fragment implementation Deps.androidx_navigation_fragment
implementation Deps.androidx_navigation_ui implementation Deps.androidx_navigation_ui
implementation Deps.androidx_recyclerview implementation Deps.androidx_recyclerview
implementation Deps.androidx_lifecycle_viewmodel_ktx
implementation Deps.autodispose implementation Deps.autodispose

View File

@ -27,6 +27,7 @@ import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.home.HomeFragmentDirections import org.mozilla.fenix.home.HomeFragmentDirections
import org.mozilla.fenix.library.bookmarks.BookmarkFragmentDirections import org.mozilla.fenix.library.bookmarks.BookmarkFragmentDirections
import org.mozilla.fenix.library.bookmarks.selectfolder.SelectBookmarkFolderFragmentDirections
import org.mozilla.fenix.library.history.HistoryFragmentDirections import org.mozilla.fenix.library.history.HistoryFragmentDirections
import org.mozilla.fenix.search.SearchFragmentDirections import org.mozilla.fenix.search.SearchFragmentDirections
import org.mozilla.fenix.settings.SettingsFragmentDirections import org.mozilla.fenix.settings.SettingsFragmentDirections
@ -155,6 +156,8 @@ open class HomeActivity : AppCompatActivity() {
SettingsFragmentDirections.actionSettingsFragmentToBrowserFragment(sessionId) SettingsFragmentDirections.actionSettingsFragmentToBrowserFragment(sessionId)
BrowserDirection.FromBookmarks -> BrowserDirection.FromBookmarks ->
BookmarkFragmentDirections.actionBookmarkFragmentToBrowserFragment(sessionId) BookmarkFragmentDirections.actionBookmarkFragmentToBrowserFragment(sessionId)
BrowserDirection.FromBookmarksFolderSelect ->
SelectBookmarkFolderFragmentDirections.actionBookmarkSelectFolderFragmentToBrowserFragment(sessionId)
BrowserDirection.FromHistory -> BrowserDirection.FromHistory ->
HistoryFragmentDirections.actionHistoryFragmentToBrowserFragment(sessionId) HistoryFragmentDirections.actionHistoryFragmentToBrowserFragment(sessionId)
} }
@ -193,5 +196,5 @@ open class HomeActivity : AppCompatActivity() {
} }
enum class BrowserDirection { enum class BrowserDirection {
FromGlobal, FromHome, FromSearch, FromSettings, FromBookmarks, FromHistory FromGlobal, FromHome, FromSearch, FromSettings, FromBookmarks, FromBookmarksFolderSelect, FromHistory
} }

View File

@ -48,6 +48,7 @@ import org.mozilla.fenix.DefaultThemeManager
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.IntentReceiverActivity import org.mozilla.fenix.IntentReceiverActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.FindInPageIntegration import org.mozilla.fenix.components.FindInPageIntegration
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.Event.BrowserMenuItemTapped.Item import org.mozilla.fenix.components.metrics.Event.BrowserMenuItemTapped.Item
@ -67,9 +68,9 @@ import org.mozilla.fenix.mvi.ActionBusFactory
import org.mozilla.fenix.mvi.getAutoDisposeObservable import org.mozilla.fenix.mvi.getAutoDisposeObservable
import org.mozilla.fenix.quickactionsheet.QuickActionAction import org.mozilla.fenix.quickactionsheet.QuickActionAction
import org.mozilla.fenix.quickactionsheet.QuickActionComponent import org.mozilla.fenix.quickactionsheet.QuickActionComponent
import org.mozilla.fenix.settings.quicksettings.QuickSettingsSheetDialogFragment
import org.mozilla.fenix.utils.ItsNotBrokenSnack import org.mozilla.fenix.utils.ItsNotBrokenSnack
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.settings.quicksettings.QuickSettingsSheetDialogFragment
@SuppressWarnings("TooManyFunctions", "LargeClass") @SuppressWarnings("TooManyFunctions", "LargeClass")
class BrowserFragment : Fragment(), BackHandler { class BrowserFragment : Fragment(), BackHandler {
@ -323,24 +324,26 @@ class BrowserFragment : Fragment(), BackHandler {
requireComponents.analytics.metrics.track(Event.QuickActionSheetBookmarkTapped) requireComponents.analytics.metrics.track(Event.QuickActionSheetBookmarkTapped)
val session = requireComponents.core.sessionManager.selectedSession val session = requireComponents.core.sessionManager.selectedSession
CoroutineScope(IO).launch { CoroutineScope(IO).launch {
requireComponents.core.bookmarksStorage val guid = requireComponents.core.bookmarksStorage
.addItem(BookmarkRoot.Mobile.id, session!!.url, session.title, null) .addItem(BookmarkRoot.Mobile.id, session!!.url, session.title, null)
launch(Main) { launch(Main) {
val rootView = context?.asActivity()?.window?.decorView
context?.asActivity()?.window?.decorView?.findViewById<View>(android.R.id.content) ?.findViewById<View>(android.R.id.content)?.let { view ->
rootView?.let { view -> FenixSnackbar.make(
Snackbar.make( view as ViewGroup,
view, Snackbar.LENGTH_LONG
getString(R.string.bookmark_created_snackbar), )
Snackbar.LENGTH_LONG .setAction(getString(R.string.edit_bookmark_snackbar_action)) {
) Navigation.findNavController(requireActivity(), R.id.container)
.setAction(getString(R.string.edit_bookmark_snackbar_action)) { .navigate(
ItsNotBrokenSnack( BrowserFragmentDirections
context!! .actionBrowserFragmentToBookmarkEditFragment(
).showSnackbar(issueNumber = "90") guid
} )
.show() )
} }
.setText(getString(R.string.bookmark_created_snackbar))
}!!.show()
} }
} }
} }

View File

@ -0,0 +1,16 @@
/* 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 android.util.TypedValue
fun Int.getColorFromAttr(context: Context): Int {
val typedValue = TypedValue()
val typedArray = context.obtainStyledAttributes(typedValue.data, intArrayOf(this))
val color = typedArray.getColor(0, 0)
typedArray.recycle()
return color
}

View File

@ -15,6 +15,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.browser.icons.BrowserIcons import mozilla.components.browser.icons.BrowserIcons
import mozilla.components.browser.icons.IconRequest import mozilla.components.browser.icons.IconRequest
import mozilla.components.browser.menu.BrowserMenu import mozilla.components.browser.menu.BrowserMenu
@ -22,17 +23,24 @@ import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType import mozilla.components.concept.storage.BookmarkNodeType
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.increaseTapArea
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class BookmarkAdapter(val actionEmitter: Observer<BookmarkAction>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { class BookmarkAdapter(val emptyView: View, val actionEmitter: Observer<BookmarkAction>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var tree: List<BookmarkNode> = listOf() private var tree: List<BookmarkNode> = listOf()
private var mode: BookmarkState.Mode = BookmarkState.Mode.Normal private var mode: BookmarkState.Mode = BookmarkState.Mode.Normal
private var isFirstRun = true
lateinit var job: Job lateinit var job: Job
fun updateData(tree: BookmarkNode?, mode: BookmarkState.Mode) { fun updateData(tree: BookmarkNode?, mode: BookmarkState.Mode) {
this.tree = tree?.children?.filterNotNull() ?: listOf() this.tree = tree?.children?.filterNotNull() ?: listOf()
isFirstRun = if (isFirstRun) false else {
emptyView.visibility = if (this.tree.isEmpty()) View.VISIBLE else View.GONE
false
}
this.mode = mode this.mode = mode
notifyDataSetChanged() notifyDataSetChanged()
} }
@ -78,36 +86,10 @@ class BookmarkAdapter(val actionEmitter: Observer<BookmarkAction>) : RecyclerVie
@SuppressWarnings("ComplexMethod") @SuppressWarnings("ComplexMethod")
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val bookmarkItemMenu = BookmarkItemMenu(holder.itemView.context) {
when (it) {
is BookmarkItemMenu.Item.Edit -> {
actionEmitter.onNext(BookmarkAction.Edit(tree[position]))
}
is BookmarkItemMenu.Item.Select -> {
actionEmitter.onNext(BookmarkAction.Select(tree[position]))
}
is BookmarkItemMenu.Item.Copy -> {
actionEmitter.onNext(BookmarkAction.Copy(tree[position]))
}
is BookmarkItemMenu.Item.Share -> {
actionEmitter.onNext(BookmarkAction.Share(tree[position]))
}
is BookmarkItemMenu.Item.OpenInNewTab -> {
actionEmitter.onNext(BookmarkAction.OpenInNewTab(tree[position]))
}
is BookmarkItemMenu.Item.OpenInPrivateTab -> {
actionEmitter.onNext(BookmarkAction.OpenInPrivateTab(tree[position]))
}
is BookmarkItemMenu.Item.Delete -> {
actionEmitter.onNext(BookmarkAction.Delete(tree[position]))
}
}
}
when (holder) { when (holder) {
is BookmarkAdapter.BookmarkItemViewHolder -> holder.bind(tree[position], bookmarkItemMenu, mode) is BookmarkAdapter.BookmarkItemViewHolder -> holder.bind(tree[position], mode)
is BookmarkAdapter.BookmarkFolderViewHolder -> holder.bind(tree[position], bookmarkItemMenu, mode) is BookmarkAdapter.BookmarkFolderViewHolder -> holder.bind(tree[position], mode)
is BookmarkAdapter.BookmarkSeparatorViewHolder -> holder.bind(bookmarkItemMenu) is BookmarkAdapter.BookmarkSeparatorViewHolder -> holder.bind(tree[position])
} }
} }
@ -133,12 +115,39 @@ class BookmarkAdapter(val actionEmitter: Observer<BookmarkAction>) : RecyclerVie
bookmark_layout.isClickable = true bookmark_layout.isClickable = true
} }
fun bind(item: BookmarkNode, bookmarkItemMenu: BookmarkItemMenu, mode: BookmarkState.Mode) { fun bind(item: BookmarkNode, mode: BookmarkState.Mode) {
this.item = item this.item = item
this.mode = mode this.mode = mode
val bookmarkItemMenu = BookmarkItemMenu(containerView!!.context, item) {
when (it) {
is BookmarkItemMenu.Item.Edit -> {
actionEmitter.onNext(BookmarkAction.Edit(item))
}
is BookmarkItemMenu.Item.Select -> {
actionEmitter.onNext(BookmarkAction.Select(item))
}
is BookmarkItemMenu.Item.Copy -> {
actionEmitter.onNext(BookmarkAction.Copy(item))
}
is BookmarkItemMenu.Item.Share -> {
actionEmitter.onNext(BookmarkAction.Share(item))
}
is BookmarkItemMenu.Item.OpenInNewTab -> {
actionEmitter.onNext(BookmarkAction.OpenInNewTab(item))
}
is BookmarkItemMenu.Item.OpenInPrivateTab -> {
actionEmitter.onNext(BookmarkAction.OpenInPrivateTab(item))
}
is BookmarkItemMenu.Item.Delete -> {
actionEmitter.onNext(BookmarkAction.Delete(item))
}
}
}
bookmark_overflow.increaseTapArea(bookmarkOverflowExtraDips)
bookmark_overflow.setOnClickListener { bookmark_overflow.setOnClickListener {
bookmarkItemMenu.menuBuilder.build(containerView!!.context).show( bookmarkItemMenu.menuBuilder.build(containerView.context).show(
anchor = it, anchor = it,
orientation = BrowserMenu.Orientation.DOWN orientation = BrowserMenu.Orientation.DOWN
) )
@ -195,12 +204,34 @@ class BookmarkAdapter(val actionEmitter: Observer<BookmarkAction>) : RecyclerVie
bookmark_layout.isClickable = true bookmark_layout.isClickable = true
} }
fun bind(folder: BookmarkNode, bookmarkItemMenu: BookmarkItemMenu, mode: BookmarkState.Mode) { fun bind(folder: BookmarkNode, mode: BookmarkState.Mode) {
bookmark_overflow.setOnClickListener { val bookmarkItemMenu = BookmarkItemMenu(containerView!!.context, folder) {
bookmarkItemMenu.menuBuilder.build(containerView!!.context).show( when (it) {
anchor = it, is BookmarkItemMenu.Item.Edit -> {
orientation = BrowserMenu.Orientation.DOWN actionEmitter.onNext(BookmarkAction.Edit(folder))
) }
is BookmarkItemMenu.Item.Select -> {
actionEmitter.onNext(BookmarkAction.Select(folder))
}
is BookmarkItemMenu.Item.Copy -> {
actionEmitter.onNext(BookmarkAction.Copy(folder))
}
is BookmarkItemMenu.Item.Delete -> {
actionEmitter.onNext(BookmarkAction.Delete(folder))
}
}
}
if (enumValues<BookmarkRoot>().all { it.id != folder.guid }) {
bookmark_overflow.increaseTapArea(bookmarkOverflowExtraDips)
bookmark_overflow.setOnClickListener {
bookmarkItemMenu.menuBuilder.build(containerView.context).show(
anchor = it,
orientation = BrowserMenu.Orientation.DOWN
)
}
} else {
bookmark_overflow.visibility = View.GONE
} }
bookmark_title?.text = folder.title bookmark_title?.text = folder.title
bookmark_layout.setOnClickListener { bookmark_layout.setOnClickListener {
@ -222,14 +253,23 @@ class BookmarkAdapter(val actionEmitter: Observer<BookmarkAction>) : RecyclerVie
init { init {
bookmark_favicon.visibility = View.GONE bookmark_favicon.visibility = View.GONE
bookmark_title.visibility = View.GONE bookmark_title.visibility = View.GONE
bookmark_overflow.increaseTapArea(bookmarkOverflowExtraDips)
bookmark_overflow.visibility = View.VISIBLE bookmark_overflow.visibility = View.VISIBLE
bookmark_separator.visibility = View.VISIBLE bookmark_separator.visibility = View.VISIBLE
bookmark_layout.isClickable = false bookmark_layout.isClickable = false
} }
fun bind(bookmarkItemMenu: BookmarkItemMenu) { fun bind(separator: BookmarkNode) {
val bookmarkItemMenu = BookmarkItemMenu(containerView!!.context, separator) {
when (it) {
is BookmarkItemMenu.Item.Delete -> {
actionEmitter.onNext(BookmarkAction.Delete(separator))
}
}
}
bookmark_overflow.setOnClickListener { bookmark_overflow.setOnClickListener {
bookmarkItemMenu.menuBuilder.build(containerView!!.context).show( bookmarkItemMenu.menuBuilder.build(containerView.context).show(
anchor = it, anchor = it,
orientation = BrowserMenu.Orientation.DOWN orientation = BrowserMenu.Orientation.DOWN
) )
@ -241,6 +281,10 @@ class BookmarkAdapter(val actionEmitter: Observer<BookmarkAction>) : RecyclerVie
} }
} }
companion object {
private const val bookmarkOverflowExtraDips = 8
}
enum class ViewType { enum class ViewType {
ITEM, FOLDER, SEPARATOR ITEM, FOLDER, SEPARATOR
} }

View File

@ -24,6 +24,9 @@ import kotlinx.coroutines.launch
import mozilla.appservices.places.BookmarkRoot import mozilla.appservices.places.BookmarkRoot
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.OAuthAccount
import mozilla.components.concept.sync.Profile
import mozilla.components.support.base.feature.BackHandler import mozilla.components.support.base.feature.BackHandler
import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.BrowsingModeManager import org.mozilla.fenix.BrowsingModeManager
@ -37,10 +40,12 @@ import org.mozilla.fenix.mvi.getManagedEmitter
import org.mozilla.fenix.utils.ItsNotBrokenSnack import org.mozilla.fenix.utils.ItsNotBrokenSnack
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class BookmarkFragment : Fragment(), CoroutineScope, BackHandler { @SuppressWarnings("TooManyFunctions")
class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserver {
private lateinit var job: Job private lateinit var job: Job
private lateinit var bookmarkComponent: BookmarkComponent private lateinit var bookmarkComponent: BookmarkComponent
private lateinit var signInComponent: SignInComponent
private lateinit var currentRoot: BookmarkNode private lateinit var currentRoot: BookmarkNode
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
@ -49,6 +54,7 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler {
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(view.bookmark_layout, ActionBusFactory.get(this)) bookmarkComponent = BookmarkComponent(view.bookmark_layout, ActionBusFactory.get(this))
signInComponent = SignInComponent(view.bookmark_layout, ActionBusFactory.get(this))
return view return view
} }
@ -61,6 +67,14 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
(activity as AppCompatActivity).supportActionBar?.show() (activity as AppCompatActivity).supportActionBar?.show()
checkIfSignedIn()
}
private fun checkIfSignedIn() {
val accountManager = requireComponents.backgroundServices.accountManager
accountManager.register(this, owner = this)
accountManager.authenticatedAccount()?.let { getManagedEmitter<SignInChange>().onNext(SignInChange.SignedIn) }
?: getManagedEmitter<SignInChange>().onNext(SignInChange.SignedOut)
} }
override fun onDestroy() { override fun onDestroy() {
@ -94,7 +108,11 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler {
Navigation.findNavController(requireActivity(), R.id.container).popBackStack() Navigation.findNavController(requireActivity(), R.id.container).popBackStack()
} }
is BookmarkAction.Edit -> { is BookmarkAction.Edit -> {
ItsNotBrokenSnack(context!!).showSnackbar(issueNumber = "1238") Navigation.findNavController(requireActivity(), R.id.container)
.navigate(
BookmarkFragmentDirections
.actionBookmarkFragmentToBookmarkEditFragment(it.item.guid)
)
} }
is BookmarkAction.Select -> { is BookmarkAction.Select -> {
ItsNotBrokenSnack(context!!).showSnackbar(issueNumber = "1239") ItsNotBrokenSnack(context!!).showSnackbar(issueNumber = "1239")
@ -131,6 +149,16 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler {
} }
} }
} }
getAutoDisposeObservable<SignInAction>()
.subscribe {
when (it) {
is SignInAction.ClickedSignIn -> {
requireComponents.services.accountsAuthFeature.beginAuthentication()
(activity as HomeActivity).openToBrowser(null, from = BrowserDirection.FromBookmarks)
}
}
}
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -157,10 +185,25 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler {
currentRoot = requireComponents.core.bookmarksStorage.getTree(currentGuid) as BookmarkNode currentRoot = requireComponents.core.bookmarksStorage.getTree(currentGuid) as BookmarkNode
launch(Main) { launch(Main) {
if (currentGuid != BookmarkRoot.Root.id) (activity as HomeActivity).title = currentRoot.title
getManagedEmitter<BookmarkChange>().onNext(BookmarkChange.Change(currentRoot)) getManagedEmitter<BookmarkChange>().onNext(BookmarkChange.Change(currentRoot))
} }
} }
} }
override fun onBackPressed(): Boolean = (bookmarkComponent.uiView as BookmarkUIView).onBackPressed() override fun onBackPressed(): Boolean = (bookmarkComponent.uiView as BookmarkUIView).onBackPressed()
override fun onAuthenticated(account: OAuthAccount) {
getManagedEmitter<SignInChange>().onNext(SignInChange.SignedIn)
}
override fun onError(error: Exception) {
}
override fun onLoggedOut() {
getManagedEmitter<SignInChange>().onNext(SignInChange.SignedOut)
}
override fun onProfileUpdated(profile: Profile) {
}
} }

View File

@ -7,11 +7,14 @@ package org.mozilla.fenix.library.bookmarks
import android.content.Context import android.content.Context
import mozilla.components.browser.menu.BrowserMenuBuilder import mozilla.components.browser.menu.BrowserMenuBuilder
import mozilla.components.browser.menu.item.SimpleBrowserMenuItem import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType
import org.mozilla.fenix.DefaultThemeManager import org.mozilla.fenix.DefaultThemeManager
import org.mozilla.fenix.R import org.mozilla.fenix.R
class BookmarkItemMenu( class BookmarkItemMenu(
private val context: Context, private val context: Context,
private val item: BookmarkNode,
private val onItemTapped: (BookmarkItemMenu.Item) -> Unit = {} private val onItemTapped: (BookmarkItemMenu.Item) -> Unit = {}
) { ) {
@ -29,24 +32,36 @@ class BookmarkItemMenu(
private val menuItems by lazy { private val menuItems by lazy {
listOf( listOf(
SimpleBrowserMenuItem(context.getString(R.string.bookmark_menu_edit_button)) { if (item.type in listOf(BookmarkNodeType.ITEM, BookmarkNodeType.FOLDER)) {
onItemTapped.invoke(BookmarkItemMenu.Item.Edit) SimpleBrowserMenuItem(context.getString(R.string.bookmark_menu_edit_button)) {
}, onItemTapped.invoke(BookmarkItemMenu.Item.Edit)
SimpleBrowserMenuItem(context.getString(R.string.bookmark_menu_select_button)) { }
onItemTapped.invoke(BookmarkItemMenu.Item.Select) } else null,
}, if (item.type in listOf(BookmarkNodeType.ITEM, BookmarkNodeType.FOLDER)) {
SimpleBrowserMenuItem(context.getString(R.string.bookmark_menu_copy_button)) { SimpleBrowserMenuItem(context.getString(R.string.bookmark_menu_select_button)) {
onItemTapped.invoke(BookmarkItemMenu.Item.Copy) onItemTapped.invoke(BookmarkItemMenu.Item.Select)
}, }
SimpleBrowserMenuItem(context.getString(R.string.bookmark_menu_share_button)) { } else null,
onItemTapped.invoke(BookmarkItemMenu.Item.Share) if (item.type in listOf(BookmarkNodeType.ITEM, BookmarkNodeType.FOLDER)) {
}, SimpleBrowserMenuItem(context.getString(R.string.bookmark_menu_copy_button)) {
SimpleBrowserMenuItem(context.getString(R.string.bookmark_menu_open_in_new_tab_button)) { onItemTapped.invoke(BookmarkItemMenu.Item.Copy)
onItemTapped.invoke(BookmarkItemMenu.Item.OpenInNewTab) }
}, } else null,
SimpleBrowserMenuItem(context.getString(R.string.bookmark_menu_open_in_private_tab_button)) { if (item.type == BookmarkNodeType.ITEM) {
onItemTapped.invoke(BookmarkItemMenu.Item.OpenInPrivateTab) SimpleBrowserMenuItem(context.getString(R.string.bookmark_menu_share_button)) {
}, onItemTapped.invoke(BookmarkItemMenu.Item.Share)
}
} else null,
if (item.type == BookmarkNodeType.ITEM) {
SimpleBrowserMenuItem(context.getString(R.string.bookmark_menu_open_in_new_tab_button)) {
onItemTapped.invoke(BookmarkItemMenu.Item.OpenInNewTab)
}
} else null,
if (item.type == BookmarkNodeType.ITEM) {
SimpleBrowserMenuItem(context.getString(R.string.bookmark_menu_open_in_private_tab_button)) {
onItemTapped.invoke(BookmarkItemMenu.Item.OpenInPrivateTab)
}
} else null,
SimpleBrowserMenuItem( SimpleBrowserMenuItem(
context.getString(R.string.bookmark_menu_delete_button), context.getString(R.string.bookmark_menu_delete_button),
textColorResource = DefaultThemeManager.resolveAttribute( textColorResource = DefaultThemeManager.resolveAttribute(
@ -56,6 +71,6 @@ class BookmarkItemMenu(
) { ) {
onItemTapped.invoke(BookmarkItemMenu.Item.Delete) onItemTapped.invoke(BookmarkItemMenu.Item.Delete)
} }
) ).filterNotNull()
} }
} }

View File

@ -6,11 +6,11 @@ package org.mozilla.fenix.library.bookmarks
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager import android.widget.LinearLayout
import androidx.recyclerview.widget.RecyclerView
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Observer import io.reactivex.Observer
import io.reactivex.functions.Consumer import io.reactivex.functions.Consumer
import kotlinx.android.synthetic.main.component_bookmark.view.*
import mozilla.appservices.places.BookmarkRoot import mozilla.appservices.places.BookmarkRoot
import mozilla.components.support.base.feature.BackHandler import mozilla.components.support.base.feature.BackHandler
import org.mozilla.fenix.R import org.mozilla.fenix.R
@ -29,16 +29,15 @@ class BookmarkUIView(
var canGoBack = false var canGoBack = false
override val view: RecyclerView = LayoutInflater.from(container.context) override val view: LinearLayout = LayoutInflater.from(container.context)
.inflate(R.layout.component_bookmark, container, true) .inflate(R.layout.component_bookmark, container, true) as LinearLayout
.findViewById(R.id.bookmark_list)
private val bookmarkAdapter = BookmarkAdapter(actionEmitter) private val bookmarkAdapter: BookmarkAdapter
init { init {
view.apply { view.bookmark_list.apply {
bookmarkAdapter = BookmarkAdapter(view.bookmarks_empty_view, actionEmitter)
adapter = bookmarkAdapter adapter = bookmarkAdapter
layoutManager = LinearLayoutManager(container.context)
} }
} }

View File

@ -0,0 +1,12 @@
/* 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.library.bookmarks
import androidx.lifecycle.ViewModel
import mozilla.components.concept.storage.BookmarkNode
class BookmarksSharedViewModel : ViewModel() {
var selectedFolder: BookmarkNode? = null
}

View File

@ -0,0 +1,50 @@
/* 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.library.bookmarks
import android.view.ViewGroup
import org.mozilla.fenix.mvi.Action
import org.mozilla.fenix.mvi.ActionBusFactory
import org.mozilla.fenix.mvi.Change
import org.mozilla.fenix.mvi.Reducer
import org.mozilla.fenix.mvi.UIComponent
import org.mozilla.fenix.mvi.UIView
import org.mozilla.fenix.mvi.ViewState
class SignInComponent(
private val container: ViewGroup,
bus: ActionBusFactory,
override var initialState: SignInState =
SignInState(false)
) : UIComponent<SignInState, SignInAction, SignInChange>(
bus.getManagedEmitter(SignInAction::class.java),
bus.getSafeManagedObservable(SignInChange::class.java)
) {
override val reducer: Reducer<SignInState, SignInChange> = { state, change ->
when (change) {
SignInChange.SignedIn -> state.copy(signedIn = true)
SignInChange.SignedOut -> state.copy(signedIn = false)
}
}
override fun initView(): UIView<SignInState, SignInAction, SignInChange> =
SignInUIView(container, actionEmitter, changesObservable)
init {
render(reducer)
}
}
data class SignInState(val signedIn: Boolean) : ViewState
sealed class SignInAction : Action {
object ClickedSignIn : SignInAction()
}
sealed class SignInChange : Change {
object SignedIn : SignInChange()
object SignedOut : SignInChange()
}

View File

@ -0,0 +1,36 @@
/* 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.library.bookmarks
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import io.reactivex.Observable
import io.reactivex.Observer
import io.reactivex.functions.Consumer
import org.mozilla.fenix.R
import org.mozilla.fenix.mvi.UIView
class SignInUIView(
container: ViewGroup,
actionEmitter: Observer<SignInAction>,
changesObservable: Observable<SignInChange>
) : UIView<SignInState, SignInAction, SignInChange>(container, actionEmitter, changesObservable) {
override val view: Button = LayoutInflater.from(container.context)
.inflate(R.layout.component_sign_in, container, true)
.findViewById(R.id.bookmark_folders_sign_in)
init {
view.setOnClickListener {
actionEmitter.onNext(SignInAction.ClickedSignIn)
}
}
override fun updateView() = Consumer<SignInState> {
view.visibility = if (it.signedIn) View.GONE else View.VISIBLE
}
}

View File

@ -0,0 +1,104 @@
/* 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.library.bookmarks.addfolder
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.Navigation
import kotlinx.android.synthetic.main.fragment_add_bookmark_folder.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import mozilla.appservices.places.BookmarkRoot
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getColorFromAttr
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.library.bookmarks.BookmarksSharedViewModel
import kotlin.coroutines.CoroutineContext
class AddBookmarkFolderFragment : Fragment(), CoroutineScope {
private lateinit var sharedViewModel: BookmarksSharedViewModel
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = Job()
setHasOptionsMenu(true)
sharedViewModel = activity?.run {
ViewModelProviders.of(this).get(BookmarksSharedViewModel::class.java)
}!!
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_add_bookmark_folder, container, false)
}
override fun onResume() {
super.onResume()
(activity as AppCompatActivity).supportActionBar?.show()
launch(IO) {
sharedViewModel.selectedFolder = sharedViewModel.selectedFolder
?: requireComponents.core.bookmarksStorage.getTree(BookmarkRoot.Mobile.id)
bookmark_add_folder_parent_selector.text = sharedViewModel.selectedFolder!!.title
bookmark_add_folder_parent_selector.setOnClickListener {
Navigation.findNavController(requireActivity(), R.id.container)
.navigate(
AddBookmarkFolderFragmentDirections
.actionBookmarkAddFolderFragmentToBookmarkSelectFolderFragment(BookmarkRoot.Root.id, true)
)
}
}
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.bookmarks_add_folder, menu)
menu.findItem(R.id.confirm_add_folder_button).icon.colorFilter =
PorterDuffColorFilter(R.attr.iconColor.getColorFromAttr(context!!), PorterDuff.Mode.SRC_IN)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.confirm_add_folder_button -> {
if (bookmark_add_folder_title_edit.text.isEmpty()) {
bookmark_add_folder_title_edit.error = getString(R.string.bookmark_empty_title_error)
return true
}
launch(IO) {
requireComponents.core.bookmarksStorage.addFolder(
sharedViewModel.selectedFolder!!.guid, bookmark_add_folder_title_edit.text.toString(), null
)
launch(Main) {
Navigation.findNavController(requireActivity(), R.id.container).popBackStack()
}
}
true
}
else -> super.onOptionsItemSelected(item)
}
}
}

View File

@ -0,0 +1,181 @@
/* 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.library.bookmarks.edit
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.Navigation
import com.jakewharton.rxbinding3.widget.textChanges
import com.uber.autodispose.AutoDispose
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.BiFunction
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.fragment_edit_bookmark.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import mozilla.appservices.places.UrlParseFailed
import mozilla.components.concept.storage.BookmarkInfo
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getColorFromAttr
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.library.bookmarks.BookmarksSharedViewModel
import java.lang.IllegalArgumentException
import java.util.concurrent.TimeUnit
import kotlin.coroutines.CoroutineContext
class EditBookmarkFragment : Fragment(), CoroutineScope {
private lateinit var sharedViewModel: BookmarksSharedViewModel
private lateinit var job: Job
private lateinit var guidToEdit: String
private var bookmarkNode: BookmarkNode? = null
private var bookmarkParent: BookmarkNode? = null
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = Job()
setHasOptionsMenu(true)
sharedViewModel = activity?.run {
ViewModelProviders.of(this).get(BookmarksSharedViewModel::class.java)
}!!
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_edit_bookmark, container, false)
}
override fun onResume() {
super.onResume()
(activity as? AppCompatActivity)?.supportActionBar?.show()
guidToEdit = EditBookmarkFragmentArgs.fromBundle(arguments!!).guidToEdit
launch(IO) {
bookmarkNode = requireComponents.core.bookmarksStorage.getTree(guidToEdit)
bookmarkParent = sharedViewModel.selectedFolder
?: bookmarkNode?.parentGuid?.let { requireComponents.core.bookmarksStorage.getTree(it) }
launch(Main) {
when (bookmarkNode?.type) {
BookmarkNodeType.FOLDER -> {
bookmark_url_edit.visibility = View.GONE
bookmark_url_label.visibility = View.GONE
}
BookmarkNodeType.ITEM -> {}
BookmarkNodeType.SEPARATOR -> {}
else -> throw IllegalArgumentException()
}
bookmark_name_edit.setText(bookmarkNode!!.title)
bookmark_url_edit.setText(bookmarkNode!!.url)
}
bookmarkParent?.let { node ->
launch(Main) {
bookmark_folder_selector.text = node.title
bookmark_folder_selector.setOnClickListener {
sharedViewModel.selectedFolder = null
Navigation.findNavController(requireActivity(), R.id.container).navigate(
EditBookmarkFragmentDirections
.actionBookmarkEditFragmentToBookmarkSelectFolderFragment(null)
)
}
}
}
}
updateBookmarkFromObservableInput()
}
override fun onPause() {
updateBookmarkNode(Pair(bookmark_name_edit.text, bookmark_url_edit.text))
super.onPause()
}
private fun updateBookmarkFromObservableInput() {
Observable.combineLatest(
bookmark_name_edit.textChanges().skipInitialValue(),
bookmark_url_edit.textChanges().skipInitialValue(),
BiFunction { name: CharSequence, url: CharSequence ->
Pair(name, url)
})
.filter { it.first.isNotBlank() && it.second.isNotBlank() }
.debounce(debouncePeriodInMs, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.`as`(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(this@EditBookmarkFragment)))
.subscribe {
try {
bookmark_url_edit.error = null
updateBookmarkNode(it)
} catch (e: UrlParseFailed) {
bookmark_url_edit.error = getString(R.string.bookmark_invalid_url_error)
}
}
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.bookmarks_edit, menu)
menu.findItem(R.id.delete_bookmark_button).icon.colorFilter =
PorterDuffColorFilter(R.attr.iconColor.getColorFromAttr(context!!), PorterDuff.Mode.SRC_IN)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.delete_bookmark_button -> {
launch(IO) {
requireComponents.core.bookmarksStorage.deleteNode(guidToEdit)
launch(Main) {
Navigation.findNavController(requireActivity(), R.id.container).popBackStack()
}
}
true
}
else -> super.onOptionsItemSelected(item)
}
}
private fun updateBookmarkNode(pair: Pair<CharSequence, CharSequence>) {
launch(IO) {
requireComponents.core.bookmarksStorage.updateNode(
guidToEdit,
BookmarkInfo(
sharedViewModel.selectedFolder?.guid ?: bookmarkNode!!.parentGuid,
bookmarkNode!!.position,
pair.first.toString(),
if (bookmarkNode?.type == BookmarkNodeType.ITEM) pair.second.toString() else null
)
)
}
}
companion object {
private const val debouncePeriodInMs = 500L
}
}

View File

@ -0,0 +1,126 @@
/* 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.library.bookmarks.selectfolder
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.bookmark_row.*
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType
import mozilla.components.support.ktx.android.content.res.pxToDp
import org.mozilla.fenix.R
import org.mozilla.fenix.library.bookmarks.BookmarksSharedViewModel
class SelectBookmarkFolderAdapter(private val sharedViewModel: BookmarksSharedViewModel) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var tree: List<BookmarkNodeWithDepth> = listOf()
fun updateData(tree: BookmarkNode?) {
this.tree = tree!!.convertToFolderDepthTree().drop(1)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.bookmark_row, parent, false)
return when (viewType) {
BookmarkFolderViewHolder.viewType -> SelectBookmarkFolderAdapter.BookmarkFolderViewHolder(
view
)
else -> throw IllegalStateException("ViewType $viewType does not match to a ViewHolder")
}
}
override fun getItemViewType(position: Int): Int {
return when (tree[position].node.type) {
BookmarkNodeType.FOLDER -> BookmarkFolderViewHolder.viewType
else -> throw IllegalStateException("Item $tree[position] does not match to a ViewType")
}
}
override fun getItemCount(): Int = tree.size
@SuppressWarnings("ComplexMethod")
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is SelectBookmarkFolderAdapter.BookmarkFolderViewHolder -> holder.bind(
tree[position],
tree[position].node == sharedViewModel.selectedFolder,
object : SelectionInterface {
override fun itemSelected(node: BookmarkNode) {
sharedViewModel.selectedFolder = node
notifyDataSetChanged()
}
})
else -> {
}
}
}
interface SelectionInterface {
fun itemSelected(node: BookmarkNode)
}
class BookmarkFolderViewHolder(
view: View,
override val containerView: View? = view
) :
RecyclerView.ViewHolder(view), LayoutContainer {
init {
bookmark_favicon.visibility = View.VISIBLE
bookmark_title.visibility = View.VISIBLE
bookmark_separator.visibility = View.GONE
bookmark_layout.isClickable = true
}
fun bind(folder: BookmarkNodeWithDepth, selected: Boolean, selectionInterface: SelectionInterface) {
val backgroundTint =
if (selected) R.color.bookmark_selection_appbar_background else R.color.bookmark_favicon_background
val backgroundTintList = ContextCompat.getColorStateList(containerView!!.context, backgroundTint)
bookmark_favicon.backgroundTintList = backgroundTintList
val res = if (selected) R.drawable.mozac_ic_check else R.drawable.ic_folder_icon
bookmark_favicon.setImageResource(res)
bookmark_overflow.visibility = View.GONE
bookmark_title?.text = folder.node.title
bookmark_layout.setOnClickListener {
selectionInterface.itemSelected(folder.node)
}
val padding =
containerView.resources.pxToDp(dpsToIndent) * (if (folder.depth > maxDepth) maxDepth else folder.depth)
bookmark_layout.setPadding(padding, 0, 0, 0)
}
companion object {
const val viewType = 1
}
}
data class BookmarkNodeWithDepth(val depth: Int, val node: BookmarkNode, val parent: String?)
private fun BookmarkNode?.convertToFolderDepthTree(
depth: Int = 0,
list: List<BookmarkNodeWithDepth> = listOf()
): List<BookmarkNodeWithDepth> {
return if (this != null) {
val newList = list.plus(listOf(BookmarkNodeWithDepth(depth, this, this.parentGuid)))
newList.plus(
children?.filter { it?.type == BookmarkNodeType.FOLDER }
?.flatMap { it.convertToFolderDepthTree(depth + 1) }
?: listOf())
} else listOf()
}
companion object {
private const val maxDepth = 10
private const val dpsToIndent = 10
}
}

View File

@ -0,0 +1,157 @@
/* 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.library.bookmarks.selectfolder
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.Navigation
import kotlinx.android.synthetic.main.fragment_select_bookmark_folder.*
import kotlinx.android.synthetic.main.fragment_select_bookmark_folder.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.concept.sync.Profile
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getColorFromAttr
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.library.bookmarks.BookmarksSharedViewModel
import org.mozilla.fenix.library.bookmarks.SignInAction
import org.mozilla.fenix.library.bookmarks.SignInChange
import org.mozilla.fenix.library.bookmarks.SignInComponent
import org.mozilla.fenix.mvi.ActionBusFactory
import org.mozilla.fenix.mvi.getAutoDisposeObservable
import org.mozilla.fenix.mvi.getManagedEmitter
import kotlin.coroutines.CoroutineContext
@SuppressWarnings("TooManyFunctions")
class SelectBookmarkFolderFragment : Fragment(), CoroutineScope, AccountObserver {
private lateinit var sharedViewModel: BookmarksSharedViewModel
private lateinit var job: Job
private var folderGuid: String? = null
private var bookmarkNode: BookmarkNode? = null
private lateinit var signInComponent: SignInComponent
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = Job()
setHasOptionsMenu(true)
sharedViewModel = activity?.run {
ViewModelProviders.of(this).get(BookmarksSharedViewModel::class.java)
}!!
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_select_bookmark_folder, container, false)
signInComponent = SignInComponent(view.select_bookmark_layout, ActionBusFactory.get(this))
return view
}
override fun onStart() {
super.onStart()
getAutoDisposeObservable<SignInAction>()
.subscribe {
when (it) {
is SignInAction.ClickedSignIn -> {
requireComponents.services.accountsAuthFeature.beginAuthentication()
view?.let {
(activity as HomeActivity).openToBrowser(null, BrowserDirection.FromBookmarksFolderSelect)
}
}
}
}
}
override fun onResume() {
super.onResume()
(activity as AppCompatActivity).supportActionBar?.show()
folderGuid = SelectBookmarkFolderFragmentArgs.fromBundle(arguments!!).folderGuid ?: BookmarkRoot.Root.id
checkIfSignedIn()
launch(IO) {
bookmarkNode = requireComponents.core.bookmarksStorage.getTree(folderGuid!!, true)
launch(Main) {
(activity as HomeActivity).title = bookmarkNode?.title ?: getString(R.string.library_bookmarks)
val adapter = SelectBookmarkFolderAdapter(sharedViewModel)
recylerView_bookmark_folders.adapter = adapter
adapter.updateData(bookmarkNode)
}
}
}
private fun checkIfSignedIn() {
val accountManager = requireComponents.backgroundServices.accountManager
accountManager.register(this, owner = this)
accountManager.authenticatedAccount()?.let { getManagedEmitter<SignInChange>().onNext(SignInChange.SignedIn) }
?: getManagedEmitter<SignInChange>().onNext(SignInChange.SignedOut)
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
val visitedAddBookmark = SelectBookmarkFolderFragmentArgs.fromBundle(arguments!!).visitedAddBookmark
if (!visitedAddBookmark) {
inflater.inflate(R.menu.bookmarks_select_folder, menu)
menu.findItem(R.id.add_folder_button).icon.colorFilter =
PorterDuffColorFilter(R.attr.iconColor.getColorFromAttr(context!!), PorterDuff.Mode.SRC_IN)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.add_folder_button -> {
launch(Main) {
Navigation.findNavController(requireActivity(), R.id.container).navigate(
SelectBookmarkFolderFragmentDirections
.actionBookmarkSelectFolderFragmentToBookmarkAddFolderFragment()
)
}
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onAuthenticated(account: OAuthAccount) {
getManagedEmitter<SignInChange>().onNext(SignInChange.SignedIn)
}
override fun onError(error: Exception) {
}
override fun onLoggedOut() {
getManagedEmitter<SignInChange>().onNext(SignInChange.SignedOut)
}
override fun onProfileUpdated(profile: Profile) {
}
}

View File

@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <!-- 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/. -->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/bookmark_layout" android:id="@+id/bookmark_layout"
@ -23,7 +27,7 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintDimensionRatio="1:1" app:layout_constraintDimensionRatio="1:1"
tools:foregroundTint="@android:color/black" android:foregroundTint="?attr/iconColor"
tools:src="@drawable/ic_folder_icon" /> tools:src="@drawable/ic_folder_icon" />
<TextView <TextView
@ -34,6 +38,7 @@
android:ellipsize="end" android:ellipsize="end"
android:lines="1" android:lines="1"
android:textSize="16sp" android:textSize="16sp"
android:textColor="?attr/bookmarksEditTextColor"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/bookmark_overflow" app:layout_constraintEnd_toStartOf="@id/bookmark_overflow"
app:layout_constraintHorizontal_bias="0" app:layout_constraintHorizontal_bias="0"
@ -57,6 +62,7 @@
android:id="@+id/bookmark_separator" android:id="@+id/bookmark_separator"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="1dp" android:layout_height="1dp"
android:layout_marginStart="68dp"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:importantForAccessibility="no" android:importantForAccessibility="no"
@ -64,7 +70,7 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/bookmark_overflow" app:layout_constraintEnd_toStartOf="@id/bookmark_overflow"
android:background="@android:color/black" android:background="?attr/bookmarksEditTextColor"
android:visibility="gone"/> android:visibility="gone"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -2,9 +2,27 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public <!-- 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 - 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/. --> - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.recyclerview.widget.RecyclerView xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/bookmark_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/bookmark_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/bookmarks_empty_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/bookmarks_empty_message"
android:visibility="gone"
android:textColor="?attr/bookmarksLabelColor"/>
</FrameLayout>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<Button
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/bookmark_folders_sign_in"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/sign_in_button"
android:padding="10dp"
android:layout_marginTop="32dp"
android:textSize="14sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/bookmark_add_folder_title_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/bookmark_name_label"
android:textAllCaps="true"
android:textColor="?attr/bookmarksLabelColor"
android:textSize="12sp" />
<EditText
android:id="@+id/bookmark_add_folder_title_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:inputType="textAutoComplete"
android:textColor="?attr/bookmarksEditTextColor"
android:textSize="15sp"
tools:text="News" />
<TextView
android:id="@+id/bookmark_add_folder_parent_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/bookmark_folder_label"
android:textAllCaps="true"
android:textColor="?attr/bookmarksLabelColor"
android:textSize="12sp" />
<TextView
android:id="@+id/bookmark_add_folder_parent_selector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:drawableStart="@drawable/ic_folder_icon"
android:drawablePadding="10dp"
android:drawableTint="?attr/iconColor"
android:textColor="?attr/bookmarksEditTextColor"
android:textSize="16sp"
tools:targetApi="m"
tools:text="Mobile Bookmarks" />
</LinearLayout>

View File

@ -8,5 +8,4 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
tools:context="org.mozilla.fenix.library.bookmarks.BookmarkFragment"> tools:context="org.mozilla.fenix.library.bookmarks.BookmarkFragment" />
</LinearLayout>

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_margin="16dp">
<TextView
android:id="@+id/bookmark_name_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/bookmark_name_label"
android:textColor="?attr/bookmarksLabelColor"
android:textSize="12sp"
android:textAllCaps="true"/>
<EditText
android:id="@+id/bookmark_name_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:textSize="15sp"
android:textColor="?attr/bookmarksEditTextColor"
tools:text="Internet for people, not profit -- Mozilla"
android:inputType="textAutoComplete"/>
<TextView
android:id="@+id/bookmark_url_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/bookmark_url_label"
android:textColor="?attr/bookmarksLabelColor"
android:textSize="12sp"
android:textAllCaps="true"/>
<EditText
android:id="@+id/bookmark_url_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:textSize="15sp"
android:textColor="?attr/bookmarksEditTextColor"
tools:text="https://www.mozilla.org/en-US/"
android:inputType="textUri"/>
<TextView
android:id="@+id/bookmark_folder_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/bookmark_folder_label"
android:textColor="?attr/bookmarksLabelColor"
android:textSize="12sp"
android:textAllCaps="true"/>
<TextView
android:id="@+id/bookmark_folder_selector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="16sp"
android:textColor="?attr/bookmarksEditTextColor"
android:drawableStart="@drawable/ic_folder_icon"
android:drawablePadding="10dp"
android:drawableTint="?attr/iconColor"
tools:text="Mobile Bookmarks"
tools:targetApi="m" />
</LinearLayout>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/select_bookmark_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recylerView_bookmark_folders"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</LinearLayout>

View File

@ -7,7 +7,7 @@
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<item <item
android:id="@+id/confirm_add_folder_button" android:id="@+id/confirm_add_folder_button"
android:icon="@drawable/ic_new" android:icon="@drawable/mozac_ic_check"
android:iconTint="?attr/iconColor" android:iconTint="?attr/iconColor"
android:title="@string/bookmark_add_folder" android:title="@string/bookmark_add_folder"
app:showAsAction="ifRoom" app:showAsAction="ifRoom"

View File

@ -79,6 +79,9 @@
<action <action
android:id="@+id/action_browserFragment_to_libraryFragment" android:id="@+id/action_browserFragment_to_libraryFragment"
app:destination="@id/libraryFragment" /> app:destination="@id/libraryFragment" />
<action
android:id="@+id/action_browserFragment_to_bookmarkEditFragment"
app:destination="@id/bookmarkEditFragment" />
</fragment> </fragment>
<fragment <fragment
@ -115,6 +118,56 @@
<action <action
android:id="@+id/action_bookmarkFragment_self" android:id="@+id/action_bookmarkFragment_self"
app:destination="@id/bookmarkFragment" /> app:destination="@id/bookmarkFragment" />
<action
android:id="@+id/action_bookmarkFragment_to_bookmarkEditFragment"
app:destination="@id/bookmarkEditFragment" />
</fragment>
<fragment
android:id="@+id/bookmarkEditFragment"
android:name="org.mozilla.fenix.library.bookmarks.edit.EditBookmarkFragment"
android:label="@string/edit_bookmark_fragment_title"
tools:layout="@layout/fragment_edit_bookmark">
<argument
android:name="guidToEdit"
app:argType="string"
app:nullable="false"/>
<action
android:id="@+id/action_bookmarkEditFragment_to_bookmarkSelectFolderFragment"
app:destination="@id/bookmarkSelectFolderFragment" />
</fragment>
<fragment
android:id="@+id/bookmarkSelectFolderFragment"
android:name="org.mozilla.fenix.library.bookmarks.selectfolder.SelectBookmarkFolderFragment"
android:label="@string/bookmark_select_folder_fragment_label"
tools:layout="@layout/fragment_select_bookmark_folder">
<argument
android:name="folderGuid"
app:argType="string"
app:nullable="true"/>
<action
android:id="@+id/action_bookmarkSelectFolderFragment_self"
app:destination="@id/bookmarkSelectFolderFragment" />
<action
android:id="@+id/action_bookmarkSelectFolderFragment_to_bookmarkAddFolderFragment"
app:destination="@id/bookmarkAddFolderFragment" />
<argument
android:name="visitedAddBookmark"
app:argType="boolean"
android:defaultValue="false" />
<action
android:id="@+id/action_bookmarkSelectFolderFragment_to_browserFragment"
app:destination="@id/browserFragment" />
</fragment>
<fragment
android:id="@+id/bookmarkAddFolderFragment"
android:name="org.mozilla.fenix.library.bookmarks.addfolder.AddBookmarkFolderFragment"
android:label="@string/bookmark_add_folder_fragment_label">
<action
android:id="@+id/action_bookmarkAddFolderFragment_to_bookmarkSelectFolderFragment"
app:destination="@id/bookmarkSelectFolderFragment" />
</fragment> </fragment>
<fragment <fragment

View File

@ -59,6 +59,13 @@
<!-- Library --> <!-- Library -->
<color name="library_list_item_text_color_light_mode">@color/off_white</color> <color name="library_list_item_text_color_light_mode">@color/off_white</color>
<!-- Bookmarks -->
<color name="bookmark_favicon_background">#DFDFE3</color>
<color name="bookmark_snackbar_background">#2E0EC1</color>
<color name="bookmark_selection_appbar_background">#2E0EC1</color>
<color name="bookmarks_label_normal_theme">#AFAD9C</color>
<color name="bookmarks_edit_normal_theme">@color/off_white</color>
<!-- Search Fragment --> <!-- Search Fragment -->
<color name="suggestionBackground_normal_theme">@color/accent_bright_dark_theme</color> <color name="suggestionBackground_normal_theme">@color/accent_bright_dark_theme</color>
<color name="search_text">@color/off_white</color> <color name="search_text">@color/off_white</color>

View File

@ -55,6 +55,10 @@
<attr name="historyTitleColor" format="reference" /> <attr name="historyTitleColor" format="reference" />
<attr name="historyHeader" format="reference" /> <attr name="historyHeader" format="reference" />
<!-- Bookmarks Fragment -->
<attr name="bookmarksLabelColor" format="reference" />
<attr name="bookmarksEditTextColor" format="reference" />
<!-- Library Fragment --> <!-- Library Fragment -->
<attr name="libraryListItemTextColor" format="reference" /> <attr name="libraryListItemTextColor" format="reference" />
</resources> </resources>

View File

@ -15,6 +15,8 @@
<!-- Bookmarks --> <!-- Bookmarks -->
<color name="bookmark_favicon_background">#DFDFE3</color> <color name="bookmark_favicon_background">#DFDFE3</color>
<color name="bookmark_snackbar_background">#2E0EC1</color>
<color name="bookmark_selection_appbar_background">#2E0EC1</color>
<!-- Specific colors for dark theme --> <!-- Specific colors for dark theme -->
<color name="background_dark_theme">#1C1B22</color> <color name="background_dark_theme">#1C1B22</color>
@ -30,6 +32,8 @@
<color name="session_placeholder_purple">@color/accent_bright_dark_theme</color> <color name="session_placeholder_purple">@color/accent_bright_dark_theme</color>
<color name="session_placeholder_pink">#FF4AA2</color> <color name="session_placeholder_pink">#FF4AA2</color>
<!-- Normal Theme --> <!-- Normal Theme -->
<color name="bookmarks_label_normal_theme">#505263</color>
<color name="bookmarks_edit_normal_theme">@color/text_color_normal_theme</color>
<color name="history_header_normal_theme">#696A6A</color> <color name="history_header_normal_theme">#696A6A</color>
<color name="history_title_normal_theme">@color/text_color_normal_theme</color> <color name="history_title_normal_theme">@color/text_color_normal_theme</color>
<color name="history_url_normal_theme">#696A6A</color> <color name="history_url_normal_theme">#696A6A</color>
@ -94,6 +98,8 @@
<color name="private_browsing_top_gradient">#242251</color> <color name="private_browsing_top_gradient">#242251</color>
<color name="private_browsing_bottom_gradient">#393862</color> <color name="private_browsing_bottom_gradient">#393862</color>
<color name="search_pill_private_selected_background">#080639</color> <color name="search_pill_private_selected_background">#080639</color>
<color name="bookmarks_label_private_theme">@color/photonGrey40</color>
<color name="bookmarks_edit_private_theme">@color/off_white</color>
<color name="history_header_private_theme">@color/photonGrey40</color> <color name="history_header_private_theme">@color/photonGrey40</color>
<color name="history_title_private_theme">@color/off_white</color> <color name="history_title_private_theme">@color/off_white</color>
<color name="history_url_private_theme">@color/photonGrey40</color> <color name="history_url_private_theme">@color/photonGrey40</color>

View File

@ -285,7 +285,7 @@
<!-- Screen title for adding a bookmarks folder --> <!-- Screen title for adding a bookmarks folder -->
<string name="bookmark_add_folder">Add folder</string> <string name="bookmark_add_folder">Add folder</string>
<!-- Snackbar title shown after a bookmark has been created. --> <!-- Snackbar title shown after a bookmark has been created. -->
<string name="bookmark_created_snackbar">Bookmark created.</string> <string name="bookmark_created_snackbar">Bookmark saved!</string>
<!-- Snackbar edit button shown after a bookmark has been created. --> <!-- Snackbar edit button shown after a bookmark has been created. -->
<string name="edit_bookmark_snackbar_action">EDIT</string> <string name="edit_bookmark_snackbar_action">EDIT</string>
@ -303,4 +303,15 @@
<string name="bookmark_menu_open_in_private_tab_button">Open in private tab</string> <string name="bookmark_menu_open_in_private_tab_button">Open in private tab</string>
<!-- Bookmark overflow menu delete button --> <!-- Bookmark overflow menu delete button -->
<string name="bookmark_menu_delete_button">Delete</string> <string name="bookmark_menu_delete_button">Delete</string>
<string name="edit_bookmark_fragment_title">Edit bookmark</string>
<string name="sign_in_button">Sign in to see synced bookmarks</string>
<string name="bookmark_url_label">URL</string>
<string name="bookmark_folder_label">FOLDER</string>
<string name="bookmark_name_label">NAME</string>
<string name="bookmark_add_folder_fragment_label">Add folder</string>
<string name="bookmark_select_folder_fragment_label">Select folder</string>
<string name="bookmark_empty_title_error">Must have a title</string>
<string name="bookmark_invalid_url_error">Invalid URL</string>
<string name="bookmarks_empty_message">No bookmarks here</string>
</resources> </resources>

View File

@ -73,6 +73,10 @@
<item name="historyURLColor">@color/history_url_normal_theme</item> <item name="historyURLColor">@color/history_url_normal_theme</item>
<item name="historyHeader">@color/history_header_normal_theme</item> <item name="historyHeader">@color/history_header_normal_theme</item>
<!-- Bookmark fragment colors -->
<item name="bookmarksLabelColor">@color/bookmarks_label_normal_theme</item>
<item name="bookmarksEditTextColor">@color/bookmarks_edit_normal_theme</item>
<!-- Library Fragment --> <!-- Library Fragment -->
<item name="libraryListItemTextColor">@color/library_list_item_text_color_light_mode</item> <item name="libraryListItemTextColor">@color/library_list_item_text_color_light_mode</item>
</style> </style>
@ -138,6 +142,10 @@
<item name="historyURLColor">@color/history_url_private_theme</item> <item name="historyURLColor">@color/history_url_private_theme</item>
<item name="historyHeader">@color/history_header_private_theme</item> <item name="historyHeader">@color/history_header_private_theme</item>
<!-- Bookmark fragment colors -->
<item name="bookmarksLabelColor">@color/bookmarks_label_private_theme</item>
<item name="bookmarksEditTextColor">@color/bookmarks_edit_private_theme</item>
<!-- Library Fragment --> <!-- Library Fragment -->
<item name="libraryListItemTextColor">@color/off_white</item> <item name="libraryListItemTextColor">@color/off_white</item>
</style> </style>

View File

@ -7,6 +7,7 @@ package org.mozilla.fenix.library.bookmarks
import io.mockk.Runs import io.mockk.Runs
import io.mockk.every import io.mockk.every
import io.mockk.just import io.mockk.just
import io.mockk.mockk
import io.mockk.spyk import io.mockk.spyk
import io.mockk.verifySequence import io.mockk.verifySequence
import io.reactivex.Observer import io.reactivex.Observer
@ -27,7 +28,7 @@ internal class BookmarkAdapterTest {
setRxSchedulers() setRxSchedulers()
emitter = TestObserver<BookmarkAction>() emitter = TestObserver<BookmarkAction>()
bookmarkAdapter = spyk( bookmarkAdapter = spyk(
BookmarkAdapter(emitter), recordPrivateCalls = true BookmarkAdapter(mockk(), emitter), recordPrivateCalls = true
) )
every { bookmarkAdapter.notifyDataSetChanged() } just Runs every { bookmarkAdapter.notifyDataSetChanged() } just Runs
} }

View File

@ -7,6 +7,7 @@ private object Versions {
const val android_gradle_plugin = "3.3.2" const val android_gradle_plugin = "3.3.2"
const val rxAndroid = "2.1.0" const val rxAndroid = "2.1.0"
const val rxKotlin = "2.3.0" const val rxKotlin = "2.3.0"
const val rxBindings = "3.0.0-alpha2"
const val anko = "0.10.8" const val anko = "0.10.8"
const val sentry = "1.7.10" const val sentry = "1.7.10"
const val leakcanary = "1.6.3" const val leakcanary = "1.6.3"
@ -52,6 +53,7 @@ object Deps {
const val rxKotlin = "io.reactivex.rxjava2:rxkotlin:${Versions.rxKotlin}" const val rxKotlin = "io.reactivex.rxjava2:rxkotlin:${Versions.rxKotlin}"
const val rxAndroid = "io.reactivex.rxjava2:rxandroid:${Versions.rxAndroid}" const val rxAndroid = "io.reactivex.rxjava2:rxandroid:${Versions.rxAndroid}"
const val rxBindings = "com.jakewharton.rxbinding3:rxbinding:${Versions.rxBindings}"
const val anko_commons = "org.jetbrains.anko:anko-commons:${Versions.anko}" const val anko_commons = "org.jetbrains.anko:anko-commons:${Versions.anko}"
const val anko_sdk = "org.jetbrains.anko:anko-sdk25:${Versions.anko}" const val anko_sdk = "org.jetbrains.anko:anko-sdk25:${Versions.anko}"
@ -123,6 +125,7 @@ object Deps {
const val androidx_appcompat = "androidx.appcompat:appcompat:${Versions.androidx_appcompat}" const val androidx_appcompat = "androidx.appcompat:appcompat:${Versions.androidx_appcompat}"
const val androidx_constraintlayout = "androidx.constraintlayout:constraintlayout:${Versions.androidx_constraint_layout}" const val androidx_constraintlayout = "androidx.constraintlayout:constraintlayout:${Versions.androidx_constraint_layout}"
const val androidx_legacy = "androidx.legacy:legacy-support-v4:${Versions.androidx_legacy}" const val androidx_legacy = "androidx.legacy:legacy-support-v4:${Versions.androidx_legacy}"
const val androidx_lifecycle_viewmodel_ktx = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.androidx_lifecycle}"
const val androidx_lifecycle_runtime = "androidx.lifecycle:lifecycle-runtime:${Versions.androidx_lifecycle}" const val androidx_lifecycle_runtime = "androidx.lifecycle:lifecycle-runtime:${Versions.androidx_lifecycle}"
const val androidx_preference = "androidx.preference:preference-ktx:${Versions.androidx_preference}" const val androidx_preference = "androidx.preference:preference-ktx:${Versions.androidx_preference}"
const val androidx_safeargs = "androidx.navigation:navigation-safe-args-gradle-plugin:${Versions.androidx_navigation}" const val androidx_safeargs = "androidx.navigation:navigation-safe-args-gradle-plugin:${Versions.androidx_navigation}"