1
0
Fork 0

For #13140: Use concept-menu for saved logins menu (#13143)

master
Tiger Oakes 2020-08-02 18:48:10 -07:00 committed by GitHub
parent ddfb3dfa72
commit f3f470a977
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 167 additions and 92 deletions

View File

@ -410,6 +410,7 @@ dependencies {
implementation Deps.mozilla_browser_domains implementation Deps.mozilla_browser_domains
implementation Deps.mozilla_browser_icons implementation Deps.mozilla_browser_icons
implementation Deps.mozilla_browser_menu implementation Deps.mozilla_browser_menu
implementation Deps.mozilla_browser_menu2
implementation Deps.mozilla_browser_search implementation Deps.mozilla_browser_search
implementation Deps.mozilla_browser_session implementation Deps.mozilla_browser_session
implementation Deps.mozilla_browser_state implementation Deps.mozilla_browser_state

View File

@ -5,51 +5,68 @@
package org.mozilla.fenix.settings.logins package org.mozilla.fenix.settings.logins
import android.content.Context import android.content.Context
import mozilla.components.browser.menu.BrowserMenuBuilder import androidx.annotation.VisibleForTesting
import mozilla.components.browser.menu.item.SimpleBrowserMenuHighlightableItem import mozilla.components.browser.menu2.BrowserMenuController
import mozilla.components.concept.menu.candidate.HighPriorityHighlightEffect
import mozilla.components.concept.menu.candidate.TextMenuCandidate
import mozilla.components.concept.menu.candidate.TextStyle
import mozilla.components.support.ktx.android.content.getColorFromAttr import mozilla.components.support.ktx.android.content.getColorFromAttr
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.ext.components
import org.mozilla.fenix.settings.logins.interactor.SavedLoginsInteractor
class SavedLoginsSortingStrategyMenu( class SavedLoginsSortingStrategyMenu(
private val context: Context, private val context: Context,
private val itemToHighlight: Item, private val savedLoginsInteractor: SavedLoginsInteractor
private val onItemTapped: (Item) -> Unit = {}
) { ) {
sealed class Item { enum class Item(val strategyString: String) {
object AlphabeticallySort : Item() AlphabeticallySort("ALPHABETICALLY"),
object LastUsedSort : Item() LastUsedSort("LAST_USED");
companion object {
fun fromString(strategyString: String) = when (strategyString) {
AlphabeticallySort.strategyString -> AlphabeticallySort
LastUsedSort.strategyString -> LastUsedSort
else -> AlphabeticallySort
}
}
} }
val menuBuilder by lazy { BrowserMenuBuilder(menuItems) } val menuController by lazy { BrowserMenuController() }
private val menuItems by lazy { @VisibleForTesting
listOfNotNull( internal fun menuItems(itemToHighlight: Item): List<TextMenuCandidate> {
SimpleBrowserMenuHighlightableItem( val textStyle = TextStyle(
label = context.getString(R.string.saved_logins_sort_strategy_alphabetically), color = context.getColorFromAttr(R.attr.primaryText)
textColorResource = ThemeManager.resolveAttribute(R.attr.primaryText, context), )
itemType = Item.AlphabeticallySort,
backgroundTint = context.getColorFromAttr(R.attr.colorControlHighlight), val highlight = HighPriorityHighlightEffect(
isHighlighted = { itemToHighlight == Item.AlphabeticallySort } backgroundTint = context.getColorFromAttr(R.attr.colorControlHighlight)
)
return listOf(
TextMenuCandidate(
text = context.getString(R.string.saved_logins_sort_strategy_alphabetically),
textStyle = textStyle,
effect = if (itemToHighlight == Item.AlphabeticallySort) highlight else null
) { ) {
onItemTapped.invoke(Item.AlphabeticallySort) savedLoginsInteractor.onSortingStrategyChanged(
SortingStrategy.Alphabetically(context.components.publicSuffixList)
)
}, },
TextMenuCandidate(
SimpleBrowserMenuHighlightableItem( text = context.getString(R.string.saved_logins_sort_strategy_last_used),
label = context.getString(R.string.saved_logins_sort_strategy_last_used), textStyle = textStyle,
textColorResource = ThemeManager.resolveAttribute(R.attr.primaryText, context), effect = if (itemToHighlight == Item.LastUsedSort) highlight else null
itemType = Item.LastUsedSort,
backgroundTint = context.getColorFromAttr(R.attr.colorControlHighlight),
isHighlighted = { itemToHighlight == Item.LastUsedSort }
) { ) {
onItemTapped.invoke(Item.LastUsedSort) savedLoginsInteractor.onSortingStrategyChanged(
SortingStrategy.LastUsed
)
} }
) )
} }
internal fun updateMenu(itemToHighlight: Item) { fun updateMenu(itemToHighlight: Item) {
menuItems.forEach { menuController.submitList(menuItems(itemToHighlight))
it.isHighlighted = { itemToHighlight == it.itemType }
}
} }
} }

View File

@ -22,8 +22,8 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import kotlinx.android.synthetic.main.fragment_saved_logins.view.* import kotlinx.android.synthetic.main.fragment_saved_logins.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi import mozilla.components.concept.menu.MenuController
import mozilla.components.browser.menu.BrowserMenu import mozilla.components.concept.menu.Orientation
import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.lib.state.ext.consumeFrom
import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
@ -31,7 +31,6 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.redirectToReAuth import org.mozilla.fenix.ext.redirectToReAuth
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.settings.logins.LoginsAction import org.mozilla.fenix.settings.logins.LoginsAction
@ -51,7 +50,6 @@ class SavedLoginsFragment : Fragment() {
private lateinit var savedLoginsInteractor: SavedLoginsInteractor private lateinit var savedLoginsInteractor: SavedLoginsInteractor
private lateinit var dropDownMenuAnchorView: View private lateinit var dropDownMenuAnchorView: View
private lateinit var sortingStrategyMenu: SavedLoginsSortingStrategyMenu private lateinit var sortingStrategyMenu: SavedLoginsSortingStrategyMenu
private lateinit var sortingStrategyPopupMenu: BrowserMenu
private lateinit var toolbarChildContainer: FrameLayout private lateinit var toolbarChildContainer: FrameLayout
private lateinit var sortLoginsMenuRoot: ConstraintLayout private lateinit var sortLoginsMenuRoot: ConstraintLayout
private lateinit var loginsListController: LoginsListController private lateinit var loginsListController: LoginsListController
@ -121,10 +119,8 @@ class SavedLoginsFragment : Fragment() {
return view return view
} }
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
consumeFrom(savedLoginsStore) { consumeFrom(savedLoginsStore) {
sortingStrategyMenu.updateMenu(savedLoginsStore.state.highlightedItem) sortingStrategyMenu.updateMenu(savedLoginsStore.state.highlightedItem)
savedLoginsListView.update(it) savedLoginsListView.update(it)
@ -161,7 +157,7 @@ class SavedLoginsFragment : Fragment() {
toolbarChildContainer.removeAllViews() toolbarChildContainer.removeAllViews()
toolbarChildContainer.visibility = View.GONE toolbarChildContainer.visibility = View.GONE
(activity as HomeActivity).getSupportActionBarAndInflateIfNecessary().setDisplayShowTitleEnabled(true) (activity as HomeActivity).getSupportActionBarAndInflateIfNecessary().setDisplayShowTitleEnabled(true)
sortingStrategyPopupMenu.dismiss() sortingStrategyMenu.menuController.dismiss()
redirectToReAuth(listOf(R.id.loginDetailFragment), findNavController().currentDestination?.id) redirectToReAuth(listOf(R.id.loginDetailFragment), findNavController().currentDestination?.id)
super.onPause() super.onPause()
@ -206,47 +202,27 @@ class SavedLoginsFragment : Fragment() {
} }
private fun attachMenu() { private fun attachMenu() {
sortingStrategyPopupMenu = sortingStrategyMenu.menuBuilder.build(requireContext()) sortingStrategyMenu.menuController.register(object : MenuController.Observer {
override fun onDismiss() {
sortLoginsMenuRoot.setOnClickListener { // Deactivate button on dismiss
sortLoginsMenuRoot.isActivated = true
sortingStrategyPopupMenu.show(
anchor = dropDownMenuAnchorView,
orientation = BrowserMenu.Orientation.DOWN
) {
sortLoginsMenuRoot.isActivated = false sortLoginsMenuRoot.isActivated = false
} }
}, view = sortLoginsMenuRoot)
sortLoginsMenuRoot.setOnClickListener {
// Activate button on show
sortLoginsMenuRoot.isActivated = true
sortingStrategyMenu.menuController.show(
anchor = dropDownMenuAnchorView,
orientation = Orientation.DOWN
)
} }
} }
private fun setupMenu(itemToHighlight: SavedLoginsSortingStrategyMenu.Item) { private fun setupMenu(itemToHighlight: SavedLoginsSortingStrategyMenu.Item) {
sortingStrategyMenu = sortingStrategyMenu = SavedLoginsSortingStrategyMenu(requireContext(), savedLoginsInteractor)
SavedLoginsSortingStrategyMenu( sortingStrategyMenu.updateMenu(itemToHighlight)
requireContext(),
itemToHighlight
) {
when (it) {
SavedLoginsSortingStrategyMenu.Item.AlphabeticallySort -> {
savedLoginsInteractor.onSortingStrategyChanged(
SortingStrategy.Alphabetically(
requireComponents.publicSuffixList
)
)
}
SavedLoginsSortingStrategyMenu.Item.LastUsedSort -> {
savedLoginsInteractor.onSortingStrategyChanged(
SortingStrategy.LastUsed
)
}
}
}
attachMenu() attachMenu()
} }
companion object {
const val SORTING_STRATEGY_ALPHABETICALLY = "ALPHABETICALLY"
const val SORTING_STRATEGY_LAST_USED = "LAST_USED"
}
} }

View File

@ -36,7 +36,6 @@ import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType
import org.mozilla.fenix.settings.logins.SavedLoginsSortingStrategyMenu import org.mozilla.fenix.settings.logins.SavedLoginsSortingStrategyMenu
import org.mozilla.fenix.settings.logins.SortingStrategy import org.mozilla.fenix.settings.logins.SortingStrategy
import org.mozilla.fenix.settings.logins.fragment.SavedLoginsFragment
import org.mozilla.fenix.settings.registerOnSharedPreferenceChangeListener import org.mozilla.fenix.settings.registerOnSharedPreferenceChangeListener
import java.security.InvalidParameterException import java.security.InvalidParameterException
@ -820,36 +819,26 @@ class Settings(private val appContext: Context) : PreferencesHolder {
private var savedLoginsSortingStrategyString by stringPreference( private var savedLoginsSortingStrategyString by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_saved_logins_sorting_strategy), appContext.getPreferenceKey(R.string.pref_key_saved_logins_sorting_strategy),
default = SavedLoginsFragment.SORTING_STRATEGY_ALPHABETICALLY default = SavedLoginsSortingStrategyMenu.Item.AlphabeticallySort.strategyString
) )
val savedLoginsMenuHighlightedItem: SavedLoginsSortingStrategyMenu.Item val savedLoginsMenuHighlightedItem: SavedLoginsSortingStrategyMenu.Item
get() { get() = SavedLoginsSortingStrategyMenu.Item.fromString(savedLoginsSortingStrategyString)
return when (savedLoginsSortingStrategyString) {
SavedLoginsFragment.SORTING_STRATEGY_ALPHABETICALLY -> {
SavedLoginsSortingStrategyMenu.Item.AlphabeticallySort
}
SavedLoginsFragment.SORTING_STRATEGY_LAST_USED -> {
SavedLoginsSortingStrategyMenu.Item.LastUsedSort
}
else -> SavedLoginsSortingStrategyMenu.Item.AlphabeticallySort
}
}
var savedLoginsSortingStrategy: SortingStrategy var savedLoginsSortingStrategy: SortingStrategy
get() { get() {
return when (savedLoginsSortingStrategyString) { return when (savedLoginsMenuHighlightedItem) {
SavedLoginsFragment.SORTING_STRATEGY_ALPHABETICALLY -> SortingStrategy.Alphabetically( SavedLoginsSortingStrategyMenu.Item.AlphabeticallySort ->
appContext.components.publicSuffixList SortingStrategy.Alphabetically(appContext.components.publicSuffixList)
) SavedLoginsSortingStrategyMenu.Item.LastUsedSort -> SortingStrategy.LastUsed
SavedLoginsFragment.SORTING_STRATEGY_LAST_USED -> SortingStrategy.LastUsed
else -> SortingStrategy.Alphabetically(appContext.components.publicSuffixList)
} }
} }
set(value) { set(value) {
savedLoginsSortingStrategyString = when (value) { savedLoginsSortingStrategyString = when (value) {
is SortingStrategy.Alphabetically -> SavedLoginsFragment.SORTING_STRATEGY_ALPHABETICALLY is SortingStrategy.Alphabetically ->
is SortingStrategy.LastUsed -> SavedLoginsFragment.SORTING_STRATEGY_LAST_USED SavedLoginsSortingStrategyMenu.Item.AlphabeticallySort.strategyString
is SortingStrategy.LastUsed ->
SavedLoginsSortingStrategyMenu.Item.LastUsedSort.strategyString
} }
} }
} }

