Closes #8791: Use A-C tab counter and upgrades to A-C 69.0.20201203202830

Upgrades to A-C 69.0.20201203202830 and addresses breaking changes:
- Upgrades androidx workmanager to 2.4.0 in line with A-C.
- RecordingDevicesNotificationFeature was removed
- SearchUseCases accept parent session ID instead of session itself
This commit is contained in:
Elise Richards 2020-11-16 15:45:48 -06:00 committed by Christian Sadilek
parent 79d1c08402
commit 77f061c362
26 changed files with 197 additions and 603 deletions

View File

@ -487,6 +487,7 @@ dependencies {
implementation Deps.mozilla_ui_icons
implementation Deps.mozilla_lib_publicsuffixlist
implementation Deps.mozilla_ui_widgets
implementation Deps.mozilla_ui_tabcounter
implementation Deps.mozilla_lib_crash
implementation Deps.mozilla_lib_push_firebase

View File

@ -572,7 +572,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
useCase.invoke(request.query)
requireActivity().startActivity(openInFenixIntent)
} else {
useCase.invoke(request.query, parentSession = parentSession)
useCase.invoke(request.query, parentSessionId = parentSession?.id)
}
},
owner = this,

View File

@ -13,8 +13,8 @@ import android.widget.FrameLayout
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.view.doOnNextLayout
import androidx.core.view.updateLayoutParams
import kotlinx.android.synthetic.main.mozac_ui_tabcounter_layout.view.*
import kotlinx.android.synthetic.main.tab_preview.view.*
import kotlinx.android.synthetic.main.tabs_tray_tab_counter.view.*
import mozilla.components.browser.thumbnails.loader.ThumbnailLoader
import mozilla.components.concept.base.images.ImageLoadRequest
import org.mozilla.fenix.R

View File

