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/. */
5 package org.mozilla.fenix.customtabs
7 import android.content.Context
8 import android.graphics.Typeface
9 import androidx.annotation.ColorRes
10 import androidx.annotation.VisibleForTesting
11 import androidx.core.content.ContextCompat.getColor
12 import mozilla.components.browser.menu.BrowserMenuBuilder
13 import mozilla.components.browser.menu.BrowserMenuHighlight
14 import mozilla.components.browser.menu.item.BrowserMenuCategory
15 import mozilla.components.browser.menu.item.BrowserMenuDivider
16 import mozilla.components.browser.menu.item.BrowserMenuHighlightableItem
17 import mozilla.components.browser.menu.item.BrowserMenuImageSwitch
18 import mozilla.components.browser.menu.item.BrowserMenuImageText
19 import mozilla.components.browser.menu.item.BrowserMenuItemToolbar
20 import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
21 import mozilla.components.browser.state.selector.findCustomTab
22 import mozilla.components.browser.state.state.CustomTabSessionState
23 import mozilla.components.browser.state.store.BrowserStore
24 import org.mozilla.fenix.R
25 import org.mozilla.fenix.components.toolbar.ToolbarMenu
26 import org.mozilla.fenix.ext.components
27 import org.mozilla.fenix.ext.getStringWithArgSafe
28 import org.mozilla.fenix.ext.settings
29 import org.mozilla.fenix.theme.ThemeManager
30 import java.util.Locale
33 * Builds the toolbar object used with the 3-dot menu in the custom tab browser fragment.
35 * @property context An Android [Context].
36 * @property store reference to the application's [BrowserStore].
37 * @property sessionId ID of the open custom tab session.
38 * @property shouldReverseItems If true, reverse the menu items.
39 * @property isSandboxCustomTab If true, menu should not show the "Open in Firefox" and "POWERED BY FIREFOX" items.
40 * @property onItemTapped Called when a menu item is tapped.
42 class CustomTabToolbarMenu(
43 private val context: Context,
44 private val store: BrowserStore,
45 private val sessionId: String?,
46 private val shouldReverseItems: Boolean,
47 private val isSandboxCustomTab: Boolean,
48 private val onItemTapped: (ToolbarMenu.Item) -> Unit = {},
51 override val menuBuilder by lazy { BrowserMenuBuilder(menuItems) }
53 /** Gets the current custom tab session */
55 internal val session: CustomTabSessionState? get() = sessionId?.let { store.state.findCustomTab(it) }
56 private val appName = context.getString(R.string.app_name)
58 override val menuToolbar by lazy {
59 val back = BrowserMenuItemToolbar.TwoStateButton(
60 primaryImageResource = mozilla.components.ui.icons.R.drawable.mozac_ic_back_24,
61 primaryContentDescription = context.getString(R.string.browser_menu_back),
62 primaryImageTintResource = primaryTextColor(),
64 session?.content?.canGoBack ?: true
66 secondaryImageTintResource = ThemeManager.resolveAttribute(
70 disableInSecondaryState = true,
71 longClickListener = { onItemTapped.invoke(ToolbarMenu.Item.Back(viewHistory = true)) },
73 onItemTapped.invoke(ToolbarMenu.Item.Back(viewHistory = false))
76 val forward = BrowserMenuItemToolbar.TwoStateButton(
77 primaryImageResource = mozilla.components.ui.icons.R.drawable.mozac_ic_forward_24,
78 primaryContentDescription = context.getString(R.string.browser_menu_forward),
79 primaryImageTintResource = primaryTextColor(),
81 session?.content?.canGoForward ?: true
83 secondaryImageTintResource = ThemeManager.resolveAttribute(
87 disableInSecondaryState = true,
88 longClickListener = { onItemTapped.invoke(ToolbarMenu.Item.Forward(viewHistory = true)) },
90 onItemTapped.invoke(ToolbarMenu.Item.Forward(viewHistory = false))
93 val refresh = BrowserMenuItemToolbar.TwoStateButton(
94 primaryImageResource = mozilla.components.ui.icons.R.drawable.mozac_ic_arrow_clockwise_24,
95 primaryContentDescription = context.getString(R.string.browser_menu_refresh),
96 primaryImageTintResource = primaryTextColor(),
98 session?.content?.loading == false
100 secondaryImageResource = mozilla.components.ui.icons.R.drawable.mozac_ic_stop,
101 secondaryContentDescription = context.getString(R.string.browser_menu_stop),
102 secondaryImageTintResource = primaryTextColor(),
103 disableInSecondaryState = false,
104 longClickListener = { onItemTapped.invoke(ToolbarMenu.Item.Reload(bypassCache = true)) },
106 if (session?.content?.loading == true) {
107 onItemTapped.invoke(ToolbarMenu.Item.Stop)
109 onItemTapped.invoke(ToolbarMenu.Item.Reload(bypassCache = false))
113 BrowserMenuItemToolbar(listOf(back, forward, refresh))
116 private fun shouldShowOpenInApp(): Boolean = session?.let { session ->
117 val appLink = context.components.useCases.appLinksUseCases.appLinkRedirect
118 appLink(session.content.url).hasExternalApp()
121 private val menuItems by lazy {
122 val menuItems = listOf(
123 poweredBy.apply { visible = { !isSandboxCustomTab } },
124 BrowserMenuDivider().apply { visible = { !isSandboxCustomTab } },
127 openInApp.apply { visible = ::shouldShowOpenInApp },
128 openInFenix.apply { visible = { !isSandboxCustomTab } },
129 BrowserMenuDivider(),
132 if (shouldReverseItems) {
139 private val desktopMode = BrowserMenuImageSwitch(
140 imageResource = R.drawable.ic_desktop,
141 label = context.getString(R.string.browser_menu_desktop_site),
142 initialState = { session?.content?.desktopMode ?: false },
144 onItemTapped.invoke(ToolbarMenu.Item.RequestDesktop(checked))
147 private val findInPage = BrowserMenuImageText(
148 label = context.getString(R.string.browser_menu_find_in_page),
149 imageResource = R.drawable.mozac_ic_search_24,
150 iconTintColorResource = primaryTextColor(),
152 onItemTapped.invoke(ToolbarMenu.Item.FindInPage)
155 private val openInApp = BrowserMenuHighlightableItem(
156 label = context.getString(R.string.browser_menu_open_app_link),
157 startImageResource = R.drawable.ic_open_in_app,
158 iconTintColorResource = primaryTextColor(),
159 highlight = BrowserMenuHighlight.LowPriority(
160 label = context.getString(R.string.browser_menu_open_app_link),
161 notificationTint = getColor(context, R.color.fx_mobile_icon_color_information),
163 isHighlighted = { !context.settings().openInAppOpened },
165 onItemTapped.invoke(ToolbarMenu.Item.OpenInApp)
168 private val openInFenix = SimpleBrowserMenuItem(
169 label = context.getString(R.string.browser_menu_open_in_fenix, appName),
170 textColorResource = primaryTextColor(),
172 onItemTapped.invoke(ToolbarMenu.Item.OpenInFenix)
175 private val poweredBy = BrowserMenuCategory(
176 label = context.getStringWithArgSafe(R.string.browser_menu_powered_by, appName)
177 .uppercase(Locale.getDefault()),
178 textSize = CAPTION_TEXT_SIZE,
179 textColorResource = primaryTextColor(),
180 textStyle = Typeface.NORMAL,
184 private fun primaryTextColor() = ThemeManager.resolveAttribute(R.attr.textPrimary, context)
187 private const val CAPTION_TEXT_SIZE = 12f