View File

@ -10,6 +10,7 @@
<dimen name="mozac_browser_menu_width_min" tools:ignore="UnusedResources">112dp</dimen> <dimen name="mozac_browser_menu_width_min" tools:ignore="UnusedResources">112dp</dimen>
<dimen name="mozac_browser_menu_width_max" tools:ignore="UnusedResources">314dp</dimen> <dimen name="mozac_browser_menu_width_max" tools:ignore="UnusedResources">314dp</dimen>
<dimen name="mozac_browser_menu_corner_radius">8dp</dimen> <dimen name="mozac_browser_menu_corner_radius">8dp</dimen>
<dimen name="mozac_browser_menu2_corner_radius">8dp</dimen>
<dimen name="toolbar_elevation">7dp</dimen> <dimen name="toolbar_elevation">7dp</dimen>
<dimen name="library_item_height">56dp</dimen> <dimen name="library_item_height">56dp</dimen>
<dimen name="library_item_icon_margin_horizontal">16dp</dimen> <dimen name="library_item_icon_margin_horizontal">16dp</dimen>

View File

@ -250,6 +250,9 @@
<style name="Mozac.Browser.Menu" parent="" tools:ignore="UnusedResources"> <style name="Mozac.Browser.Menu" parent="" tools:ignore="UnusedResources">
<item name="cardBackgroundColor">?above</item> <item name="cardBackgroundColor">?above</item>
</style> </style>
<style name="Mozac.Browser.Menu2" parent="" tools:ignore="UnusedResources">
<item name="cardBackgroundColor">?above</item>
</style>
<style name="PrivateTheme" parent="PrivateThemeBase" /> <style name="PrivateTheme" parent="PrivateThemeBase" />