@ -23,7 +23,6 @@ import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.session.engine.EngineMiddleware
import mozilla.components.browser.session.storage.SessionStorage
import mozilla.components.browser.session.undo.UndoMiddleware
import mozilla.components.browser.state.action.RecentlyClosedAction
import mozilla.components.browser.state.action.RestoreCompleteAction
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.store.BrowserStore
@ -40,8 +39,8 @@ import mozilla.components.concept.fetch.Client
import mozilla.components.feature.customtabs.store.CustomTabsServiceStore
import mozilla.components.feature.downloads.DownloadMiddleware
import mozilla.components.feature.logins.exceptions.LoginExceptionStorage
import mozilla.components.feature.media.RecordingDevicesNotificationFeature
import mozilla.components.feature.media.middleware.MediaMiddleware
import mozilla.components.feature.media.middleware.RecordingDevicesMiddleware
import mozilla.components.feature.pwa.ManifestStorage
import mozilla.components.feature.pwa.WebAppShortcutManager
import mozilla.components.feature.readerview.ReaderViewMiddleware
@ -198,11 +197,10 @@ class Core(
context,
additionalBundledSearchEngineIds = listOf("reddit", "youtube"),
migration = SearchMigration(context)
)
),
RecordingDevicesMiddleware(context)
) + EngineMiddleware.create(engine, ::findSessionById)
).also {
it.dispatch(RecentlyClosedAction.InitializeRecentlyClosedState)
}
)
}
private fun lookupSessionManager(): SessionManager {
@ -242,10 +240,6 @@ class Core(
// Install the "cookies" WebExtension and tracks user interaction with SERPs.
searchTelemetry.install(engine, store)
// Show an ongoing notification when recording devices (camera, microphone) are used by web content
RecordingDevicesNotificationFeature(context, sessionManager)
.enable()
// Restore the previous state.
GlobalScope.launch(Dispatchers.Main) {
withContext(Dispatchers.IO) {

View File

@ -4,6 +4,8 @@
package org.mozilla.fenix.components.toolbar
import mozilla.components.ui.tabcounter.TabCounterMenu
open class BrowserInteractor(
private val browserToolbarController: BrowserToolbarController,
private val menuController: BrowserToolbarMenuController

View File

@ -9,10 +9,12 @@ import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.EngineView
import mozilla.components.support.ktx.kotlin.isUrl
import mozilla.components.ui.tabcounter.TabCounterMenu
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserAnimator.Companion.getToolbarNavOptions
import org.mozilla.fenix.browser.BrowserFragmentDirections
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.readermode.ReaderModeController
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
@ -104,6 +106,9 @@ class DefaultBrowserToolbarController(
override fun handleTabCounterItemInteraction(item: TabCounterMenu.Item) {
when (item) {
is TabCounterMenu.Item.CloseTab -> {
metrics.track(
Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.CLOSE_TAB)
)
sessionManager.selectedSession?.let {
// When closing the last tab we must show the undo snackbar in the home fragment
if (sessionManager.sessionsOfType(it.private).count() == 1) {
@ -120,8 +125,24 @@ class DefaultBrowserToolbarController(
}
}
is TabCounterMenu.Item.NewTab -> {
activity.browsingModeManager.mode = item.mode
navController.navigate(BrowserFragmentDirections.actionGlobalHome(focusOnAddressBar = true))
metrics.track(
Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_TAB)
)
activity.browsingModeManager.mode = BrowsingMode.Normal
navController.navigate(
BrowserFragmentDirections.actionGlobalHome(focusOnAddressBar = true)
)
}
is TabCounterMenu.Item.NewPrivateTab -> {
metrics.track(
Event.TabCounterMenuItemTapped(
Event.TabCounterMenuItemTapped.Item.NEW_PRIVATE_TAB
)
)
activity.browsingModeManager.mode = BrowsingMode.Private
navController.navigate(
BrowserFragmentDirections.actionGlobalHome(focusOnAddressBar = true)
)
}
}
}

View File

@ -29,6 +29,7 @@ import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.browser.toolbar.behavior.BrowserToolbarBottomBehavior
import mozilla.components.browser.toolbar.display.DisplayToolbar
import mozilla.components.support.utils.URLStringUtils
import mozilla.components.ui.tabcounter.TabCounterMenu
import org.mozilla.fenix.R
import org.mozilla.fenix.customtabs.CustomTabToolbarIntegration
import org.mozilla.fenix.customtabs.CustomTabToolbarMenu
@ -267,10 +268,6 @@ class BrowserToolbarView(
}
}
companion object {
private const val TOOLBAR_ELEVATION = 16
}
@Suppress("ComplexCondition")
private fun ToolbarMenu.Item.performHapticIfNeeded(view: View) {
if (this is ToolbarMenu.Item.Reload && this.bypassCache ||

View File

@ -0,0 +1,60 @@
/* 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.components.toolbar
import android.content.Context
import androidx.annotation.VisibleForTesting
import mozilla.components.concept.menu.candidate.DividerMenuCandidate
import mozilla.components.concept.menu.candidate.MenuCandidate
import mozilla.components.ui.tabcounter.TabCounterMenu
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
class FenixTabCounterMenu(
context: Context,
onItemTapped: (Item) -> Unit,
iconColor: Int? = null
) : TabCounterMenu(context, onItemTapped, iconColor) {
@VisibleForTesting
internal fun menuItems(showOnly: BrowsingMode): List<MenuCandidate> {
return when (showOnly) {
BrowsingMode.Normal -> listOf(newTabItem)
BrowsingMode.Private -> listOf(newPrivateTabItem)
}
}
@VisibleForTesting
internal fun menuItems(toolbarPosition: ToolbarPosition): List<MenuCandidate> {
val items = listOf(
newTabItem,
newPrivateTabItem,
DividerMenuCandidate(),
closeTabItem
)
return when (toolbarPosition) {
ToolbarPosition.BOTTOM -> items.reversed()
ToolbarPosition.TOP -> items
}
}
/**
* Update the displayed menu items.
* @param showOnly Show only the new tab item corresponding to the given [BrowsingMode].
*/
fun updateMenu(showOnly: BrowsingMode) {
val items = menuItems(showOnly)
menuController.submitList(items)
}
/**
* Update the displayed menu items.
* @param toolbarPosition Return a list that is ordered based on the given [ToolbarPosition].
*/
fun updateMenu(toolbarPosition: ToolbarPosition) {
menuController.submitList(menuItems(toolbarPosition))
}
}

View File

@ -1,271 +0,0 @@
/* 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.components.toolbar
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.content.Context
import android.graphics.Typeface
import android.util.AttributeSet
import android.util.TypedValue
import android.view.LayoutInflater
import android.widget.RelativeLayout
import androidx.core.view.updatePadding
import kotlinx.android.synthetic.main.mozac_ui_tabcounter_layout.view.*
import org.mozilla.fenix.R
import java.text.NumberFormat
class TabCounter @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : RelativeLayout(context, attrs, defStyle) {
private val animationSet: AnimatorSet
init {
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.mozac_ui_tabcounter_layout, this)
// This is needed because without this counter box will be empty.
setCount(INTERNAL_COUNT)
animationSet = createAnimatorSet()
}
private fun updateContentDescription(count: Int) {
counter_root.contentDescription = if (count == 1) {
context?.getString(R.string.open_tab_tray_single)
} else {
String.format(context.getString(R.string.open_tab_tray_plural), count.toString())
}
}
fun setCountWithAnimation(count: Int) {
setCount(count)
// No need to animate on these cases.
when {
INTERNAL_COUNT == 0 -> return // Initial state.
INTERNAL_COUNT == count -> return // There isn't any tab added or removed.
INTERNAL_COUNT > MAX_VISIBLE_TABS -> return // There are still over MAX_VISIBLE_TABS tabs open.
}
// Cancel previous animations if necessary.
if (animationSet.isRunning) {
animationSet.cancel()
}
// Trigger animations.
animationSet.start()
}
fun setCount(count: Int) {
updateContentDescription(count)
adjustTextSize(count)
counter_text.text = formatForDisplay(count)
INTERNAL_COUNT = count
}
private fun createAnimatorSet(): AnimatorSet {
val animatorSet = AnimatorSet()
createBoxAnimatorSet(animatorSet)
createTextAnimatorSet(animatorSet)
return animatorSet
}
private fun createBoxAnimatorSet(animatorSet: AnimatorSet) {
// The first animator, fadeout in 33 ms (49~51, 2 frames).
val fadeOut = ObjectAnimator.ofFloat(
counter_box, "alpha",
ANIM_BOX_FADEOUT_FROM, ANIM_BOX_FADEOUT_TO
).setDuration(ANIM_BOX_FADEOUT_DURATION)
// Move up on y-axis, from 0.0 to -5.3 in 50ms, with fadeOut (49~52, 3 frames).
val moveUp1 = ObjectAnimator.ofFloat(
counter_box, "translationY",
ANIM_BOX_MOVEUP1_TO, ANIM_BOX_MOVEUP1_FROM
).setDuration(ANIM_BOX_MOVEUP1_DURATION)
// Move down on y-axis, from -5.3 to -1.0 in 116ms, after moveUp1 (52~59, 7 frames).
val moveDown2 = ObjectAnimator.ofFloat(
counter_box, "translationY",
ANIM_BOX_MOVEDOWN2_FROM, ANIM_BOX_MOVEDOWN2_TO
).setDuration(ANIM_BOX_MOVEDOWN2_DURATION)
// FadeIn in 66ms, with moveDown2 (52~56, 4 frames).
val fadeIn = ObjectAnimator.ofFloat(
counter_box, "alpha",
ANIM_BOX_FADEIN_FROM, ANIM_BOX_FADEIN_TO
).setDuration(ANIM_BOX_FADEIN_DURATION)
// Move down on y-axis, from -1.0 to 2.7 in 116ms, after moveDown2 (59~66, 7 frames).
val moveDown3 = ObjectAnimator.ofFloat(
counter_box, "translationY",
ANIM_BOX_MOVEDOWN3_FROM, ANIM_BOX_MOVEDOWN3_TO
).setDuration(ANIM_BOX_MOVEDOWN3_DURATION)
// Move up on y-axis, from 2.7 to 0 in 133ms, after moveDown3 (66~74, 8 frames).
val moveUp4 = ObjectAnimator.ofFloat(
counter_box, "translationY",
ANIM_BOX_MOVEDOWN4_FROM, ANIM_BOX_MOVEDOWN4_TO
).setDuration(ANIM_BOX_MOVEDOWN4_DURATION)
// Scale up height from 2% to 105% in 100ms, after moveUp1 and delay 16ms (53~59, 6 frames).
val scaleUp1 = ObjectAnimator.ofFloat(
counter_box, "scaleY",
ANIM_BOX_SCALEUP1_FROM, ANIM_BOX_SCALEUP1_TO
).setDuration(ANIM_BOX_SCALEUP1_DURATION)
scaleUp1.startDelay = ANIM_BOX_SCALEUP1_DELAY // delay 1 frame after moveUp1
// Scale down height from 105% to 99% in 116ms, after scaleUp1 (59~66, 7 frames).
val scaleDown2 = ObjectAnimator.ofFloat(
counter_box, "scaleY",
ANIM_BOX_SCALEDOWN2_FROM, ANIM_BOX_SCALEDOWN2_TO
).setDuration(ANIM_BOX_SCALEDOWN2_DURATION)
// Scale up height from 99% to 100% in 133ms, after scaleDown2 (66~74, 8 frames).
val scaleUp3 = ObjectAnimator.ofFloat(
counter_box, "scaleY",
ANIM_BOX_SCALEUP3_FROM, ANIM_BOX_SCALEUP3_TO
).setDuration(ANIM_BOX_SCALEUP3_DURATION)
animatorSet.play(fadeOut).with(moveUp1)
animatorSet.play(moveUp1).before(moveDown2)
animatorSet.play(moveDown2).with(fadeIn)
animatorSet.play(moveDown2).before(moveDown3)
animatorSet.play(moveDown3).before(moveUp4)
animatorSet.play(moveUp1).before(scaleUp1)
animatorSet.play(scaleUp1).before(scaleDown2)
animatorSet.play(scaleDown2).before(scaleUp3)
}
private fun createTextAnimatorSet(animatorSet: AnimatorSet) {
val firstAnimator = animatorSet.childAnimations[0]
// Fadeout in 100ms, with firstAnimator (49~51, 2 frames).
val fadeOut = ObjectAnimator.ofFloat(
counter_text, "alpha",
ANIM_TEXT_FADEOUT_FROM, ANIM_TEXT_FADEOUT_TO
).setDuration(ANIM_TEXT_FADEOUT_DURATION)
// FadeIn in 66 ms, after fadeOut with delay 96ms (57~61, 4 frames).
val fadeIn = ObjectAnimator.ofFloat(
counter_text, "alpha",
ANIM_TEXT_FADEIN_FROM, ANIM_TEXT_FADEIN_TO
).setDuration(ANIM_TEXT_FADEIN_DURATION)
fadeIn.startDelay = (ANIM_TEXT_FADEIN_DELAY) // delay 6 frames after fadeOut
// Move down on y-axis, from 0 to 4.4 in 66ms, with fadeIn (57~61, 4 frames).
val moveDown = ObjectAnimator.ofFloat(
counter_text, "translationY",
ANIM_TEXT_MOVEDOWN_FROM, ANIM_TEXT_MOVEDOWN_TO
).setDuration(ANIM_TEXT_MOVEDOWN_DURATION)
moveDown.startDelay = (ANIM_TEXT_MOVEDOWN_DELAY) // delay 6 frames after fadeOut
// Move up on y-axis, from 0 to 4.4 in 66ms, after moveDown (61~69, 8 frames).
val moveUp = ObjectAnimator.ofFloat(
counter_text, "translationY",
ANIM_TEXT_MOVEUP_FROM, ANIM_TEXT_MOVEUP_TO
).setDuration(ANIM_TEXT_MOVEUP_DURATION)
animatorSet.play(firstAnimator).with(fadeOut)
animatorSet.play(fadeOut).before(fadeIn)
animatorSet.play(fadeIn).with(moveDown)
animatorSet.play(moveDown).before(moveUp)
}
private fun formatForDisplay(count: Int): String {
return if (count > MAX_VISIBLE_TABS) {
counter_text.updatePadding(bottom = INFINITE_CHAR_PADDING_BOTTOM)
SO_MANY_TABS_OPEN
} else NumberFormat.getInstance().format(count.toLong())
}
private fun adjustTextSize(newCount: Int) {
val newRatio = if (newCount in TWO_DIGITS_TAB_COUNT_THRESHOLD..MAX_VISIBLE_TABS) {
TWO_DIGITS_SIZE_RATIO
} else {
ONE_DIGIT_SIZE_RATIO
}
val counterBoxWidth =
context.resources.getDimensionPixelSize(R.dimen.tab_counter_box_width_height)
val textSize = newRatio * counterBoxWidth
counter_text.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
counter_text.setTypeface(null, Typeface.BOLD)
counter_text.setPadding(0, 0, 0, 0)
}
companion object {
internal var INTERNAL_COUNT = 0
internal const val MAX_VISIBLE_TABS = 99
internal const val SO_MANY_TABS_OPEN = ""
internal const val INFINITE_CHAR_PADDING_BOTTOM = 6
internal const val ONE_DIGIT_SIZE_RATIO = 0.5f
internal const val TWO_DIGITS_SIZE_RATIO = 0.4f
internal const val TWO_DIGITS_TAB_COUNT_THRESHOLD = 10
// createBoxAnimatorSet
private const val ANIM_BOX_FADEOUT_FROM = 1.0f
private const val ANIM_BOX_FADEOUT_TO = 0.0f
private const val ANIM_BOX_FADEOUT_DURATION = 33L
private const val ANIM_BOX_MOVEUP1_FROM = 0.0f
private const val ANIM_BOX_MOVEUP1_TO = -5.3f
private const val ANIM_BOX_MOVEUP1_DURATION = 50L
private const val ANIM_BOX_MOVEDOWN2_FROM = -5.3f
private const val ANIM_BOX_MOVEDOWN2_TO = -1.0f
private const val ANIM_BOX_MOVEDOWN2_DURATION = 167L
private const val ANIM_BOX_FADEIN_FROM = 0.01f
private const val ANIM_BOX_FADEIN_TO = 1.0f
private const val ANIM_BOX_FADEIN_DURATION = 66L
private const val ANIM_BOX_MOVEDOWN3_FROM = -1.0f
private const val ANIM_BOX_MOVEDOWN3_TO = 2.7f
private const val ANIM_BOX_MOVEDOWN3_DURATION = 116L
private const val ANIM_BOX_MOVEDOWN4_FROM = 2.7f
private const val ANIM_BOX_MOVEDOWN4_TO = 0.0f
private const val ANIM_BOX_MOVEDOWN4_DURATION = 133L
private const val ANIM_BOX_SCALEUP1_FROM = 0.02f
private const val ANIM_BOX_SCALEUP1_TO = 1.05f
private const val ANIM_BOX_SCALEUP1_DURATION = 100L
private const val ANIM_BOX_SCALEUP1_DELAY = 16L
private const val ANIM_BOX_SCALEDOWN2_FROM = 1.05f
private const val ANIM_BOX_SCALEDOWN2_TO = 0.99f
private const val ANIM_BOX_SCALEDOWN2_DURATION = 116L
private const val ANIM_BOX_SCALEUP3_FROM = 0.99f
private const val ANIM_BOX_SCALEUP3_TO = 1.00f
private const val ANIM_BOX_SCALEUP3_DURATION = 133L
// createTextAnimatorSet
private const val ANIM_TEXT_FADEOUT_FROM = 1.0f
private const val ANIM_TEXT_FADEOUT_TO = 0.0f
private const val ANIM_TEXT_FADEOUT_DURATION = 33L
private const val ANIM_TEXT_FADEIN_FROM = 0.01f
private const val ANIM_TEXT_FADEIN_TO = 1.0f
private const val ANIM_TEXT_FADEIN_DURATION = 66L
private const val ANIM_TEXT_FADEIN_DELAY = 16L * 6
private const val ANIM_TEXT_MOVEDOWN_FROM = 0.0f
private const val ANIM_TEXT_MOVEDOWN_TO = 4.4f
private const val ANIM_TEXT_MOVEDOWN_DURATION = 66L
private const val ANIM_TEXT_MOVEDOWN_DELAY = 16L * 6
private const val ANIM_TEXT_MOVEUP_FROM = 4.4f
private const val ANIM_TEXT_MOVEUP_TO = 0.0f
private const val ANIM_TEXT_MOVEUP_DURATION = 66L
}
}

View File

@ -1,121 +0,0 @@
/* 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.components.toolbar
import android.content.Context
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.menu2.BrowserMenuController
import mozilla.components.concept.menu.MenuController
import mozilla.components.concept.menu.candidate.DividerMenuCandidate
import mozilla.components.concept.menu.candidate.DrawableMenuIcon
import mozilla.components.concept.menu.candidate.MenuCandidate
import mozilla.components.concept.menu.candidate.TextMenuCandidate
import mozilla.components.concept.menu.candidate.TextStyle
import mozilla.components.support.ktx.android.content.getColorFromAttr
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
class TabCounterMenu(
context: Context,
private val metrics: MetricController,
private val onItemTapped: (Item) -> Unit
) {
sealed class Item {
object CloseTab : Item()
data class NewTab(val mode: BrowsingMode) : Item()
}
val menuController: MenuController by lazy { BrowserMenuController() }
private val newTabItem: TextMenuCandidate
private val newPrivateTabItem: TextMenuCandidate
private val closeTabItem: TextMenuCandidate
init {
val primaryTextColor = context.getColorFromAttr(R.attr.primaryText)
val textStyle = TextStyle(color = primaryTextColor)
newTabItem = TextMenuCandidate(
text = context.getString(R.string.browser_menu_new_tab),
start = DrawableMenuIcon(
context,
R.drawable.ic_new,
tint = primaryTextColor
),
textStyle = textStyle
) {
metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_TAB))
onItemTapped(Item.NewTab(BrowsingMode.Normal))
}
newPrivateTabItem = TextMenuCandidate(
text = context.getString(R.string.home_screen_shortcut_open_new_private_tab_2),
start = DrawableMenuIcon(
context,
R.drawable.ic_private_browsing,
tint = primaryTextColor
),
textStyle = textStyle
) {
metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_PRIVATE_TAB))
onItemTapped(Item.NewTab(BrowsingMode.Private))
}
closeTabItem = TextMenuCandidate(
text = context.getString(R.string.close_tab),
start = DrawableMenuIcon(
context,
R.drawable.ic_close,
tint = primaryTextColor
),
textStyle = textStyle
) {
metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.CLOSE_TAB))
onItemTapped(Item.CloseTab)
}
}
@VisibleForTesting
internal fun menuItems(showOnly: BrowsingMode): List<MenuCandidate> {
return when (showOnly) {
BrowsingMode.Normal -> listOf(newTabItem)
BrowsingMode.Private -> listOf(newPrivateTabItem)
}
}
@VisibleForTesting
internal fun menuItems(toolbarPosition: ToolbarPosition): List<MenuCandidate> {
val items = listOf(
newTabItem,
newPrivateTabItem,
DividerMenuCandidate(),
closeTabItem
)
return when (toolbarPosition) {
ToolbarPosition.BOTTOM -> items.reversed()
ToolbarPosition.TOP -> items
}
}
/**
* Update the displayed menu items.
* @param showOnly Show only the new tab item corresponding to the given [BrowsingMode].
*/
fun updateMenu(showOnly: BrowsingMode) {
menuController.submitList(menuItems(showOnly))
}
/**
* Update the displayed menu items.
* @param toolbarPosition Return a list that is ordered based on the given [ToolbarPosition].
*/
fun updateMenu(toolbarPosition: ToolbarPosition) {
menuController.submitList(menuItems(toolbarPosition))
}
}

View File

@ -1,85 +0,0 @@
/* 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.components.toolbar
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.LifecycleOwner
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import mozilla.components.browser.state.selector.getNormalOrPrivateTabs
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.toolbar.Toolbar
import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.support.ktx.android.content.res.resolveAttribute
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import org.mozilla.fenix.ext.components
import java.lang.ref.WeakReference
/**
* A [Toolbar.Action] implementation that shows a [TabCounter].
*/
@OptIn(ExperimentalCoroutinesApi::class)
class TabCounterToolbarButton(
private val lifecycleOwner: LifecycleOwner,
private val onItemTapped: (TabCounterMenu.Item) -> Unit = {},
private val showTabs: () -> Unit
) : Toolbar.Action {
private var reference: WeakReference<TabCounter> = WeakReference<TabCounter>(null)
override fun createView(parent: ViewGroup): View {
val store = parent.context.components.core.store
val metrics = parent.context.components.analytics.metrics
val settings = parent.context.components.settings
store.flowScoped(lifecycleOwner) { flow ->
flow.map { state -> state.getNormalOrPrivateTabs(isPrivate(store)).size }
.ifChanged()
.collect { tabs -> updateCount(tabs) }
}
val menu = TabCounterMenu(parent.context, metrics, onItemTapped)
menu.updateMenu(settings.toolbarPosition)
val view = TabCounter(parent.context).apply {
reference = WeakReference(this)
setOnClickListener {
showTabs.invoke()
}
setOnLongClickListener {
menu.menuController.show(anchor = it)
true
}
addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View?) {
setCount(store.state.getNormalOrPrivateTabs(isPrivate(store)).size)
}
override fun onViewDetachedFromWindow(v: View?) { /* no-op */ }
})
}
// Set selectableItemBackgroundBorderless
view.setBackgroundResource(parent.context.theme.resolveAttribute(
android.R.attr.selectableItemBackgroundBorderless
))
return view
}
override fun bind(view: View) = Unit
private fun updateCount(count: Int) {
reference.get()?.setCountWithAnimation(count)
}
private fun isPrivate(store: BrowserStore): Boolean {
return store.state.selectedTab?.content?.private ?: false
}
}

View File

@ -7,13 +7,17 @@ package org.mozilla.fenix.components.toolbar
import android.content.Context
import android.content.res.Configuration
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.domains.autocomplete.DomainAutocompleteProvider
import mozilla.components.browser.state.selector.normalTabs
import mozilla.components.browser.state.selector.privateTabs
import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.browser.toolbar.display.DisplayToolbar
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.storage.HistoryStorage
import mozilla.components.feature.tabs.toolbar.TabCounterToolbarButton
import mozilla.components.feature.toolbar.ToolbarAutocompleteFeature
import mozilla.components.feature.toolbar.ToolbarFeature
import mozilla.components.feature.toolbar.ToolbarPresenter
@ -35,9 +39,10 @@ abstract class ToolbarIntegration(
renderStyle: ToolbarFeature.RenderStyle
) : LifecycleAwareFeature {
val store = context.components.core.store
private val toolbarPresenter: ToolbarPresenter = ToolbarPresenter(
toolbar,
context.components.core.store,
store,
sessionId,
ToolbarFeature.UrlRenderConfiguration(
PublicSuffixList(context),
@ -139,16 +144,40 @@ class DefaultToolbarIntegration(
)!!
)
val tabsAction = TabCounterToolbarButton(
lifecycleOwner,
val tabCounterMenu = FenixTabCounterMenu(
context = context,
onItemTapped = {
interactor.onTabCounterMenuItemTapped(it)
},
iconColor =
if (isPrivate) {
ContextCompat.getColor(context, R.color.primary_text_private_theme)
} else {
null
}
).also {
it.updateMenu(context.settings().toolbarPosition)
}
val tabsAction = TabCounterToolbarButton(
lifecycleOwner = lifecycleOwner,
showTabs = {
toolbar.hideKeyboard()
interactor.onTabCounterClicked()
}
},
store = store,
menu = tabCounterMenu,
privateColor = ContextCompat.getColor(context, R.color.primary_text_private_theme)
)
val tabCount = if (isPrivate) {
store.state.privateTabs.size
} else {
store.state.normalTabs.size
}
tabsAction.updateCount(tabCount)
toolbar.addBrowserAction(tabsAction)
val engineForSpeculativeConnects = if (!isPrivate) engine else null

View File

@ -41,9 +41,7 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_home.privateBrowsingButton
import kotlinx.android.synthetic.main.fragment_home.search_engine_icon
import kotlinx.android.synthetic.main.fragment_home.toolbarLayout
import kotlinx.android.synthetic.main.fragment_home.*
import kotlinx.android.synthetic.main.fragment_home.view.bottomBarShadow
import kotlinx.android.synthetic.main.fragment_home.view.bottom_bar
import kotlinx.android.synthetic.main.fragment_home.view.homeAppBar
@ -81,6 +79,7 @@ import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.support.ktx.android.content.res.resolveAttribute
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import mozilla.components.ui.tabcounter.TabCounterMenu
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
@ -94,7 +93,7 @@ import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.tips.FenixTipManager
import org.mozilla.fenix.components.tips.Tip
import org.mozilla.fenix.components.tips.providers.MasterPasswordTipProvider
import org.mozilla.fenix.components.toolbar.TabCounterMenu
import org.mozilla.fenix.components.toolbar.FenixTabCounterMenu
import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.hideToolbar
@ -351,23 +350,7 @@ class HomeFragment : Fragment() {
observeSearchEngineChanges()
createHomeMenu(requireContext(), WeakReference(view.menuButton))
val tabCounterMenu = TabCounterMenu(
view.context,
metrics = view.context.components.analytics.metrics
) {
if (it is TabCounterMenu.Item.NewTab) {
(activity as HomeActivity).browsingModeManager.mode = it.mode
}
}
val inverseBrowsingMode = when ((activity as HomeActivity).browsingModeManager.mode) {
BrowsingMode.Normal -> BrowsingMode.Private
BrowsingMode.Private -> BrowsingMode.Normal
}
tabCounterMenu.updateMenu(showOnly = inverseBrowsingMode)
view.tab_button.setOnLongClickListener {
tabCounterMenu.menuController.show(anchor = it)
true
}
createTabCounterMenu(view)
view.menuButton.setColorFilter(
ContextCompat.getColor(
@ -463,6 +446,40 @@ class HomeFragment : Fragment() {
}
}
private fun createTabCounterMenu(view: View) {
val browsingModeManager = (activity as HomeActivity).browsingModeManager
val mode = browsingModeManager.mode
val onItemTapped: (TabCounterMenu.Item) -> Unit = {
if (it is TabCounterMenu.Item.NewTab) {
browsingModeManager.mode = BrowsingMode.Normal
} else if (it is TabCounterMenu.Item.NewPrivateTab) {
browsingModeManager.mode = BrowsingMode.Private
}
}
val tabCounterMenu = FenixTabCounterMenu(
view.context,
onItemTapped,
iconColor = if (mode == BrowsingMode.Private) {
ContextCompat.getColor(requireContext(), R.color.primary_text_private_theme)
} else {
null
}
)
val inverseBrowsingMode = when (mode) {
BrowsingMode.Normal -> BrowsingMode.Private
BrowsingMode.Private -> BrowsingMode.Normal
}
tabCounterMenu.updateMenu(showOnly = inverseBrowsingMode)
view.tab_button.setOnLongClickListener {
tabCounterMenu.menuController.show(anchor = it)
true
}
}
private fun removeAllTabsAndShowSnackbar(sessionCode: String) {
if (sessionCode == ALL_PRIVATE_TABS) {
sessionManager.removePrivateSessions()
@ -960,8 +977,11 @@ class HomeFragment : Fragment() {
)
}
// TODO use [FenixTabCounterToolbarButton] instead of [TabCounter]:
// https://github.com/mozilla-mobile/fenix/issues/16792
private fun updateTabCounter(browserState: BrowserState) {
val tabCount = if (browsingModeManager.mode.isPrivate) {
view?.tab_button?.setColor(ContextCompat.getColor(requireContext(), R.color.primary_text_private_theme))
browserState.privateTabs.size
} else {
browserState.normalTabs.size

View File

@ -68,7 +68,7 @@ class AwesomeBarView(
override fun invoke(
searchTerms: String,
searchEngine: mozilla.components.browser.search.SearchEngine?,
parentSession: Session?
parentSessionId: String?
) {
interactor.onSearchTermsTapped(searchTerms)
}
@ -78,7 +78,7 @@ class AwesomeBarView(
override fun invoke(
searchTerms: String,
searchEngine: mozilla.components.browser.search.SearchEngine?,
parentSession: Session?
parentSessionId: String?
) {
interactor.onSearchTermsTapped(searchTerms)
}

View File

@ -42,12 +42,12 @@ import mozilla.components.browser.tabstray.TabViewHolder
import mozilla.components.feature.syncedtabs.SyncedTabsFeature
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.support.ktx.android.util.dpToPx
import mozilla.components.ui.tabcounter.TabCounter.Companion.INFINITE_CHAR_PADDING_BOTTOM
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.InfoBanner
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.toolbar.TabCounter.Companion.INFINITE_CHAR_PADDING_BOTTOM
import org.mozilla.fenix.components.toolbar.TabCounter.Companion.MAX_VISIBLE_TABS
import org.mozilla.fenix.components.toolbar.TabCounter.Companion.SO_MANY_TABS_OPEN
import mozilla.components.ui.tabcounter.TabCounter.Companion.MAX_VISIBLE_TABS
import mozilla.components.ui.tabcounter.TabCounter.Companion.SO_MANY_TABS_OPEN
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.updateAccessibilityCollectionInfo
@ -62,7 +62,7 @@ import mozilla.components.browser.storage.sync.Tab as SyncTab
/**
* View that contains and configures the BrowserAwesomeBar
*/
@Suppress("LongParameterList", "TooManyFunctions", "LargeClass")
@Suppress("LongParameterList", "TooManyFunctions", "LargeClass", "ForbiddenComment")
class TabTrayView(
private val container: ViewGroup,
private val tabsAdapter: FenixTabsAdapter,
@ -697,7 +697,7 @@ class TabTrayView(
view.resources.getDimensionPixelSize(R.dimen.tab_tray_top_offset)
}
behavior.setExpandedOffset(topOffset)
behavior.expandedOffset = topOffset
}
fun dismissMenu() {

View File

@ -145,7 +145,7 @@
app:barrierDirection="start"
app:constraint_referenced_ids="tab_button" />
<org.mozilla.fenix.components.toolbar.TabCounter
<mozilla.components.ui.tabcounter.TabCounter
android:id="@+id/tab_button"
android:layout_width="48dp"
android:layout_height="48dp"

View File

@ -1,37 +0,0 @@
<?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/. -->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:layout_height="wrap_content"
tools:layout_width="wrap_content">
<FrameLayout
android:id="@+id/counter_root"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true">
<ImageView
android:id="@+id/counter_box"
android:layout_width="@dimen/tab_counter_box_width_height"
android:layout_height="@dimen/tab_counter_box_width_height"
android:importantForAccessibility="no"
app:srcCompat="@drawable/ic_tabs"/>
<!-- This text size auto adjusts based on num digits in `TabCounter` -->
<TextView
android:id="@+id/counter_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAlignment="center"
android:textColor="?primaryText"
android:textSize="12sp"
android:textStyle="bold"
tools:text="16" />
</FrameLayout>
</merge>

View File

@ -31,7 +31,7 @@
android:layout_weight="1"
android:background="@drawable/home_search_background" />
<org.mozilla.fenix.components.toolbar.TabCounter
<mozilla.components.ui.tabcounter.TabCounter
android:id="@+id/tab_button"
android:layout_width="48dp"
android:layout_height="48dp"

View File

@ -100,4 +100,8 @@
<!-- TextInputLayout default Error Color -->
<color name="design_error" tools:override="true">@color/destructive_dark_theme</color>
<!-- Tab Counter colors -->
<color name="mozac_ui_tabcounter_default_tint">@color/primary_text_dark_theme</color>
<color name="mozac_ui_tabcounter_default_text">@color/primary_text_dark_theme</color>
</resources>

View File

@ -413,4 +413,8 @@
<!-- TextInputLayout default Error Color -->
<color name="design_error" tools:override="true">@color/destructive_light_theme</color>
<!-- Tab Counter colors -->
<color name="mozac_ui_tabcounter_default_tint">@color/primary_text_light_theme</color>
<color name="mozac_ui_tabcounter_default_text">@color/primary_text_light_theme</color>
</resources>

View File

@ -152,8 +152,6 @@
<string name="browser_menu_find_in_page">Find in page</string>
<!-- Browser menu button that creates a private tab -->
<string name="browser_menu_private_tab">Private tab</string>
<!-- Browser menu button that creates a new tab -->
<string name="browser_menu_new_tab">New tab</string>
<!-- Browser menu button that saves the current tab to a collection -->
<string name="browser_menu_save_to_collection_2">Save to collection</string>
<!-- Browser menu button that open a share menu to share the current site -->

View File

@ -4,6 +4,7 @@ import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.mockk
import io.mockk.verify
import mozilla.components.ui.tabcounter.TabCounterMenu
import org.junit.Before
import org.junit.Test

View File

@ -22,6 +22,7 @@ import mozilla.components.concept.engine.EngineView
import mozilla.components.feature.search.SearchUseCases
import mozilla.components.feature.session.SessionUseCases
import mozilla.components.feature.top.sites.TopSitesUseCases
import mozilla.components.ui.tabcounter.TabCounterMenu
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
@ -240,7 +241,7 @@ class DefaultBrowserToolbarControllerTest {
@Test
fun handleToolbarNewTabPress() {
val browsingModeManager = SimpleBrowsingModeManager(BrowsingMode.Private)
val item = TabCounterMenu.Item.NewTab(BrowsingMode.Normal)
val item = TabCounterMenu.Item.NewTab
every { activity.browsingModeManager } returns browsingModeManager
every { navController.navigate(BrowserFragmentDirections.actionGlobalHome(focusOnAddressBar = true)) } just Runs
@ -254,7 +255,7 @@ class DefaultBrowserToolbarControllerTest {
@Test
fun handleToolbarNewPrivateTabPress() {
val browsingModeManager = SimpleBrowsingModeManager(BrowsingMode.Normal)
val item = TabCounterMenu.Item.NewTab(BrowsingMode.Private)
val item = TabCounterMenu.Item.NewPrivateTab
every { activity.browsingModeManager } returns browsingModeManager
every { navController.navigate(BrowserFragmentDirections.actionGlobalHome(focusOnAddressBar = true)) } just Runs

View File

@ -7,50 +7,31 @@ package org.mozilla.fenix.components.toolbar
import android.content.Context
import androidx.appcompat.view.ContextThemeWrapper
import io.mockk.mockk
import io.mockk.verifyAll
import io.mockk.verify
import mozilla.components.concept.menu.candidate.DividerMenuCandidate
import mozilla.components.concept.menu.candidate.DrawableMenuIcon
import mozilla.components.concept.menu.candidate.TextMenuCandidate
import mozilla.components.support.test.robolectric.testContext
import mozilla.components.ui.tabcounter.TabCounterMenu
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class)
class TabCounterMenuTest {
private lateinit var context: Context
private lateinit var metrics: MetricController
private lateinit var onItemTapped: (TabCounterMenu.Item) -> Unit
private lateinit var menu: TabCounterMenu
private lateinit var menu: FenixTabCounterMenu
@Before
fun setup() {
context = ContextThemeWrapper(testContext, R.style.NormalTheme)
metrics = mockk(relaxed = true)
onItemTapped = mockk(relaxed = true)
menu = TabCounterMenu(context, metrics, onItemTapped)
}
@Test
fun `all items use primary text color styling`() {
val items = menu.menuItems(ToolbarPosition.BOTTOM)
assertEquals(4, items.size)
val textItems = items.mapNotNull { it as? TextMenuCandidate }
assertEquals(3, textItems.size)
val primaryTextColor = context.getColor(R.color.primary_text_normal_theme)
for (item in textItems) {
assertEquals(primaryTextColor, item.textStyle.color)
assertEquals(primaryTextColor, (item.start as DrawableMenuIcon).tint)
}
menu = FenixTabCounterMenu(context, onItemTapped)
}
@Test
@ -62,10 +43,7 @@ class TabCounterMenuTest {
assertEquals("New tab", item.text)
item.onClick()
verifyAll {
metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_TAB))
onItemTapped(TabCounterMenu.Item.NewTab(BrowsingMode.Normal))
}
verify { onItemTapped(TabCounterMenu.Item.NewTab) }
}
@Test
@ -77,10 +55,7 @@ class TabCounterMenuTest {
assertEquals("New private tab", item.text)
item.onClick()
verifyAll {
metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_PRIVATE_TAB))
onItemTapped(TabCounterMenu.Item.NewTab(BrowsingMode.Private))
}
verify { onItemTapped(TabCounterMenu.Item.NewPrivateTab) }
}
@Test

View File

@ -3,5 +3,5 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
object AndroidComponents {
const val VERSION = "68.0.20201201190117"
const val VERSION = "69.0.20201203202830"
}

View File

@ -32,7 +32,7 @@ object Versions {
const val androidx_core = "1.3.2"
const val androidx_paging = "2.1.0"
const val androidx_transition = "1.3.0"
const val androidx_work = "2.2.0"
const val androidx_work = "2.4.0"
const val google_material = "1.2.1"
const val mozilla_android_components = AndroidComponents.VERSION
@ -139,6 +139,7 @@ object Deps {
const val mozilla_ui_colors = "org.mozilla.components:ui-colors:${Versions.mozilla_android_components}"
const val mozilla_ui_icons = "org.mozilla.components:ui-icons:${Versions.mozilla_android_components}"
const val mozilla_ui_widgets = "org.mozilla.components:ui-widgets:${Versions.mozilla_android_components}"
const val mozilla_ui_tabcounter = "org.mozilla.components:ui-tabcounter:${Versions.mozilla_android_components}"
const val mozilla_lib_crash = "org.mozilla.components:lib-crash:${Versions.mozilla_android_components}"
const val mozilla_lib_push_firebase = "org.mozilla.components:lib-push-firebase:${Versions.mozilla_android_components}"