Skip to content

Commit e1666f8

Browse files
committed
Bug 1969021 - Build 'Customize Toolbar Layout' setting r=android-reviewers,Roger
Differential Revision: https://phabricator.services.mozilla.com/D267048
1 parent 4b62952 commit e1666f8

File tree

14 files changed

+385
-60
lines changed

14 files changed

+385
-60
lines changed

mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMiddleware.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ import org.mozilla.fenix.ext.isWideWindow
124124
import org.mozilla.fenix.ext.nav
125125
import org.mozilla.fenix.ext.navigateSafe
126126
import org.mozilla.fenix.nimbus.FxNimbus
127+
import org.mozilla.fenix.settings.ToolbarShortcutPreference
127128
import org.mozilla.fenix.settings.quicksettings.protections.cookiebanners.getCookieBannerUIMode
128129
import org.mozilla.fenix.tabstray.Page
129130
import org.mozilla.fenix.tabstray.ext.isActiveDownload
@@ -741,9 +742,12 @@ class BrowserToolbarMiddleware(
741742
val isTallWindow = environment?.fragment?.isTallWindow() == true
742743
val tabStripEnabled = settings.isTabStripEnabled
743744
val shouldUseExpandedToolbar = settings.shouldUseExpandedToolbar
745+
val useCustomPrimary = settings.shouldShowToolbarCustomization && !shouldUseExpandedToolbar
746+
val primarySlotAction = mapShortcutToAction(settings.toolbarShortcutKey)
747+
.takeIf { useCustomPrimary } ?: ToolbarAction.NewTab
744748

745749
val configs = listOf(
746-
ToolbarActionConfig(ToolbarAction.NewTab) {
750+
ToolbarActionConfig(primarySlotAction) {
747751
!tabStripEnabled && (!shouldUseExpandedToolbar || !isTallWindow || isWideWindow)
748752
},
749753
ToolbarActionConfig(ToolbarAction.TabCounter) {
@@ -1240,4 +1244,14 @@ class BrowserToolbarMiddleware(
12401244
Source.AddressBar -> MetricsUtils.BookmarkAction.Source.BROWSER_TOOLBAR
12411245
Source.NavigationBar -> MetricsUtils.BookmarkAction.Source.BROWSER_NAVBAR
12421246
}
1247+
1248+
companion object {
1249+
@VisibleForTesting
1250+
@JvmStatic
1251+
internal fun mapShortcutToAction(key: String): ToolbarAction = when (key) {
1252+
ToolbarShortcutPreference.Keys.NEW_TAB -> ToolbarAction.NewTab
1253+
ToolbarShortcutPreference.Keys.SHARE -> ToolbarAction.Share
1254+
else -> ToolbarAction.NewTab
1255+
}
1256+
}
12431257
}

mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/CustomizationFragment.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class CustomizationFragment : PreferenceFragmentCompat() {
6060
updateToolbarCategoryBasedOnTabStrip(tabletAndTabStripEnabled)
6161
setupTabStripCategory()
6262
setupToolbarLayout()
63+
updateToolbarShortcutBasedOnLayout()
6364

6465
// if tab strip is enabled, swipe toolbar to switch tabs should not be enabled so the
6566
// preference is not shown
@@ -86,6 +87,21 @@ class CustomizationFragment : PreferenceFragmentCompat() {
8687
}
8788
}
8889

90+
private fun updateToolbarShortcutBasedOnLayout() {
91+
val category = requirePreference<PreferenceCategory>(
92+
R.string.pref_key_customization_category_toolbar_shortcut,
93+
)
94+
val settings = requireContext().settings()
95+
96+
category.isVisible =
97+
settings.shouldShowToolbarCustomization &&
98+
Config.channel.isNightlyOrDebug &&
99+
settings.shouldUseComposableToolbar &&
100+
settings.toolbarRedesignEnabled &&
101+
isTallWindow() &&
102+
!settings.shouldUseExpandedToolbar
103+
}
104+
89105
private fun setupRadioGroups() {
90106
addToRadioGroup(
91107
radioLightTheme,
@@ -194,6 +210,11 @@ class CustomizationFragment : PreferenceFragmentCompat() {
194210
isVisible = Config.channel.isNightlyOrDebug && settings.shouldUseComposableToolbar &&
195211
settings.toolbarRedesignEnabled && isTallWindow() && !isWideWindow()
196212
}
213+
214+
val layoutToggle = requirePreference<ToggleRadioButtonPreference>(R.string.pref_key_toolbar_expanded)
215+
layoutToggle.setOnToggleChanged {
216+
updateToolbarShortcutBasedOnLayout()
217+
}
197218
updateToolbarLayoutIcons()
198219
}
199220

mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt

Lines changed: 26 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -82,57 +82,30 @@ class SecretSettingsFragment : PreferenceFragmentCompat() {
8282
}
8383
}
8484
}
85-
requirePreference<SwitchPreference>(R.string.pref_key_enable_simple_toolbar_customization).apply {
85+
requirePreference<SwitchPreference>(R.string.pref_key_enable_toolbar_customization).apply {
86+
val newOption = context.settings().toolbarRedesignEnabled
8687
isEnabled = newOption
87-
when (newOption) {
88-
true -> {
89-
summary = null
90-
}
91-
92-
false -> {
93-
isChecked = false
94-
summary = getString(R.string.preferences_debug_settings_toolbar_redesign_summary)
95-
context.settings().shouldShowSimpleToolbarCustomization = false
96-
}
88+
summary = when (newOption) {
89+
true -> null
90+
false -> getString(R.string.preferences_debug_settings_toolbar_customization_summary)
9791
}
98-
}
99-
requirePreference<SwitchPreference>(R.string.pref_key_enable_expanded_toolbar_customization).apply {
100-
isEnabled = newOption
101-
when (newOption) {
102-
true -> {
103-
summary = null
104-
}
105-
106-
false -> {
107-
isChecked = false
108-
summary = getString(R.string.preferences_debug_settings_toolbar_redesign_summary)
109-
context.settings().shouldShowExpandedToolbarCustomization = false
110-
}
92+
if (!newOption && isChecked) {
93+
isChecked = false
94+
context.settings().shouldShowToolbarCustomization = false
11195
}
11296
}
11397
}
11498
true
11599
}
116100
}
117-
118-
requirePreference<SwitchPreference>(R.string.pref_key_enable_simple_toolbar_customization).apply {
119-
isVisible = Config.channel.isDebug
120-
isChecked = context.settings().shouldShowSimpleToolbarCustomization
121-
isEnabled = context.settings().shouldUseComposableToolbar
122-
summary = when (context.settings().shouldUseComposableToolbar) {
123-
true -> null
124-
false -> getString(R.string.preferences_debug_settings_toolbar_redesign_summary)
125-
}
126-
onPreferenceChangeListener = SharedPreferenceUpdater()
127-
}
128-
129-
requirePreference<SwitchPreference>(R.string.pref_key_enable_expanded_toolbar_customization).apply {
101+
requirePreference<SwitchPreference>(R.string.pref_key_enable_toolbar_customization).apply {
130102
isVisible = Config.channel.isDebug
131-
isChecked = context.settings().shouldShowExpandedToolbarCustomization
132-
isEnabled = context.settings().shouldUseComposableToolbar
133-
summary = when (context.settings().shouldUseComposableToolbar) {
103+
isChecked = context.settings().shouldShowToolbarCustomization
104+
val newOption = context.settings().toolbarRedesignEnabled
105+
isEnabled = newOption
106+
summary = when (newOption) {
134107
true -> null
135-
false -> getString(R.string.preferences_debug_settings_toolbar_redesign_summary)
108+
false -> getString(R.string.preferences_debug_settings_toolbar_customization_summary)
136109
}
137110
onPreferenceChangeListener = SharedPreferenceUpdater()
138111
}
@@ -148,9 +121,20 @@ class SecretSettingsFragment : PreferenceFragmentCompat() {
148121
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
149122
(newValue as? Boolean)?.let { newOption ->
150123
context.settings().toolbarRedesignEnabled = newOption
151-
if (newOption == false) {
124+
if (!newOption) {
152125
context.settings().shouldUseExpandedToolbar = false
153126
}
127+
requirePreference<SwitchPreference>(R.string.pref_key_enable_toolbar_customization).apply {
128+
isEnabled = newOption
129+
summary = when (newOption) {
130+
true -> null
131+
false -> getString(R.string.preferences_debug_settings_toolbar_customization_summary)
132+
}
133+
if (!newOption && isChecked) {
134+
isChecked = false
135+
context.settings().shouldShowToolbarCustomization = false
136+
}
137+
}
154138
}
155139
true
156140
}

mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/ToggleRadioButtonPreference.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ class ToggleRadioButtonPreference @JvmOverloads constructor(
3535
private var trueOptionIconRes: Int = 0
3636
private var falseOptionIconRes: Int = 0
3737

38+
private var onToggleChanged: ((Boolean) -> Unit)? = null
39+
40+
/**
41+
* Registers a listener that is invoked whenever the toggle selection changes.
42+
*/
43+
fun setOnToggleChanged(listener: (Boolean) -> Unit) {
44+
onToggleChanged = listener
45+
}
46+
3847
init {
3948
layoutResource = R.layout.preference_widget_toggle_radio_button
4049
isSelectable = false
@@ -82,13 +91,15 @@ class ToggleRadioButtonPreference @JvmOverloads constructor(
8291
optionFalseIconView.isSelected = false
8392
preferences.edit { putBoolean(sharedKey, true) }
8493
notifyChanged()
94+
onToggleChanged?.invoke(true)
8595
}
8696

8797
optionFalseView.setOnClickListener {
8898
optionTrueIconView.isSelected = false
8999
optionFalseIconView.isSelected = true
90100
preferences.edit { putBoolean(sharedKey, false) }
91101
notifyChanged()
102+
onToggleChanged?.invoke(false)
92103
}
93104
}
94105

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
package org.mozilla.fenix.settings
6+
7+
import android.content.Context
8+
import android.content.res.ColorStateList
9+
import android.content.res.Configuration
10+
import android.util.AttributeSet
11+
import android.view.LayoutInflater
12+
import android.view.View
13+
import android.view.ViewGroup
14+
import android.widget.ImageView
15+
import android.widget.LinearLayout
16+
import android.widget.RadioButton
17+
import android.widget.TextView
18+
import androidx.annotation.DrawableRes
19+
import androidx.annotation.StringRes
20+
import androidx.compose.ui.graphics.toArgb
21+
import androidx.core.widget.ImageViewCompat
22+
import androidx.preference.Preference
23+
import androidx.preference.PreferenceViewHolder
24+
import mozilla.components.compose.base.theme.AcornColors
25+
import mozilla.components.compose.base.theme.darkColorPalette
26+
import mozilla.components.compose.base.theme.lightColorPalette
27+
import org.mozilla.fenix.R
28+
import org.mozilla.fenix.ext.settings
29+
import mozilla.components.ui.icons.R as iconsR
30+
31+
/**
32+
* Custom Preference that renders the options list.
33+
* Selecting an option moves it to the top and persists to SharedPreferences.
34+
*/
35+
class ToolbarShortcutPreference @JvmOverloads constructor(
36+
context: Context,
37+
attrs: AttributeSet? = null,
38+
) : Preference(context, attrs) {
39+
40+
private val options: List<Option> by lazy {
41+
listOf(
42+
Option(Keys.NEW_TAB, iconsR.drawable.mozac_ic_plus_24, R.string.home_screen_shortcut_open_new_tab_2),
43+
Option(Keys.SHARE, iconsR.drawable.mozac_ic_share_android_24, R.string.browser_menu_share),
44+
)
45+
}
46+
47+
init {
48+
layoutResource = R.layout.preference_toolbar_shortcut
49+
isSelectable = false
50+
}
51+
52+
override fun onBindViewHolder(holder: PreferenceViewHolder) {
53+
super.onBindViewHolder(holder)
54+
55+
val settings = context.settings()
56+
val selectedKey = settings.toolbarShortcutKey
57+
58+
val selectedContainer = holder.findViewById(R.id.selected_container) as LinearLayout
59+
val optionsContainer = holder.findViewById(R.id.options_container) as LinearLayout
60+
val separator = holder.findViewById(R.id.separator) as View
61+
62+
val selected = options.firstOrNull { it.key == selectedKey } ?: options.first()
63+
64+
selectedContainer.removeAllViews()
65+
selectedContainer.addView(
66+
makeRow(
67+
parent = selectedContainer,
68+
option = selected,
69+
isChecked = true,
70+
isEnabled = false,
71+
onClick = {},
72+
),
73+
)
74+
75+
val remaining = options.filter { it.key != selected.key }.distinctBy { it.key }
76+
optionsContainer.removeAllViews()
77+
remaining.forEach { opt ->
78+
optionsContainer.addView(
79+
makeRow(
80+
parent = optionsContainer,
81+
option = opt,
82+
isChecked = false,
83+
isEnabled = true,
84+
) { newlySelected ->
85+
settings.toolbarShortcutKey = newlySelected.key
86+
notifyChanged()
87+
},
88+
)
89+
}
90+
91+
separator.visibility = if (remaining.isEmpty()) View.GONE else View.VISIBLE
92+
}
93+
94+
private fun makeRow(
95+
parent: ViewGroup,
96+
option: Option,
97+
isChecked: Boolean,
98+
isEnabled: Boolean,
99+
onClick: (Option) -> Unit,
100+
): View {
101+
val row = LayoutInflater.from(context)
102+
.inflate(R.layout.toolbar_shortcut_row, parent, false) as LinearLayout
103+
104+
val radio = row.findViewById<RadioButton>(R.id.row_radio)
105+
val icon = row.findViewById<ImageView>(R.id.row_icon)
106+
val label = row.findViewById<TextView>(R.id.row_label)
107+
108+
icon.setImageResource(option.icon)
109+
label.setText(option.label)
110+
111+
radio?.setStartCheckedIndicator()
112+
radio.isChecked = isChecked
113+
radio.isEnabled = true
114+
115+
val colors = getPalette(context)
116+
117+
label.setTextColor(
118+
(if (isChecked) colors.textAccentDisabled else colors.textPrimary).toArgb(),
119+
)
120+
121+
ImageViewCompat.setImageTintList(
122+
icon,
123+
ColorStateList.valueOf(
124+
(if (isChecked) colors.iconAccentViolet else colors.iconPrimary).toArgb(),
125+
),
126+
)
127+
128+
radio.buttonTintList = ColorStateList(
129+
arrayOf(
130+
intArrayOf(android.R.attr.state_checked),
131+
intArrayOf(-android.R.attr.state_checked),
132+
),
133+
intArrayOf(colors.formSelected.toArgb(), colors.formDefault.toArgb()),
134+
)
135+
136+
if (isEnabled) {
137+
val clicker = View.OnClickListener {
138+
onClick(option)
139+
}
140+
row.setOnClickListener(clicker)
141+
}
142+
143+
row.isEnabled = isEnabled
144+
145+
return row
146+
}
147+
148+
/**
149+
* Get the color palette based on the current browsing mode.
150+
*
151+
* N.B: This logic was taken from [Theme.getTheme] in FirefoxTheme, however we cannot use it
152+
* directly because those functions are annotated to be Composable and refactoring that can be
153+
* done in a follow-up when needed. This needs to be done less hacky.
154+
* https://bugzilla.mozilla.org/show_bug.cgi?id=1993265
155+
*/
156+
private fun getPalette(context: Context): AcornColors {
157+
val uiMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
158+
return if (uiMode == Configuration.UI_MODE_NIGHT_YES) darkColorPalette else lightColorPalette
159+
}
160+
161+
private data class Option(
162+
val key: String,
163+
@param:DrawableRes val icon: Int,
164+
@param:StringRes val label: Int,
165+
)
166+
167+
/**
168+
* String keys used to persist and map the selected toolbar shortcut option.
169+
* These values are stored in preferences and also used to resolve the UI/action.
170+
*/
171+
object Keys {
172+
const val NEW_TAB = "new_tab"
173+
const val SHARE = "share"
174+
}
175+
}

0 commit comments

Comments
 (0)