View File

@ -0,0 +1,88 @@
/* 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.settings.logins
import android.content.Context
import androidx.appcompat.view.ContextThemeWrapper
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.verify
import mozilla.components.concept.menu.candidate.HighPriorityHighlightEffect
import mozilla.components.support.ktx.android.content.getColorFromAttr
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.logins.SavedLoginsSortingStrategyMenu.Item
import org.mozilla.fenix.settings.logins.interactor.SavedLoginsInteractor
@RunWith(FenixRobolectricTestRunner::class)
class SavedLoginsSortingStrategyMenuTest {
private lateinit var context: Context
private lateinit var interactor: SavedLoginsInteractor
private lateinit var menu: SavedLoginsSortingStrategyMenu
@Before
fun setup() {
context = ContextThemeWrapper(testContext, R.style.NormalTheme)
interactor = mockk()
menu = SavedLoginsSortingStrategyMenu(context, interactor)
}
@Test
fun `item enum can be deserialized from string`() {
assertEquals(Item.AlphabeticallySort, Item.fromString("ALPHABETICALLY"))
assertEquals(Item.LastUsedSort, Item.fromString("LAST_USED"))
assertEquals(Item.AlphabeticallySort, Item.fromString("OTHER"))
}
@Test
fun `effect is set on alphabetical sort candidate`() {
val (name, lastUsed) = menu.menuItems(Item.AlphabeticallySort)
assertEquals(
HighPriorityHighlightEffect(context.getColorFromAttr(R.attr.colorControlHighlight)),
name.effect
)
assertNull(lastUsed.effect)
}
@Test
fun `effect is set on last used sort candidate`() {
val (name, lastUsed) = menu.menuItems(Item.LastUsedSort)
assertNull(name.effect)
assertEquals(
HighPriorityHighlightEffect(context.getColorFromAttr(R.attr.colorControlHighlight)),
lastUsed.effect
)
}
@Test
fun `candidates call interactor on click`() {
val (name, lastUsed) = menu.menuItems(Item.AlphabeticallySort)
every { interactor.onSortingStrategyChanged(any()) } just Runs
name.onClick()
verify {
interactor.onSortingStrategyChanged(
SortingStrategy.Alphabetically(context.components.publicSuffixList)
)
}
lastUsed.onClick()
verify {
interactor.onSortingStrategyChanged(
SortingStrategy.LastUsed
)
}
}
}