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.home
7 import android.content.Context
8 import androidx.core.content.ContextCompat.getColor
9 import androidx.lifecycle.Lifecycle
10 import androidx.lifecycle.LifecycleOwner
11 import androidx.lifecycle.lifecycleScope
12 import kotlinx.coroutines.Dispatchers
13 import kotlinx.coroutines.launch
14 import mozilla.components.browser.menu.BrowserMenuBuilder
15 import mozilla.components.browser.menu.BrowserMenuHighlight
16 import mozilla.components.browser.menu.BrowserMenuItem
17 import mozilla.components.browser.menu.ext.getHighlight
18 import mozilla.components.browser.menu.item.BrowserMenuDivider
19 import mozilla.components.browser.menu.item.BrowserMenuHighlightableItem
20 import mozilla.components.browser.menu.item.BrowserMenuImageSwitch
21 import mozilla.components.browser.menu.item.BrowserMenuImageText
22 import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
23 import mozilla.components.concept.sync.AccountObserver
24 import mozilla.components.concept.sync.AuthType
25 import mozilla.components.concept.sync.OAuthAccount
26 import mozilla.components.support.ktx.android.content.getColorFromAttr
27 import org.mozilla.fenix.Config
28 import org.mozilla.fenix.R
29 import org.mozilla.fenix.components.accounts.AccountState
30 import org.mozilla.fenix.components.accounts.FenixAccountManager
31 import org.mozilla.fenix.components.toolbar.BrowserMenuSignIn
32 import org.mozilla.fenix.ext.components
33 import org.mozilla.fenix.ext.settings
34 import org.mozilla.fenix.nimbus.FxNimbus
35 import org.mozilla.fenix.theme.ThemeManager
36 import org.mozilla.fenix.whatsnew.WhatsNew
38 @Suppress("LargeClass", "LongMethod")
40 private val lifecycleOwner: LifecycleOwner,
41 private val context: Context,
42 private val onItemTapped: (Item) -> Unit = {},
43 private val onMenuBuilderChanged: (BrowserMenuBuilder) -> Unit = {},
44 private val onHighlightPresent: (BrowserMenuHighlight) -> Unit = {},
47 object Bookmarks : Item()
48 object History : Item()
49 object Downloads : Item()
50 object Extensions : Item()
51 data class SyncAccount(val accountState: AccountState) : Item()
54 * A button item to open up the settings page of FxA, shown up in mozilla online builds.
56 object ManageAccountAndDevices : Item()
57 object WhatsNew : Item()
59 object CustomizeHome : Item()
60 object Settings : Item()
62 object ReconnectSync : Item()
63 data class DesktopMode(val checked: Boolean) : Item()
66 private val primaryTextColor = ThemeManager.resolveAttribute(R.attr.textPrimary, context)
67 private val syncDisconnectedColor =
68 ThemeManager.resolveAttribute(R.attr.syncDisconnected, context)
69 private val syncDisconnectedBackgroundColor =
70 context.getColorFromAttr(R.attr.syncDisconnectedBackground)
72 private val accountManager = FenixAccountManager(context)
74 // 'Reconnect' and 'Quit' items aren't needed most of the time, so we'll only create the if necessary.
75 private val reconnectToSyncItem by lazy {
76 BrowserMenuHighlightableItem(
77 context.getString(R.string.sync_reconnect),
78 R.drawable.ic_sync_disconnected,
79 iconTintColorResource = syncDisconnectedColor,
80 textColorResource = primaryTextColor,
81 highlight = BrowserMenuHighlight.HighPriority(
82 backgroundTint = syncDisconnectedBackgroundColor,
85 isHighlighted = { true },
87 onItemTapped.invoke(Item.ReconnectSync)
91 private val quitItem by lazy {
93 context.getString(R.string.delete_browsing_data_on_quit_action),
94 R.drawable.mozac_ic_cross_circle_24,
97 onItemTapped.invoke(Item.Quit)
101 private fun syncSignInMenuItem(): BrowserMenuItem {
102 return BrowserMenuSignIn(primaryTextColor) {
103 onItemTapped.invoke(Item.SyncAccount(accountManager.accountState))
107 val desktopItem = BrowserMenuImageSwitch(
108 imageResource = R.drawable.ic_desktop,
109 label = context.getString(R.string.browser_menu_desktop_site),
110 initialState = { context.settings().openNextTabInDesktopMode },
112 onItemTapped.invoke(Item.DesktopMode(checked))
115 @Suppress("ComplexMethod")
116 private fun coreMenuItems(): List<BrowserMenuItem> {
117 val settings = context.components.settings
119 val bookmarksItem = BrowserMenuImageText(
120 context.getString(R.string.library_bookmarks),
121 R.drawable.ic_bookmark_list,
124 onItemTapped.invoke(Item.Bookmarks)
127 val historyItem = BrowserMenuImageText(
128 context.getString(R.string.library_history),
129 R.drawable.ic_history,
132 onItemTapped.invoke(Item.History)
135 val downloadsItem = BrowserMenuImageText(
136 context.getString(R.string.library_downloads),
137 R.drawable.ic_download,
140 onItemTapped.invoke(Item.Downloads)
143 val extensionsItem = BrowserMenuImageText(
144 context.getString(R.string.browser_menu_add_ons),
145 R.drawable.ic_addons_extensions,
148 onItemTapped.invoke(Item.Extensions)
151 val manageAccountAndDevicesItem = SimpleBrowserMenuItem(
152 context.getString(R.string.browser_menu_manage_account_and_devices),
153 textColorResource = primaryTextColor,
155 onItemTapped.invoke(Item.ManageAccountAndDevices)
158 val whatsNewItem = BrowserMenuHighlightableItem(
159 context.getString(R.string.browser_menu_whats_new),
160 R.drawable.ic_whats_new,
161 iconTintColorResource = primaryTextColor,
162 highlight = BrowserMenuHighlight.LowPriority(
163 notificationTint = getColor(context, R.color.fx_mobile_icon_color_information),
165 isHighlighted = { WhatsNew.shouldHighlightWhatsNew(context) },
167 onItemTapped.invoke(Item.WhatsNew)
170 val helpItem = BrowserMenuImageText(
171 context.getString(R.string.browser_menu_help),
172 R.drawable.mozac_ic_help_circle_24,
175 onItemTapped.invoke(Item.Help)
178 val customizeHomeItem = BrowserMenuImageText(
179 context.getString(R.string.browser_menu_customize_home_1),
180 R.drawable.ic_customize,
183 onItemTapped.invoke(Item.CustomizeHome)
186 // Use nimbus to set the icon and title.
187 val nimbusValidation = FxNimbus.features.nimbusValidation.value()
188 val settingsItem = BrowserMenuImageText(
189 nimbusValidation.settingsTitle,
190 R.drawable.mozac_ic_settings,
193 onItemTapped.invoke(Item.Settings)
196 // Only query account manager if it has been initialized.
197 // We don't want to cause its initialization just for this check.
198 val accountAuthItem =
199 if (context.components.backgroundServices.accountManagerAvailableQueue.isReady() &&
200 context.components.backgroundServices.accountManager.accountNeedsReauth()
207 val menuItems = listOfNotNull(
212 syncSignInMenuItem(),
214 if (Config.channel.isMozillaOnline) manageAccountAndDevicesItem else null,
215 BrowserMenuDivider(),
217 BrowserMenuDivider(),
222 if (settings.shouldDeleteBrowsingDataOnQuit) quitItem else null,
224 items.getHighlight()?.let { onHighlightPresent(it) }
231 val menuItems = coreMenuItems()
233 // Report initial state.
234 onMenuBuilderChanged(BrowserMenuBuilder(menuItems))
236 // Observe account state changes, and update menu item builder with a new set of items.
237 context.components.backgroundServices.accountManagerAvailableQueue.runIfReadyOrQueue {
238 // This task isn't relevant if our parent fragment isn't around anymore.
239 if (lifecycleOwner.lifecycle.currentState == Lifecycle.State.DESTROYED) {
240 return@runIfReadyOrQueue
242 context.components.backgroundServices.accountManager.register(
243 object : AccountObserver {
244 override fun onAuthenticationProblems() {
245 lifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
246 onMenuBuilderChanged(
254 override fun onAuthenticated(account: OAuthAccount, authType: AuthType) {
255 lifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
256 onMenuBuilderChanged(
264 override fun onLoggedOut() {
265 lifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
266 onMenuBuilderChanged(