commit aabaae1041c48cda93045cff4c0b5dbfe2cfd793 parent ee610bcc49edc4508883e8701224790de1d54c9a Author: rmalicdem <rmalicdem@mozilla.com> Date: Tue, 11 Nov 2025 21:29:21 +0000 Bug 1969124 - Part 1: Implement navbar customization r=android-reviewers,Roger Differential Revision: https://phabricator.services.mozilla.com/D271151 Diffstat:
10 files changed, 507 insertions(+), 262 deletions(-)
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMiddleware.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMiddleware.kt @@ -126,7 +126,7 @@ import org.mozilla.fenix.ext.isWideWindow import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.navigateSafe import org.mozilla.fenix.nimbus.FxNimbus -import org.mozilla.fenix.settings.ToolbarShortcutPreference +import org.mozilla.fenix.settings.ShortcutType import org.mozilla.fenix.settings.quicksettings.protections.cookiebanners.getCookieBannerUIMode import org.mozilla.fenix.tabstray.Page import org.mozilla.fenix.tabstray.ext.isActiveDownload @@ -747,8 +747,8 @@ class BrowserToolbarMiddleware( private fun buildEndPageActions(): List<Action> { val isWideScreen = environment?.fragment?.isWideWindow() == true val tabStripEnabled = settings.isTabStripEnabled - val translateShortcutEnabled = settings.toolbarShortcutKey == ToolbarShortcutPreference.Keys.TRANSLATE - val shareShortcutEnabled = settings.toolbarShortcutKey == ToolbarShortcutPreference.Keys.SHARE + val translateShortcutEnabled = settings.toolbarSimpleShortcutKey == ShortcutType.TRANSLATE + val shareShortcutEnabled = settings.toolbarSimpleShortcutKey == ShortcutType.SHARE return listOf( ToolbarActionConfig(ToolbarAction.ReaderMode) { @@ -776,7 +776,8 @@ class BrowserToolbarMiddleware( val shouldUseExpandedToolbar = settings.shouldUseExpandedToolbar val useCustomPrimary = settings.shouldShowToolbarCustomization && !shouldUseExpandedToolbar val primarySlotAction = mapShortcutToAction( - settings.toolbarShortcutKey, + settings.toolbarSimpleShortcutKey, + ToolbarAction.NewTab, isBookmarked, ).takeIf { useCustomPrimary } ?: ToolbarAction.NewTab @@ -817,14 +818,15 @@ class BrowserToolbarMiddleware( val isWideWindow = environment.fragment.isWideWindow() val isTallWindow = environment.fragment.isTallWindow() val shouldUseExpandedToolbar = settings.shouldUseExpandedToolbar + val useCustomPrimary = settings.shouldShowToolbarCustomization && shouldUseExpandedToolbar + val primarySlotAction = mapShortcutToAction( + settings.toolbarExpandedShortcutKey, + getBookmarkAction(isBookmarked), + isBookmarked, + ).takeIf { useCustomPrimary } ?: getBookmarkAction(isBookmarked) return listOf( - ToolbarActionConfig(ToolbarAction.Bookmark) { - shouldUseExpandedToolbar && isTallWindow && !isWideWindow && !isBookmarked - }, - ToolbarActionConfig(ToolbarAction.EditBookmark) { - shouldUseExpandedToolbar && isTallWindow && !isWideWindow && isBookmarked - }, + ToolbarActionConfig(primarySlotAction) { shouldUseExpandedToolbar && isTallWindow && !isWideWindow }, ToolbarActionConfig(ToolbarAction.Share) { shouldUseExpandedToolbar && isTallWindow && !isWideWindow }, ToolbarActionConfig(ToolbarAction.NewTab) { shouldUseExpandedToolbar && isTallWindow && !isWideWindow }, ToolbarActionConfig(ToolbarAction.TabCounter) { shouldUseExpandedToolbar && isTallWindow && !isWideWindow }, @@ -1315,21 +1317,26 @@ class BrowserToolbarMiddleware( companion object { @VisibleForTesting + internal fun getBookmarkAction(isBookmarked: Boolean): ToolbarAction = + when (isBookmarked) { + true -> ToolbarAction.EditBookmark + false -> ToolbarAction.Bookmark + } + + @VisibleForTesting @JvmStatic internal fun mapShortcutToAction( key: String, + default: ToolbarAction, isBookmarked: Boolean = false, ): ToolbarAction = when (key) { - ToolbarShortcutPreference.Keys.NEW_TAB -> ToolbarAction.NewTab - ToolbarShortcutPreference.Keys.SHARE -> ToolbarAction.Share - ToolbarShortcutPreference.Keys.BOOKMARK -> when (isBookmarked) { - true -> ToolbarAction.EditBookmark - false -> ToolbarAction.Bookmark - } - ToolbarShortcutPreference.Keys.TRANSLATE -> ToolbarAction.Translate - ToolbarShortcutPreference.Keys.HOMEPAGE -> ToolbarAction.Homepage - ToolbarShortcutPreference.Keys.BACK -> ToolbarAction.Back - else -> ToolbarAction.NewTab + ShortcutType.NEW_TAB -> ToolbarAction.NewTab + ShortcutType.SHARE -> ToolbarAction.Share + ShortcutType.BOOKMARK -> getBookmarkAction(isBookmarked) + ShortcutType.TRANSLATE -> ToolbarAction.Translate + ShortcutType.HOMEPAGE -> ToolbarAction.Homepage + ShortcutType.BACK -> ToolbarAction.Back + else -> default } } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/CustomizationFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/CustomizationFragment.kt @@ -93,18 +93,29 @@ class CustomizationFragment : PreferenceFragmentCompat() { } private fun updateToolbarShortcutBasedOnLayout() { - val category = requirePreference<PreferenceCategory>( - R.string.pref_key_customization_category_toolbar_shortcut, + val simpleCategory = requirePreference<PreferenceCategory>( + R.string.pref_key_customization_category_toolbar_simple_shortcut, + ) + val expandedCategory = requirePreference<PreferenceCategory>( + R.string.pref_key_customization_category_toolbar_expanded_shortcut, ) val settings = requireContext().settings() - category.isVisible = + simpleCategory.isVisible = settings.shouldShowToolbarCustomization && Config.channel.isNightlyOrDebug && settings.shouldUseComposableToolbar && settings.toolbarRedesignEnabled && isTallWindow() && !settings.shouldUseExpandedToolbar + + expandedCategory.isVisible = + settings.shouldShowToolbarCustomization && + Config.channel.isNightlyOrDebug && + settings.shouldUseComposableToolbar && + settings.toolbarRedesignEnabled && + isTallWindow() && + settings.shouldUseExpandedToolbar } private fun setupRadioGroups() { diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/ToolbarExpandedShortcutPreference.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/ToolbarExpandedShortcutPreference.kt @@ -0,0 +1,162 @@ +/* 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 + +import android.content.Context +import android.content.res.ColorStateList +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.RadioButton +import android.widget.TextView +import androidx.annotation.AttrRes +import androidx.annotation.ColorInt +import androidx.core.widget.ImageViewCompat +import androidx.preference.Preference +import androidx.preference.PreferenceViewHolder +import com.google.android.material.color.MaterialColors +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.settings +import com.google.android.material.R as materialR +import mozilla.components.ui.icons.R as iconsR + +/** + * Custom Preference that renders the expanded shortcut options list. + * Selecting an option moves it to the top and persists to SharedPreferences. + */ +class ToolbarExpandedShortcutPreference @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, +) : Preference(context, attrs) { + + private val options: List<ShortcutOption> by lazy { + listOf( + ShortcutOption( + ShortcutType.BOOKMARK, + iconsR.drawable.mozac_ic_bookmark_24, + R.string.toolbar_customize_shortcut_add_bookmark, + ), + ) + } + + @ColorInt + private var colorTertiary: Int = 0 + + @ColorInt + private var colorOnSurface: Int = 0 + + @ColorInt + private var colorOnSurfaceVariant: Int = 0 + + init { + layoutResource = R.layout.preference_toolbar_shortcut + isSelectable = false + } + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + + val settings = context.settings() + val selectedKey = settings.toolbarExpandedShortcutKey + + val selectedContainer = holder.findViewById(R.id.selected_container) as LinearLayout + val optionsContainer = holder.findViewById(R.id.options_container) as LinearLayout + val separator = holder.findViewById(R.id.separator) as View + + colorTertiary = holder.itemView.getMaterialColor(materialR.attr.colorTertiary) + colorOnSurface = holder.itemView.getMaterialColor(materialR.attr.colorOnSurface) + colorOnSurfaceVariant = holder.itemView.getMaterialColor(materialR.attr.colorOnSurfaceVariant) + + val selected = options.firstOrNull { it.key == selectedKey } ?: options.first() + + selectedContainer.removeAllViews() + selectedContainer.addView( + makeRow( + parent = selectedContainer, + option = selected, + isChecked = true, + isEnabled = false, + onClick = {}, + ), + ) + + val remaining = options.filter { it.key != selected.key }.distinctBy { it.key } + optionsContainer.removeAllViews() + remaining.forEach { opt -> + optionsContainer.addView( + makeRow( + parent = optionsContainer, + option = opt, + isChecked = false, + isEnabled = true, + ) { newlySelected -> + settings.toolbarExpandedShortcutKey = newlySelected.key + notifyChanged() + }, + ) + } + + separator.visibility = if (remaining.isEmpty()) View.GONE else View.VISIBLE + } + + private fun makeRow( + parent: ViewGroup, + option: ShortcutOption, + isChecked: Boolean, + isEnabled: Boolean, + onClick: (ShortcutOption) -> Unit, + ): View { + val row = LayoutInflater.from(context) + .inflate(R.layout.toolbar_shortcut_row, parent, false) as LinearLayout + + val radio = row.findViewById<RadioButton>(R.id.row_radio) + val icon = row.findViewById<ImageView>(R.id.row_icon) + val label = row.findViewById<TextView>(R.id.row_label) + + icon.setImageResource(option.icon) + label.setText(option.label) + + radio?.setStartCheckedIndicator() + radio.isChecked = isChecked + radio.isEnabled = true + + label.setTextColor( + if (isChecked) colorTertiary else colorOnSurface, + ) + + ImageViewCompat.setImageTintList( + icon, + ColorStateList.valueOf( + if (isChecked) colorTertiary else colorOnSurface, + ), + ) + + radio.buttonTintList = ColorStateList( + arrayOf( + intArrayOf(android.R.attr.state_checked), + intArrayOf(-android.R.attr.state_checked), + ), + intArrayOf(colorTertiary, colorOnSurfaceVariant), + ) + + if (isEnabled) { + val clicker = View.OnClickListener { + onClick(option) + } + row.setOnClickListener(clicker) + } + + row.isEnabled = isEnabled + + return row + } + + private fun View.getMaterialColor(@AttrRes attr: Int): Int { + return MaterialColors.getColor(this, attr) + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/ToolbarShortcut.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/ToolbarShortcut.kt @@ -0,0 +1,27 @@ +/* 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 + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes + +/** + * String keys used to persist and map the selected toolbar shortcut option. + * These values are stored in preferences and also used to resolve the UI/action. + */ +object ShortcutType { + const val NEW_TAB = "new_tab" + const val SHARE = "share" + const val BOOKMARK = "bookmark" + const val TRANSLATE = "translate" + const val HOMEPAGE = "homepage" + const val BACK = "back" +} + +internal data class ShortcutOption( + val key: String, + @param:DrawableRes val icon: Int, + @param:StringRes val label: Int, +) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/ToolbarShortcutPreference.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/ToolbarShortcutPreference.kt @@ -1,208 +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.settings - -import android.content.Context -import android.content.res.ColorStateList -import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.RadioButton -import android.widget.TextView -import androidx.annotation.AttrRes -import androidx.annotation.ColorInt -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import androidx.core.widget.ImageViewCompat -import androidx.preference.Preference -import androidx.preference.PreferenceViewHolder -import com.google.android.material.color.MaterialColors -import org.mozilla.fenix.R -import org.mozilla.fenix.ext.settings -import com.google.android.material.R as materialR -import mozilla.components.ui.icons.R as iconsR - -/** - * Custom Preference that renders the options list. - * Selecting an option moves it to the top and persists to SharedPreferences. - */ -class ToolbarShortcutPreference @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, -) : Preference(context, attrs) { - - private val options: List<Option> by lazy { - listOf( - Option( - Keys.NEW_TAB, - iconsR.drawable.mozac_ic_plus_24, - R.string.toolbar_customize_shortcut_new_tab, - ), - Option( - Keys.SHARE, - iconsR.drawable.mozac_ic_share_android_24, - R.string.toolbar_customize_shortcut_share, - ), - Option( - Keys.BOOKMARK, - iconsR.drawable.mozac_ic_bookmark_24, - R.string.toolbar_customize_shortcut_add_bookmark, - ), - Option( - Keys.TRANSLATE, - iconsR.drawable.mozac_ic_translate_24, - R.string.toolbar_customize_shortcut_translate, - ), - Option( - Keys.HOMEPAGE, - iconsR.drawable.mozac_ic_home_24, - R.string.toolbar_customize_shortcut_homepage, - ), - Option( - Keys.BACK, - iconsR.drawable.mozac_ic_back_24, - R.string.toolbar_customize_shortcut_back, - ), - ) - } - - @ColorInt - private var colorTertiary: Int = 0 - - @ColorInt - private var colorOnSurface: Int = 0 - - @ColorInt - private var colorOnSurfaceVariant: Int = 0 - - init { - layoutResource = R.layout.preference_toolbar_shortcut - isSelectable = false - } - - override fun onBindViewHolder(holder: PreferenceViewHolder) { - super.onBindViewHolder(holder) - - val settings = context.settings() - val selectedKey = settings.toolbarShortcutKey - - val selectedContainer = holder.findViewById(R.id.selected_container) as LinearLayout - val optionsContainer = holder.findViewById(R.id.options_container) as LinearLayout - val separator = holder.findViewById(R.id.separator) as View - - colorTertiary = holder.itemView.getMaterialColor(materialR.attr.colorTertiary) - colorOnSurface = holder.itemView.getMaterialColor(materialR.attr.colorOnSurface) - colorOnSurfaceVariant = holder.itemView.getMaterialColor(materialR.attr.colorOnSurfaceVariant) - - val selected = options.firstOrNull { it.key == selectedKey } ?: options.first() - - selectedContainer.removeAllViews() - selectedContainer.addView( - makeRow( - parent = selectedContainer, - option = selected, - isChecked = true, - isEnabled = false, - onClick = {}, - ), - ) - - val remaining = options.filter { it.key != selected.key }.distinctBy { it.key } - optionsContainer.removeAllViews() - remaining.forEach { opt -> - optionsContainer.addView( - makeRow( - parent = optionsContainer, - option = opt, - isChecked = false, - isEnabled = true, - ) { newlySelected -> - settings.toolbarShortcutKey = newlySelected.key - notifyChanged() - }, - ) - } - - separator.visibility = if (remaining.isEmpty()) View.GONE else View.VISIBLE - } - - private fun makeRow( - parent: ViewGroup, - option: Option, - isChecked: Boolean, - isEnabled: Boolean, - onClick: (Option) -> Unit, - ): View { - val row = LayoutInflater.from(context) - .inflate(R.layout.toolbar_shortcut_row, parent, false) as LinearLayout - - val radio = row.findViewById<RadioButton>(R.id.row_radio) - val icon = row.findViewById<ImageView>(R.id.row_icon) - val label = row.findViewById<TextView>(R.id.row_label) - - icon.setImageResource(option.icon) - label.setText(option.label) - - radio?.setStartCheckedIndicator() - radio.isChecked = isChecked - radio.isEnabled = true - - label.setTextColor( - if (isChecked) colorTertiary else colorOnSurface, - ) - - ImageViewCompat.setImageTintList( - icon, - ColorStateList.valueOf( - if (isChecked) colorTertiary else colorOnSurface, - ), - ) - - radio.buttonTintList = ColorStateList( - arrayOf( - intArrayOf(android.R.attr.state_checked), - intArrayOf(-android.R.attr.state_checked), - ), - intArrayOf(colorTertiary, colorOnSurfaceVariant), - ) - - if (isEnabled) { - val clicker = View.OnClickListener { - onClick(option) - } - row.setOnClickListener(clicker) - } - - row.isEnabled = isEnabled - - return row - } - - private fun View.getMaterialColor(@AttrRes attr: Int): Int { - return MaterialColors.getColor(this, attr) - } - - private data class Option( - val key: String, - @param:DrawableRes val icon: Int, - @param:StringRes val label: Int, - ) - - /** - * String keys used to persist and map the selected toolbar shortcut option. - * These values are stored in preferences and also used to resolve the UI/action. - */ - object Keys { - const val NEW_TAB = "new_tab" - const val SHARE = "share" - const val BOOKMARK = "bookmark" - const val TRANSLATE = "translate" - const val HOMEPAGE = "homepage" - const val BACK = "back" - } -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/ToolbarSimpleShortcutPreference.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/ToolbarSimpleShortcutPreference.kt @@ -0,0 +1,187 @@ +/* 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 + +import android.content.Context +import android.content.res.ColorStateList +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.RadioButton +import android.widget.TextView +import androidx.annotation.AttrRes +import androidx.annotation.ColorInt +import androidx.core.widget.ImageViewCompat +import androidx.preference.Preference +import androidx.preference.PreferenceViewHolder +import com.google.android.material.color.MaterialColors +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.settings +import com.google.android.material.R as materialR +import mozilla.components.ui.icons.R as iconsR + +/** + * Custom Preference that renders the simple shortcut options list. + * Selecting an option moves it to the top and persists to SharedPreferences. + */ +class ToolbarSimpleShortcutPreference @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, +) : Preference(context, attrs) { + + private val options: List<ShortcutOption> by lazy { + listOf( + ShortcutOption( + ShortcutType.NEW_TAB, + iconsR.drawable.mozac_ic_plus_24, + R.string.toolbar_customize_shortcut_new_tab, + ), + ShortcutOption( + ShortcutType.SHARE, + iconsR.drawable.mozac_ic_share_android_24, + R.string.toolbar_customize_shortcut_share, + ), + ShortcutOption( + ShortcutType.BOOKMARK, + iconsR.drawable.mozac_ic_bookmark_24, + R.string.toolbar_customize_shortcut_add_bookmark, + ), + ShortcutOption( + ShortcutType.TRANSLATE, + iconsR.drawable.mozac_ic_translate_24, + R.string.toolbar_customize_shortcut_translate, + ), + ShortcutOption( + ShortcutType.HOMEPAGE, + iconsR.drawable.mozac_ic_home_24, + R.string.toolbar_customize_shortcut_homepage, + ), + ShortcutOption( + ShortcutType.BACK, + iconsR.drawable.mozac_ic_back_24, + R.string.toolbar_customize_shortcut_back, + ), + ) + } + + @ColorInt + private var colorTertiary: Int = 0 + + @ColorInt + private var colorOnSurface: Int = 0 + + @ColorInt + private var colorOnSurfaceVariant: Int = 0 + + init { + layoutResource = R.layout.preference_toolbar_shortcut + isSelectable = false + } + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + + val settings = context.settings() + val selectedKey = settings.toolbarSimpleShortcutKey + + val selectedContainer = holder.findViewById(R.id.selected_container) as LinearLayout + val optionsContainer = holder.findViewById(R.id.options_container) as LinearLayout + val separator = holder.findViewById(R.id.separator) as View + + colorTertiary = holder.itemView.getMaterialColor(materialR.attr.colorTertiary) + colorOnSurface = holder.itemView.getMaterialColor(materialR.attr.colorOnSurface) + colorOnSurfaceVariant = holder.itemView.getMaterialColor(materialR.attr.colorOnSurfaceVariant) + + val selected = options.firstOrNull { it.key == selectedKey } ?: options.first() + + selectedContainer.removeAllViews() + selectedContainer.addView( + makeRow( + parent = selectedContainer, + option = selected, + isChecked = true, + isEnabled = false, + onClick = {}, + ), + ) + + val remaining = options.filter { it.key != selected.key }.distinctBy { it.key } + optionsContainer.removeAllViews() + remaining.forEach { opt -> + optionsContainer.addView( + makeRow( + parent = optionsContainer, + option = opt, + isChecked = false, + isEnabled = true, + ) { newlySelected -> + settings.toolbarSimpleShortcutKey = newlySelected.key + notifyChanged() + }, + ) + } + + separator.visibility = if (remaining.isEmpty()) View.GONE else View.VISIBLE + } + + private fun makeRow( + parent: ViewGroup, + option: ShortcutOption, + isChecked: Boolean, + isEnabled: Boolean, + onClick: (ShortcutOption) -> Unit, + ): View { + val row = LayoutInflater.from(context) + .inflate(R.layout.toolbar_shortcut_row, parent, false) as LinearLayout + + val radio = row.findViewById<RadioButton>(R.id.row_radio) + val icon = row.findViewById<ImageView>(R.id.row_icon) + val label = row.findViewById<TextView>(R.id.row_label) + + icon.setImageResource(option.icon) + label.setText(option.label) + + radio?.setStartCheckedIndicator() + radio.isChecked = isChecked + radio.isEnabled = true + + label.setTextColor( + if (isChecked) colorTertiary else colorOnSurface, + ) + + ImageViewCompat.setImageTintList( + icon, + ColorStateList.valueOf( + if (isChecked) colorTertiary else colorOnSurface, + ), + ) + + radio.buttonTintList = ColorStateList( + arrayOf( + intArrayOf(android.R.attr.state_checked), + intArrayOf(-android.R.attr.state_checked), + ), + intArrayOf(colorTertiary, colorOnSurfaceVariant), + ) + + if (isEnabled) { + val clicker = View.OnClickListener { + onClick(option) + } + row.setOnClickListener(clicker) + } + + row.isEnabled = isEnabled + + return row + } + + private fun View.getMaterialColor(@AttrRes attr: Int): Int { + return MaterialColors.getColor(this, attr) + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -204,15 +204,24 @@ class Settings(private val appContext: Context) : PreferencesHolder { ) /** - * Indicates what toolbar shortcut key is currently selected. + * Indicates what simple toolbar shortcut key is currently selected. */ - var toolbarShortcutKey: String by stringPreference( - key = appContext.getPreferenceKey(R.string.pref_key_toolbar_shortcut), + var toolbarSimpleShortcutKey: String by stringPreference( + key = appContext.getPreferenceKey(R.string.pref_key_toolbar_simple_shortcut), default = { "new_tab" }, persistDefaultIfNotExists = true, ) /** + * Indicates what expanded toolbar shortcut key is currently selected. + */ + var toolbarExpandedShortcutKey: String by stringPreference( + key = appContext.getPreferenceKey(R.string.pref_key_toolbar_expanded_shortcut), + default = { "bookmark" }, + persistDefaultIfNotExists = true, + ) + + /** * Indicates if the Pocket recommendations homescreen section should also show sponsored stories. */ val showPocketSponsoredStories by lazyFeatureFlagPreference( diff --git a/mobile/android/fenix/app/src/main/res/values/preference_keys.xml b/mobile/android/fenix/app/src/main/res/values/preference_keys.xml @@ -203,8 +203,10 @@ <string name="pref_key_customization_category_toolbar" translatable="false">pref_key_customization_category_toolbar</string> <string name="pref_key_customization_category_toolbar_layout" translatable="false">pref_key_customization_category_toolbar_layout</string> <string name="pref_key_customization_category_app_icon" translatable="false">pref_key_customization_category_app_icon</string> - <string name="pref_key_customization_category_toolbar_shortcut" translatable="false">pref_key_customization_category_toolbar_shortcut</string> - <string name="pref_key_toolbar_shortcut" translatable="false">pref_key_toolbar_shortcut</string> + <string name="pref_key_customization_category_toolbar_simple_shortcut" translatable="false">pref_key_customization_category_toolbar_simple_shortcut</string> + <string name="pref_key_customization_category_toolbar_expanded_shortcut" translatable="false">pref_key_customization_category_toolbar_expanded_shortcut</string> + <string name="pref_key_toolbar_simple_shortcut" translatable="false">pref_key_toolbar_simple_shortcut</string> + <string name="pref_key_toolbar_expanded_shortcut" translatable="false">pref_key_toolbar_expanded_shortcut</string> <!-- HTTPS Only Settings --> <string name="pref_key_https_only_settings" translatable="false">pref_key_https_only_settings</string> diff --git a/mobile/android/fenix/app/src/main/res/xml/customization_preferences.xml b/mobile/android/fenix/app/src/main/res/xml/customization_preferences.xml @@ -82,16 +82,29 @@ app:trueOptionTitle="@string/preference_expanded_toolbar" /> </androidx.preference.PreferenceCategory> - <!-- Toolbar shortcut picker (simple layout only) --> + <!-- Simple toolbar shortcut picker --> <androidx.preference.PreferenceCategory android:layout="@layout/preference_cat_style" android:title="@string/preferences_toolbar_shortcut" - android:key="@string/pref_key_customization_category_toolbar_shortcut" + android:key="@string/pref_key_customization_category_toolbar_simple_shortcut" app:iconSpaceReserved="false"> <!-- Custom preference shows the selected option on top --> - <org.mozilla.fenix.settings.ToolbarShortcutPreference - android:key="@string/pref_key_toolbar_shortcut" + <org.mozilla.fenix.settings.ToolbarSimpleShortcutPreference + android:key="@string/pref_key_toolbar_simple_shortcut" + android:layout="@layout/preference_toolbar_shortcut"/> + </androidx.preference.PreferenceCategory> + + <!-- Expanded toolbar shortcut picker --> + <androidx.preference.PreferenceCategory + android:layout="@layout/preference_cat_style" + android:title="@string/preferences_toolbar_shortcut" + android:key="@string/pref_key_customization_category_toolbar_expanded_shortcut" + app:iconSpaceReserved="false"> + + <!-- Custom preference shows the selected option on top --> + <org.mozilla.fenix.settings.ToolbarExpandedShortcutPreference + android:key="@string/pref_key_toolbar_expanded_shortcut" android:layout="@layout/preference_toolbar_shortcut"/> </androidx.preference.PreferenceCategory> diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMiddlewareTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMiddlewareTest.kt @@ -167,7 +167,7 @@ import org.mozilla.fenix.components.usecases.FenixBrowserUseCases import org.mozilla.fenix.ext.directionsEq import org.mozilla.fenix.ext.settings import org.mozilla.fenix.helpers.FenixGleanTestRule -import org.mozilla.fenix.settings.ToolbarShortcutPreference +import org.mozilla.fenix.settings.ShortcutType import org.mozilla.fenix.tabstray.Page import org.mozilla.fenix.tabstray.TabsTrayAccessPoint import org.mozilla.fenix.utils.Settings @@ -2898,7 +2898,7 @@ class BrowserToolbarMiddlewareTest { } every { mockContext.resources.configuration } returns configuration every { settings.isTabStripEnabled } returns false - every { settings.toolbarShortcutKey } returns ToolbarShortcutPreference.Keys.SHARE + every { settings.toolbarSimpleShortcutKey } returns ShortcutType.SHARE val browserScreenStore = buildBrowserScreenStore() val middleware = buildMiddleware(appStore, browserScreenStore, browserStore) val toolbarStore = buildStore(middleware, browsingModeManager = browsingModeManager, navController = navController) @@ -2915,7 +2915,7 @@ class BrowserToolbarMiddlewareTest { } every { mockContext.resources.configuration } returns configuration every { settings.isTabStripEnabled } returns false - every { settings.toolbarShortcutKey } returns ToolbarShortcutPreference.Keys.TRANSLATE + every { settings.toolbarSimpleShortcutKey } returns ShortcutType.TRANSLATE val browserScreenStore = buildBrowserScreenStore() val middleware = buildMiddleware(appStore, browserScreenStore, browserStore) val toolbarStore = buildStore(middleware, browsingModeManager = browsingModeManager, navController = navController) @@ -2945,7 +2945,7 @@ class BrowserToolbarMiddlewareTest { ) } answers { browserAnimatorActionCaptor.captured.invoke(true) } every { settings.shouldShowToolbarCustomization } returns true - every { settings.toolbarShortcutKey } returns ToolbarShortcutPreference.Keys.HOMEPAGE + every { settings.toolbarSimpleShortcutKey } returns ShortcutType.HOMEPAGE val middleware = buildMiddleware() val toolbarStore = buildStore( @@ -2962,7 +2962,7 @@ class BrowserToolbarMiddlewareTest { fun `GIVEN homepage as new tab is enabled WHEN clicking the homepage button THEN navigate to homepage`() = runTest { every { settings.enableHomepageAsNewTab } returns true every { settings.shouldShowToolbarCustomization } returns true - every { settings.toolbarShortcutKey } returns ToolbarShortcutPreference.Keys.HOMEPAGE + every { settings.toolbarSimpleShortcutKey } returns ShortcutType.HOMEPAGE val middleware = buildMiddleware() val toolbarStore = buildStore( @@ -2978,7 +2978,7 @@ class BrowserToolbarMiddlewareTest { @Test fun `GIVEN simple toolbar use add bookmark shortcut AND the current page is not bookmarked WHEN initializing toolbar THEN show Bookmark in end browser actions`() = runTest { every { settings.shouldShowToolbarCustomization } returns true - every { settings.toolbarShortcutKey } returns ToolbarShortcutPreference.Keys.BOOKMARK + every { settings.toolbarSimpleShortcutKey } returns ShortcutType.BOOKMARK val toolbarStore = buildStore() val bookmarkButton = toolbarStore.state.displayState.browserActionsEnd[0] as ActionButtonRes @@ -2988,7 +2988,7 @@ class BrowserToolbarMiddlewareTest { @Test fun `GIVEN simple toolbar use add bookmark shortcut AND the current page is bookmarked WHEN initializing toolbar THEN show ACTIVE EditBookmark in end browser actions`() = runTest { every { settings.shouldShowToolbarCustomization } returns true - every { settings.toolbarShortcutKey } returns ToolbarShortcutPreference.Keys.BOOKMARK + every { settings.toolbarSimpleShortcutKey } returns ShortcutType.BOOKMARK val tab = createTab("https://example.com") val browserStore = BrowserStore( @@ -3017,7 +3017,7 @@ class BrowserToolbarMiddlewareTest { @Test fun `GIVEN simple toolbar use translate shortcut AND current page is not translated WHEN initializing toolbar THEN show Translate in end browser actions`() = runTest { every { settings.shouldShowToolbarCustomization } returns true - every { settings.toolbarShortcutKey } returns ToolbarShortcutPreference.Keys.TRANSLATE + every { settings.toolbarSimpleShortcutKey } returns ShortcutType.TRANSLATE val pageTranslationStatus: PageTranslationStatus = mockk(relaxed = true) { every { isTranslationPossible } returns true @@ -3036,7 +3036,7 @@ class BrowserToolbarMiddlewareTest { @Test fun `GIVEN simple toolbar use translate shortcut AND current page is translated WHEN initializing toolbar THEN show ACTIVE Translate in end browser actions`() = runTest { every { settings.shouldShowToolbarCustomization } returns true - every { settings.toolbarShortcutKey } returns ToolbarShortcutPreference.Keys.TRANSLATE + every { settings.toolbarSimpleShortcutKey } returns ShortcutType.TRANSLATE val pageTranslationStatus: PageTranslationStatus = mockk(relaxed = true) { every { isTranslationPossible } returns true @@ -3055,7 +3055,7 @@ class BrowserToolbarMiddlewareTest { @Test fun `GIVEN simple toolbar use homepage shortcut WHEN initializing toolbar THEN show Homepage in end browser actions`() = runTest { every { settings.shouldShowToolbarCustomization } returns true - every { settings.toolbarShortcutKey } returns ToolbarShortcutPreference.Keys.HOMEPAGE + every { settings.toolbarSimpleShortcutKey } returns ShortcutType.HOMEPAGE val toolbarStore = buildStore() @@ -3066,7 +3066,7 @@ class BrowserToolbarMiddlewareTest { @Test fun `GIVEN simple toolbar use back shortcut AND current page has no history WHEN initializing toolbar THEN show DISABLED Back in end browser actions`() = runTest { every { settings.shouldShowToolbarCustomization } returns true - every { settings.toolbarShortcutKey } returns ToolbarShortcutPreference.Keys.BACK + every { settings.toolbarSimpleShortcutKey } returns ShortcutType.BACK val toolbarStore = buildStore() @@ -3077,7 +3077,7 @@ class BrowserToolbarMiddlewareTest { @Test fun `GIVEN simple toolbar use back shortcut AND current page has history WHEN initializing toolbar THEN show ACTIVE Back in end browser actions`() = runTest { every { settings.shouldShowToolbarCustomization } returns true - every { settings.toolbarShortcutKey } returns ToolbarShortcutPreference.Keys.BACK + every { settings.toolbarSimpleShortcutKey } returns ShortcutType.BACK val tab = createTab(url = "https://example.com").let { it.copy(content = it.content.copy(canGoBack = true)) @@ -3099,41 +3099,76 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `mapShortcutToAction maps keys to actions and falls back to NewTab`() { + fun `mapShortcutToAction maps keys to actions and falls back to default set`() { assertEquals( ToolbarAction.NewTab, - BrowserToolbarMiddleware.mapShortcutToAction(ToolbarShortcutPreference.Keys.NEW_TAB), + BrowserToolbarMiddleware.mapShortcutToAction( + key = ShortcutType.NEW_TAB, + default = ToolbarAction.NewTab, + ), ) assertEquals( ToolbarAction.Share, - BrowserToolbarMiddleware.mapShortcutToAction(ToolbarShortcutPreference.Keys.SHARE), + BrowserToolbarMiddleware.mapShortcutToAction( + key = ShortcutType.SHARE, + default = ToolbarAction.NewTab, + ), ) assertEquals( ToolbarAction.Bookmark, - BrowserToolbarMiddleware.mapShortcutToAction(ToolbarShortcutPreference.Keys.BOOKMARK), + BrowserToolbarMiddleware.mapShortcutToAction( + key = ShortcutType.BOOKMARK, + default = ToolbarAction.NewTab, + isBookmarked = false, + ), ) assertEquals( ToolbarAction.EditBookmark, BrowserToolbarMiddleware.mapShortcutToAction( - ToolbarShortcutPreference.Keys.BOOKMARK, - true, + key = ShortcutType.BOOKMARK, + default = ToolbarAction.NewTab, + isBookmarked = true, ), ) assertEquals( ToolbarAction.Translate, - BrowserToolbarMiddleware.mapShortcutToAction(ToolbarShortcutPreference.Keys.TRANSLATE), + BrowserToolbarMiddleware.mapShortcutToAction( + key = ShortcutType.TRANSLATE, + default = ToolbarAction.NewTab, + ), ) assertEquals( ToolbarAction.Homepage, - BrowserToolbarMiddleware.mapShortcutToAction(ToolbarShortcutPreference.Keys.HOMEPAGE), + BrowserToolbarMiddleware.mapShortcutToAction( + key = ShortcutType.HOMEPAGE, + default = ToolbarAction.NewTab, + ), ) assertEquals( ToolbarAction.Back, - BrowserToolbarMiddleware.mapShortcutToAction(ToolbarShortcutPreference.Keys.BACK), + BrowserToolbarMiddleware.mapShortcutToAction( + key = ShortcutType.BACK, + default = ToolbarAction.NewTab, + ), ) assertEquals( ToolbarAction.NewTab, - BrowserToolbarMiddleware.mapShortcutToAction("does_not_exist"), + BrowserToolbarMiddleware.mapShortcutToAction( + key = "does_not_exist", + default = ToolbarAction.NewTab, + ), + ) + } + + @Test + fun `getBookmarkAction returns correct action based on isBookmarked flag`() { + assertEquals( + ToolbarAction.Bookmark, + BrowserToolbarMiddleware.getBookmarkAction(isBookmarked = false), + ) + assertEquals( + ToolbarAction.EditBookmark, + BrowserToolbarMiddleware.getBookmarkAction(isBookmarked = true), ) }