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 itselfupstream-sync
parent
79d1c08402
commit
77f061c362
@ -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))
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
|
Loading…
Reference in New Issue