1
0
Fork 0

Issue #2379 - Use LibraryPageView in history

master
Tiger Oakes 2019-08-01 11:58:41 -04:00 committed by Emily Kager
parent 2813a3cff7
commit ccae66c08a
11 changed files with 172 additions and 265 deletions

View File

@ -17,7 +17,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
@ -100,14 +99,9 @@ fun Context.share(text: String, subject: String = ""): Boolean {
fun Context.getRootView(): View? = fun Context.getRootView(): View? =
asActivity()?.window?.decorView?.findViewById<View>(android.R.id.content) as? ViewGroup asActivity()?.window?.decorView?.findViewById<View>(android.R.id.content) as? ViewGroup
/**
* Returns the color resource corresponding to the attribute.
*/
@ColorRes
fun Context.getColorResFromAttr(@AttrRes attr: Int) = ThemeManager.resolveAttribute(attr, this)
/** /**
* Returns the color int corresponding to the attribute. * Returns the color int corresponding to the attribute.
*/ */
@ColorInt @ColorInt
fun Context.getColorFromAttr(@AttrRes attr: Int) = ContextCompat.getColor(this, getColorResFromAttr(attr)) fun Context.getColorFromAttr(@AttrRes attr: Int) =
ContextCompat.getColor(this, ThemeManager.resolveAttribute(attr, this))

View File

@ -0,0 +1,39 @@
/* 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
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import org.mozilla.fenix.BrowsingModeManager
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
abstract class LibraryPageFragment<T> : Fragment() {
abstract val selectedItems: Set<T>
protected fun close() {
findNavController().popBackStack(R.id.libraryFragment, true)
}
protected fun openItemsInNewTab(private: Boolean = false, toUrl: (T) -> String?) {
context?.components?.useCases?.tabsUseCases?.let { tabsUseCases ->
val addTab = if (private) tabsUseCases.addPrivateTab else tabsUseCases.addTab
selectedItems.asSequence()
.mapNotNull(toUrl)
.forEach { url ->
addTab.invoke(url)
}
}
(activity as HomeActivity).browsingModeManager.mode = if (private) {
BrowsingModeManager.Mode.Private
} else {
BrowsingModeManager.Mode.Normal
}
(activity as HomeActivity).supportActionBar?.hide()
}
}

View File

@ -11,29 +11,44 @@ import android.graphics.PorterDuffColorFilter
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ActionMenuView import android.widget.ActionMenuView
import android.widget.ImageButton import android.widget.ImageButton
import androidx.annotation.ColorRes import androidx.annotation.ColorInt
import androidx.appcompat.view.menu.ActionMenuItemView import androidx.appcompat.view.menu.ActionMenuItemView
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.forEach import androidx.core.view.forEach
import kotlinx.android.extensions.LayoutContainer
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.asActivity import org.mozilla.fenix.ext.asActivity
import org.mozilla.fenix.ext.getColorFromAttr
open class LibraryPageView( open class LibraryPageView(
container: ViewGroup override val containerView: ViewGroup
) { ) : LayoutContainer {
protected val context: Context = container.context protected val context: Context inline get() = containerView.context
protected val activity = context.asActivity() protected val activity = context.asActivity()
protected fun setUiForNormalMode(title: String?) {
activity?.title = title
setToolbarColors(
ContextCompat.getColor(context, R.color.white_color),
context.getColorFromAttr(R.attr.accentHighContrast)
)
}
protected fun setUiForSelectingMode(title: String?) {
activity?.title = title
setToolbarColors(
context.getColorFromAttr(R.attr.primaryText),
context.getColorFromAttr(R.attr.foundation)
)
}
/** /**
* Adjust the colors of the [Toolbar] on the top of the screen. * Adjust the colors of the [Toolbar] on the top of the screen.
*/ */
protected fun setToolbarColors(@ColorRes foregroundRes: Int, @ColorRes backgroundRes: Int) { private fun setToolbarColors(@ColorInt foreground: Int, @ColorInt background: Int) {
val toolbar = activity?.findViewById<Toolbar>(R.id.navigationToolbar) val toolbar = activity?.findViewById<Toolbar>(R.id.navigationToolbar)
val foreground = ContextCompat.getColor(context, foregroundRes)
val background = ContextCompat.getColor(context, backgroundRes)
toolbar?.apply { toolbar?.apply {
setBackgroundColor(background) setBackgroundColor(background)
setTitleTextColor(foreground) setTitleTextColor(foreground)

View File

@ -15,7 +15,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -34,8 +33,6 @@ import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.concept.sync.Profile import mozilla.components.concept.sync.Profile
import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.base.feature.BackHandler import mozilla.components.support.base.feature.BackHandler
import org.mozilla.fenix.BrowsingModeManager
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbarPresenter import org.mozilla.fenix.components.FenixSnackbarPresenter
import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.components.StoreProvider
@ -47,10 +44,11 @@ import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.setRootTitles 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.ext.withOptionalDesktopFolders
import org.mozilla.fenix.library.LibraryPageFragment
import org.mozilla.fenix.utils.allowUndo import org.mozilla.fenix.utils.allowUndo
@SuppressWarnings("TooManyFunctions", "LargeClass") @SuppressWarnings("TooManyFunctions", "LargeClass")
class BookmarkFragment : Fragment(), BackHandler, AccountObserver { class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), BackHandler, AccountObserver {
private lateinit var bookmarkStore: BookmarkStore private lateinit var bookmarkStore: BookmarkStore
private lateinit var bookmarkView: BookmarkView private lateinit var bookmarkView: BookmarkView
@ -75,6 +73,8 @@ class BookmarkFragment : Fragment(), BackHandler, AccountObserver {
private val metrics private val metrics
get() = context?.components?.analytics?.metrics get() = context?.components?.analytics?.metrics
override val selectedItems get() = bookmarkStore.state.mode.selectedItems
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)
@ -173,8 +173,7 @@ class BookmarkFragment : Fragment(), BackHandler, AccountObserver {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.libraryClose -> { R.id.libraryClose -> {
navigation close()
.popBackStack(R.id.libraryFragment, true)
true true
} }
R.id.add_bookmark_folder -> { R.id.add_bookmark_folder -> {
@ -186,20 +185,21 @@ class BookmarkFragment : Fragment(), BackHandler, AccountObserver {
true true
} }
R.id.open_bookmarks_in_new_tabs_multi_select -> { R.id.open_bookmarks_in_new_tabs_multi_select -> {
getSelectedBookmarks().forEach { node -> openItemsInNewTab { node -> node.url }
node.url?.let {
context?.components?.useCases?.tabsUseCases?.addTab?.invoke(it)
}
}
(activity as HomeActivity).browsingModeManager.mode = BrowsingModeManager.Mode.Normal
(activity as HomeActivity).supportActionBar?.hide()
nav(R.id.bookmarkFragment, BookmarkFragmentDirections.actionBookmarkFragmentToHomeFragment()) nav(R.id.bookmarkFragment, BookmarkFragmentDirections.actionBookmarkFragmentToHomeFragment())
metrics?.track(Event.OpenedBookmarksInNewTabs) metrics?.track(Event.OpenedBookmarksInNewTabs)
true true
} }
R.id.open_bookmarks_in_private_tabs_multi_select -> {
openItemsInNewTab(private = true) { node -> node.url }
nav(R.id.bookmarkFragment, BookmarkFragmentDirections.actionBookmarkFragmentToHomeFragment())
metrics?.track(Event.OpenedBookmarksInPrivateTabs)
true
}
R.id.edit_bookmark_multi_select -> { R.id.edit_bookmark_multi_select -> {
val bookmark = getSelectedBookmarks().first() val bookmark = bookmarkStore.state.mode.selectedItems.first()
nav( nav(
R.id.bookmarkFragment, R.id.bookmarkFragment,
BookmarkFragmentDirections BookmarkFragmentDirections
@ -207,21 +207,8 @@ class BookmarkFragment : Fragment(), BackHandler, AccountObserver {
) )
true true
} }
R.id.open_bookmarks_in_private_tabs_multi_select -> {
getSelectedBookmarks().forEach { node ->
node.url?.let {
context?.components?.useCases?.tabsUseCases?.addPrivateTab?.invoke(it)
}
}
(activity as HomeActivity).browsingModeManager.mode = BrowsingModeManager.Mode.Private
(activity as HomeActivity).supportActionBar?.hide()
nav(R.id.bookmarkFragment, BookmarkFragmentDirections.actionBookmarkFragmentToHomeFragment())
metrics?.track(Event.OpenedBookmarksInPrivateTabs)
true
}
R.id.delete_bookmarks_multi_select -> { R.id.delete_bookmarks_multi_select -> {
deleteMulti(getSelectedBookmarks()) deleteMulti(bookmarkStore.state.mode.selectedItems)
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
@ -247,8 +234,6 @@ class BookmarkFragment : Fragment(), BackHandler, AccountObserver {
override fun onProfileUpdated(profile: Profile) { override fun onProfileUpdated(profile: Profile) {
} }
private fun getSelectedBookmarks() = bookmarkView.getSelected()
private suspend fun refreshBookmarks() { private suspend fun refreshBookmarks() {
context?.bookmarkStorage()?.getTree(bookmarkStore.state.tree!!.guid, false).withOptionalDesktopFolders(context) context?.bookmarkStorage()?.getTree(bookmarkStore.state.tree!!.guid, false).withOptionalDesktopFolders(context)
?.let { node -> ?.let { node ->
@ -265,7 +250,7 @@ class BookmarkFragment : Fragment(), BackHandler, AccountObserver {
super.onPause() super.onPause()
} }
private suspend fun deleteSelectedBookmarks(selected: Set<BookmarkNode> = getSelectedBookmarks()) { private suspend fun deleteSelectedBookmarks(selected: Set<BookmarkNode>) {
selected.forEach { selected.forEach {
context?.bookmarkStorage()?.deleteNode(it.guid) context?.bookmarkStorage()?.deleteNode(it.guid)
} }

View File

@ -22,8 +22,10 @@ class BookmarkStore(
*/ */
data class BookmarkState(val tree: BookmarkNode?, val mode: Mode = Mode.Normal) : State { data class BookmarkState(val tree: BookmarkNode?, val mode: Mode = Mode.Normal) : State {
sealed class Mode { sealed class Mode {
open val selectedItems = emptySet<BookmarkNode>()
object Normal : Mode() object Normal : Mode()
data class Selecting(val selectedItems: Set<BookmarkNode>) : Mode() data class Selecting(override val selectedItems: Set<BookmarkNode>) : Mode()
} }
} }
@ -46,32 +48,22 @@ sealed class BookmarkAction : Action {
fun bookmarkStateReducer(state: BookmarkState, action: BookmarkAction): BookmarkState { fun bookmarkStateReducer(state: BookmarkState, action: BookmarkAction): BookmarkState {
return when (action) { return when (action) {
is BookmarkAction.Change -> { is BookmarkAction.Change -> {
val mode = val items = state.mode.selectedItems.filter { it in action.tree }
if (state.mode is BookmarkState.Mode.Selecting) { state.copy(
val items = state.mode.selectedItems.filter { tree = action.tree,
it in action.tree mode = if (items.isEmpty()) BookmarkState.Mode.Normal else BookmarkState.Mode.Selecting(items.toSet())
}.toSet() )
if (items.isEmpty()) BookmarkState.Mode.Normal else BookmarkState.Mode.Selecting(items)
} else state.mode
state.copy(tree = action.tree, mode = mode)
}
is BookmarkAction.Select -> {
val selectedItems = if (state.mode is BookmarkState.Mode.Selecting) {
state.mode.selectedItems + action.item
} else setOf(action.item)
state.copy(mode = BookmarkState.Mode.Selecting(selectedItems))
} }
is BookmarkAction.Select ->
state.copy(mode = BookmarkState.Mode.Selecting(state.mode.selectedItems + action.item))
is BookmarkAction.Deselect -> { is BookmarkAction.Deselect -> {
val selectedItems = if (state.mode is BookmarkState.Mode.Selecting) { val items = state.mode.selectedItems - action.item
state.mode.selectedItems - action.item state.copy(
} else setOf() mode = if (items.isEmpty()) BookmarkState.Mode.Normal else BookmarkState.Mode.Selecting(items)
val mode = )
if (selectedItems.isEmpty()) BookmarkState.Mode.Normal else BookmarkState.Mode.Selecting(selectedItems)
state.copy(mode = mode)
} }
BookmarkAction.DeselectAll -> { BookmarkAction.DeselectAll ->
state.copy(mode = BookmarkState.Mode.Normal) state.copy(mode = BookmarkState.Mode.Normal)
}
} }
} }

View File

@ -5,16 +5,13 @@
package org.mozilla.fenix.library.bookmarks package org.mozilla.fenix.library.bookmarks
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.component_bookmark.view.* import kotlinx.android.synthetic.main.component_bookmark.view.*
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.support.base.feature.BackHandler import mozilla.components.support.base.feature.BackHandler
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getColorResFromAttr
import org.mozilla.fenix.library.LibraryPageView import org.mozilla.fenix.library.LibraryPageView
/** /**
@ -129,12 +126,13 @@ interface BookmarkViewInteractor {
} }
class BookmarkView( class BookmarkView(
private val container: ViewGroup, container: ViewGroup,
val interactor: BookmarkViewInteractor val interactor: BookmarkViewInteractor
) : LibraryPageView(container), LayoutContainer, BackHandler { ) : LibraryPageView(container), BackHandler {
override val containerView: View? val view: LinearLayout = LayoutInflater.from(container.context)
get() = container .inflate(R.layout.component_bookmark, container, true)
.findViewById(R.id.bookmarks_wrapper)
var mode: BookmarkState.Mode = BookmarkState.Mode.Normal var mode: BookmarkState.Mode = BookmarkState.Mode.Normal
private set private set
@ -142,9 +140,6 @@ class BookmarkView(
private set private set
private var canGoBack = false private var canGoBack = false
val view: LinearLayout = LayoutInflater.from(container.context)
.inflate(R.layout.component_bookmark, container, true) as LinearLayout
private val bookmarkAdapter: BookmarkAdapter private val bookmarkAdapter: BookmarkAdapter
init { init {
@ -155,17 +150,19 @@ class BookmarkView(
} }
fun update(state: BookmarkState) { fun update(state: BookmarkState) {
canGoBack = !(listOf(null, BookmarkRoot.Root.id).contains(state.tree?.guid)) canGoBack = BookmarkRoot.Root.matches(state.tree)
if (state.tree != tree) { tree = state.tree
tree = state.tree
}
if (state.mode != mode) { if (state.mode != mode) {
mode = state.mode mode = state.mode
interactor.switchMode(mode) interactor.switchMode(mode)
} }
when (val modeCopy = state.mode) {
is BookmarkState.Mode.Normal -> setUIForNormalMode(state.tree) bookmarkAdapter.updateData(state.tree, mode)
is BookmarkState.Mode.Selecting -> setUIForSelectingMode(state.tree, modeCopy) when (state.mode) {
is BookmarkState.Mode.Normal ->
setUiForNormalMode(state.tree)
is BookmarkState.Mode.Selecting ->
setUiForSelectingMode(context.getString(R.string.bookmarks_multi_select_title, mode.selectedItems.size))
} }
} }
@ -183,34 +180,16 @@ class BookmarkView(
} }
} }
fun getSelected(): Set<BookmarkNode> = bookmarkAdapter.selected private fun setUiForNormalMode(root: BookmarkNode?) {
super.setUiForNormalMode(
private fun setUIForSelectingMode( if (BookmarkRoot.Mobile.matches(root)) context.getString(R.string.library_bookmarks) else root?.title
root: BookmarkNode?,
mode: BookmarkState.Mode.Selecting
) {
bookmarkAdapter.updateData(root, mode)
activity?.title =
context.getString(R.string.bookmarks_multi_select_title, mode.selectedItems.size)
setToolbarColors(
R.color.white_color,
context.getColorResFromAttr(R.attr.accentHighContrast)
) )
} }
private fun setUIForNormalMode(root: BookmarkNode?) { /**
bookmarkAdapter.updateData(root, BookmarkState.Mode.Normal) * Returns true if [root] matches the bookmark root ID.
setTitle(root) */
setToolbarColors( private fun BookmarkRoot.matches(root: BookmarkNode?): Boolean {
context.getColorResFromAttr(R.attr.primaryText), return root == null || id == root.guid
context.getColorResFromAttr(R.attr.foundation)
)
}
private fun setTitle(root: BookmarkNode?) {
activity?.title = when (root?.guid) {
BookmarkRoot.Mobile.id, null -> context.getString(R.string.library_bookmarks)
else -> root.title
}
} }
} }

View File

@ -17,10 +17,8 @@ import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.Navigation
import kotlinx.android.synthetic.main.fragment_history.view.* import kotlinx.android.synthetic.main.fragment_history.view.*
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -37,10 +35,11 @@ import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
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.library.LibraryPageFragment
import org.mozilla.fenix.share.ShareTab import org.mozilla.fenix.share.ShareTab
@SuppressWarnings("TooManyFunctions") @SuppressWarnings("TooManyFunctions")
class HistoryFragment : Fragment(), BackHandler { class HistoryFragment : LibraryPageFragment<HistoryItem>(), BackHandler {
private lateinit var historyStore: HistoryStore private lateinit var historyStore: HistoryStore
private lateinit var historyView: HistoryView private lateinit var historyView: HistoryView
private lateinit var historyInteractor: HistoryInteractor private lateinit var historyInteractor: HistoryInteractor
@ -71,6 +70,8 @@ class HistoryFragment : Fragment(), BackHandler {
return view return view
} }
override val selectedItems get() = historyStore.state.mode.selectedItems
private fun invalidateOptionsMenu() { private fun invalidateOptionsMenu() {
activity?.invalidateOptionsMenu() activity?.invalidateOptionsMenu()
} }
@ -91,7 +92,7 @@ class HistoryFragment : Fragment(), BackHandler {
setHasOptionsMenu(true) setHasOptionsMenu(true)
} }
fun deleteHistoryItems(items: Set<HistoryItem>) { private fun deleteHistoryItems(items: Set<HistoryItem>) {
lifecycleScope.launch { lifecycleScope.launch {
val storage = context?.components?.core?.historyStorage val storage = context?.components?.core?.historyStorage
for (item in items) { for (item in items) {
@ -126,10 +127,8 @@ class HistoryFragment : Fragment(), BackHandler {
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
val mode = historyStore.state.mode val mode = historyStore.state.mode
when (mode) { when (mode) {
HistoryState.Mode.Normal -> HistoryState.Mode.Normal -> R.menu.library_menu
R.menu.library_menu is HistoryState.Mode.Editing -> R.menu.history_select_multi
is HistoryState.Mode.Editing ->
R.menu.history_select_multi
else -> null else -> null
}?.let { inflater.inflate(it, menu) } }?.let { inflater.inflate(it, menu) }
@ -146,11 +145,10 @@ class HistoryFragment : Fragment(), BackHandler {
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
R.id.share_history_multi_select -> { R.id.share_history_multi_select -> {
val selectedHistory = val selectedHistory = historyStore.state.mode.selectedItems
(historyStore.state.mode as? HistoryState.Mode.Editing)?.selectedItems ?: setOf()
when { when {
selectedHistory.size == 1 -> selectedHistory.size == 1 ->
share(selectedHistory.first().url) share(url = selectedHistory.first().url)
selectedHistory.size > 1 -> { selectedHistory.size > 1 -> {
val shareTabs = selectedHistory.map { ShareTab(it.url, it.title) } val shareTabs = selectedHistory.map { ShareTab(it.url, it.title) }
share(tabs = shareTabs) share(tabs = shareTabs)
@ -159,36 +157,25 @@ class HistoryFragment : Fragment(), BackHandler {
true true
} }
R.id.libraryClose -> { R.id.libraryClose -> {
Navigation.findNavController(requireActivity(), R.id.container) close()
.popBackStack(R.id.libraryFragment, true)
true true
} }
R.id.delete_history_multi_select -> { R.id.delete_history_multi_select -> {
val components = context?.applicationContext?.components!! val components = context?.components!!
val selectedHistory =
(historyStore.state.mode as? HistoryState.Mode.Editing)?.selectedItems ?: setOf()
lifecycleScope.launch(Main) { lifecycleScope.launch(Main) {
deleteSelectedHistory(selectedHistory, components) deleteSelectedHistory(historyStore.state.mode.selectedItems, components)
viewModel.invalidate() viewModel.invalidate()
historyStore.dispatch(HistoryAction.ExitDeletionMode) historyStore.dispatch(HistoryAction.ExitDeletionMode)
} }
true true
} }
R.id.open_history_in_new_tabs_multi_select -> { R.id.open_history_in_new_tabs_multi_select -> {
val selectedHistory = openItemsInNewTab { selectedItem ->
(historyStore.state.mode as? HistoryState.Mode.Editing)?.selectedItems ?: setOf() requireComponents.analytics.metrics.track(Event.HistoryItemOpened)
requireComponents.useCases.tabsUseCases.addTab.let { useCase -> selectedItem.url
for (selectedItem in selectedHistory) {
requireComponents.analytics.metrics.track(Event.HistoryItemOpened)
useCase.invoke(selectedItem.url)
}
} }
(activity as HomeActivity).apply {
browsingModeManager.mode = BrowsingModeManager.Mode.Normal
supportActionBar?.hide()
}
nav( nav(
R.id.historyFragment, R.id.historyFragment,
HistoryFragmentDirections.actionHistoryFragmentToHomeFragment() HistoryFragmentDirections.actionHistoryFragmentToHomeFragment()
@ -196,13 +183,9 @@ class HistoryFragment : Fragment(), BackHandler {
true true
} }
R.id.open_history_in_private_tabs_multi_select -> { R.id.open_history_in_private_tabs_multi_select -> {
val selectedHistory = openItemsInNewTab(private = true) { selectedItem ->
(historyStore.state.mode as? HistoryState.Mode.Editing)?.selectedItems ?: setOf() requireComponents.analytics.metrics.track(Event.HistoryItemOpened)
requireComponents.useCases.tabsUseCases.addPrivateTab.let { useCase -> selectedItem.url
for (selectedItem in selectedHistory) {
requireComponents.analytics.metrics.track(Event.HistoryItemOpened)
useCase.invoke(selectedItem.url)
}
} }
(activity as HomeActivity).apply { (activity as HomeActivity).apply {
@ -220,7 +203,7 @@ class HistoryFragment : Fragment(), BackHandler {
override fun onBackPressed(): Boolean = historyView.onBackPressed() override fun onBackPressed(): Boolean = historyView.onBackPressed()
fun openItem(item: HistoryItem) { private fun openItem(item: HistoryItem) {
requireComponents.analytics.metrics.track(Event.HistoryItemOpened) requireComponents.analytics.metrics.track(Event.HistoryItemOpened)
(activity as HomeActivity).openToBrowserAndLoad( (activity as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = item.url, searchTermOrURL = item.url,

View File

@ -16,8 +16,7 @@ class HistoryInteractor(
private val deleteHistoryItems: (Set<HistoryItem>) -> Unit private val deleteHistoryItems: (Set<HistoryItem>) -> Unit
) : HistoryViewInteractor { ) : HistoryViewInteractor {
override fun onItemPress(item: HistoryItem) { override fun onItemPress(item: HistoryItem) {
val mode = store.state.mode when (val mode = store.state.mode) {
when (mode) {
is HistoryState.Mode.Normal -> openToBrowser(item) is HistoryState.Mode.Normal -> openToBrowser(item)
is HistoryState.Mode.Editing -> { is HistoryState.Mode.Editing -> {
val isSelected = mode.selectedItems.contains(item) val isSelected = mode.selectedItems.contains(item)
@ -32,9 +31,7 @@ class HistoryInteractor(
} }
override fun onItemLongPress(item: HistoryItem) { override fun onItemLongPress(item: HistoryItem) {
val isSelected = (store.state.mode as? HistoryState.Mode.Editing)?.let { val isSelected = store.state.mode.selectedItems.contains(item)
it.selectedItems.contains(item)
} ?: false
if (isSelected) { if (isSelected) {
store.dispatch(HistoryAction.RemoveItemForRemoval(item)) store.dispatch(HistoryAction.RemoveItemForRemoval(item))

View File

@ -41,9 +41,11 @@ sealed class HistoryAction : Action {
*/ */
data class HistoryState(val items: List<HistoryItem>, val mode: Mode) : State { data class HistoryState(val items: List<HistoryItem>, val mode: Mode) : State {
sealed class Mode { sealed class Mode {
open val selectedItems = emptySet<HistoryItem>()
object Normal : Mode() object Normal : Mode()
data class Editing(val selectedItems: Set<HistoryItem>) : Mode()
object Deleting : Mode() object Deleting : Mode()
data class Editing(override val selectedItems: Set<HistoryItem>) : Mode()
} }
} }
@ -52,24 +54,13 @@ data class HistoryState(val items: List<HistoryItem>, val mode: Mode) : State {
*/ */
fun historyStateReducer(state: HistoryState, action: HistoryAction): HistoryState { fun historyStateReducer(state: HistoryState, action: HistoryAction): HistoryState {
return when (action) { return when (action) {
is HistoryAction.AddItemForRemoval -> { is HistoryAction.AddItemForRemoval ->
val mode = state.mode state.copy(mode = HistoryState.Mode.Editing(state.mode.selectedItems + action.item))
if (mode is HistoryState.Mode.Editing) {
val items = mode.selectedItems + setOf(action.item)
state.copy(mode = HistoryState.Mode.Editing(items))
} else {
state.copy(mode = HistoryState.Mode.Editing(setOf(action.item)))
}
}
is HistoryAction.RemoveItemForRemoval -> { is HistoryAction.RemoveItemForRemoval -> {
var mode = state.mode val selected = state.mode.selectedItems - action.item
state.copy(
if (mode is HistoryState.Mode.Editing) { mode = if (selected.isEmpty()) HistoryState.Mode.Normal else HistoryState.Mode.Editing(selected)
val items = mode.selectedItems.minus(action.item) )
state.copy(mode = HistoryState.Mode.Editing(items))
} else {
state
}
} }
is HistoryAction.ExitEditMode -> state.copy(mode = HistoryState.Mode.Normal) is HistoryAction.ExitEditMode -> state.copy(mode = HistoryState.Mode.Normal)
is HistoryAction.EnterDeletionMode -> state.copy(mode = HistoryState.Mode.Deleting) is HistoryAction.EnterDeletionMode -> state.copy(mode = HistoryState.Mode.Deleting)

View File

@ -4,27 +4,17 @@
package org.mozilla.fenix.library.history package org.mozilla.fenix.library.history
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageButton
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator import androidx.recyclerview.widget.SimpleItemAnimator
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.component_history.* import kotlinx.android.synthetic.main.component_history.*
import kotlinx.android.synthetic.main.component_history.view.* import kotlinx.android.synthetic.main.component_history.view.*
import kotlinx.android.synthetic.main.component_history.view.history_list
import mozilla.components.support.base.feature.BackHandler import mozilla.components.support.base.feature.BackHandler
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.asActivity import org.mozilla.fenix.library.LibraryPageView
import org.mozilla.fenix.ext.getColorResFromAttr
/** /**
* Interface for the HistoryViewInteractor. This interface is implemented by objects that want * Interface for the HistoryViewInteractor. This interface is implemented by objects that want
@ -73,27 +63,22 @@ interface HistoryViewInteractor {
* View that contains and configures the History List * View that contains and configures the History List
*/ */
class HistoryView( class HistoryView(
private val container: ViewGroup, container: ViewGroup,
val interactor: HistoryInteractor val interactor: HistoryInteractor
) : LayoutContainer, BackHandler { ) : LibraryPageView(container), BackHandler {
val view: ConstraintLayout = LayoutInflater.from(container.context) val view: ConstraintLayout = LayoutInflater.from(container.context)
.inflate(R.layout.component_history, container, true) .inflate(R.layout.component_history, container, true)
.findViewById(R.id.history_wrapper) .findViewById(R.id.history_wrapper)
override val containerView: View?
get() = container
val historyAdapter: HistoryAdapter
private var items: List<HistoryItem> = listOf() private var items: List<HistoryItem> = listOf()
private val context = container.context
var mode: HistoryState.Mode = HistoryState.Mode.Normal var mode: HistoryState.Mode = HistoryState.Mode.Normal
private set private set
private val activity = context?.asActivity()
private val layoutManager = LinearLayoutManager(container.context)
init {
historyAdapter = HistoryAdapter(interactor)
val historyAdapter = HistoryAdapter(interactor)
private val layoutManager = LinearLayoutManager(container.context)
init {
view.history_list.apply { view.history_list.apply {
layoutManager = this@HistoryView.layoutManager layoutManager = this@HistoryView.layoutManager
adapter = historyAdapter adapter = historyAdapter
@ -102,37 +87,36 @@ class HistoryView(
} }
fun update(state: HistoryState) { fun update(state: HistoryState) {
view.progress_bar.visibility = val oldMode = mode
if (state.mode is HistoryState.Mode.Deleting) View.VISIBLE else View.GONE
if (state.mode != mode) { view.progress_bar.isVisible = state.mode === HistoryState.Mode.Deleting
items = state.items
mode = state.mode
if (state.mode != oldMode) {
interactor.onModeSwitched() interactor.onModeSwitched()
historyAdapter.updateMode(state.mode) historyAdapter.updateMode(state.mode)
val oldMode = mode // Deselect all the previously selected items
if (oldMode is HistoryState.Mode.Editing) { oldMode.selectedItems.forEach {
oldMode.selectedItems.forEach { historyAdapter.notifyItemChanged(it.id)
historyAdapter.notifyItemChanged(it.id)
}
} }
} }
(state.mode as? HistoryState.Mode.Editing)?.also { if (state.mode is HistoryState.Mode.Editing) {
val oldMode = (mode as? HistoryState.Mode.Editing) val unselectedItems = oldMode.selectedItems - state.mode.selectedItems
val unselectedItems = oldMode?.selectedItems?.minus(it.selectedItems) ?: setOf()
it.selectedItems.union(unselectedItems).forEach { item -> state.mode.selectedItems.union(unselectedItems).forEach { item ->
historyAdapter.notifyItemChanged(item.id) historyAdapter.notifyItemChanged(item.id)
} }
} }
items = state.items
when (val mode = state.mode) { when (val mode = state.mode) {
is HistoryState.Mode.Normal -> setUIForNormalMode() is HistoryState.Mode.Normal ->
is HistoryState.Mode.Editing -> setUIForSelectingMode(mode.selectedItems.size) setUiForNormalMode(context.getString(R.string.library_history))
is HistoryState.Mode.Editing ->
setUiForSelectingMode(context.getString(R.string.history_multi_select_title, mode.selectedItems.size))
} }
mode = state.mode
} }
fun updateEmptyState(userHasHistory: Boolean) { fun updateEmptyState(userHasHistory: Boolean) {
@ -140,59 +124,6 @@ class HistoryView(
history_empty_view.isVisible = !userHasHistory history_empty_view.isVisible = !userHasHistory
} }
private fun setUIForSelectingMode(selectedItemSize: Int) {
activity?.title =
context.getString(R.string.history_multi_select_title, selectedItemSize)
setToolbarColors(
R.color.white_color,
context!!.getColorResFromAttr(R.attr.accentHighContrast)
)
}
private fun setUIForNormalMode() {
activity?.title = context.getString(R.string.library_history)
setToolbarColors(
context!!.getColorResFromAttr(R.attr.primaryText),
context.getColorResFromAttr(R.attr.foundation)
)
}
private fun setToolbarColors(foreground: Int, background: Int) {
val toolbar = (activity as AppCompatActivity).findViewById<Toolbar>(R.id.navigationToolbar)
val colorFilter = PorterDuffColorFilter(
ContextCompat.getColor(context, foreground),
PorterDuff.Mode.SRC_IN
)
toolbar.setBackgroundColor(ContextCompat.getColor(context, background))
toolbar.setTitleTextColor(ContextCompat.getColor(context, foreground))
themeToolbar(
toolbar, foreground,
background, colorFilter
)
}
private fun themeToolbar(
toolbar: Toolbar,
textColor: Int,
backgroundColor: Int,
colorFilter: PorterDuffColorFilter? = null
) {
toolbar.setTitleTextColor(ContextCompat.getColor(context!!, textColor))
toolbar.setBackgroundColor(ContextCompat.getColor(context, backgroundColor))
if (colorFilter == null) {
return
}
toolbar.overflowIcon?.colorFilter = colorFilter
(0 until toolbar.childCount).forEach {
when (val item = toolbar.getChildAt(it)) {
is ImageButton -> item.drawable.colorFilter = colorFilter
}
}
}
override fun onBackPressed(): Boolean { override fun onBackPressed(): Boolean {
return interactor.onBackPressed() return interactor.onBackPressed()
} }

View File

@ -6,6 +6,7 @@
xmlns:android="http://schemas.android.com/apk/res/android" 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/bookmarks_wrapper"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -17,7 +18,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/bookmark_row" /> tools:listitem="@layout/library_site_item" />
<TextView <TextView
android:id="@+id/bookmarks_empty_view" android:id="@+id/bookmarks_empty_view"