Bug 1840210 - Rename mozac_ic_quit to mozac_ic_cross_circle_24
[gecko.git] / mobile / android / fenix / app / src / main / java / org / mozilla / fenix / home / HomeMenu.kt
blob6ab2af549a0ffb701b53883fbe16250e4eff4259
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")
39 class HomeMenu(
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 = {},
45 ) {
46     sealed class Item {
47         object Bookmarks : Item()
48         object History : Item()
49         object Downloads : Item()
50         object Extensions : Item()
51         data class SyncAccount(val accountState: AccountState) : Item()
53         /**
54          * A button item to open up the settings page of FxA, shown up in mozilla online builds.
55          */
56         object ManageAccountAndDevices : Item()
57         object WhatsNew : Item()
58         object Help : Item()
59         object CustomizeHome : Item()
60         object Settings : Item()
61         object Quit : Item()
62         object ReconnectSync : Item()
63         data class DesktopMode(val checked: Boolean) : Item()
64     }
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,
83                 canPropagate = false,
84             ),
85             isHighlighted = { true },
86         ) {
87             onItemTapped.invoke(Item.ReconnectSync)
88         }
89     }
91     private val quitItem by lazy {
92         BrowserMenuImageText(
93             context.getString(R.string.delete_browsing_data_on_quit_action),
94             R.drawable.mozac_ic_cross_circle_24,
95             primaryTextColor,
96         ) {
97             onItemTapped.invoke(Item.Quit)
98         }
99     }
101     private fun syncSignInMenuItem(): BrowserMenuItem {
102         return BrowserMenuSignIn(primaryTextColor) {
103             onItemTapped.invoke(Item.SyncAccount(accountManager.accountState))
104         }
105     }
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 },
111     ) { checked ->
112         onItemTapped.invoke(Item.DesktopMode(checked))
113     }
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,
122             primaryTextColor,
123         ) {
124             onItemTapped.invoke(Item.Bookmarks)
125         }
127         val historyItem = BrowserMenuImageText(
128             context.getString(R.string.library_history),
129             R.drawable.ic_history,
130             primaryTextColor,
131         ) {
132             onItemTapped.invoke(Item.History)
133         }
135         val downloadsItem = BrowserMenuImageText(
136             context.getString(R.string.library_downloads),
137             R.drawable.ic_download,
138             primaryTextColor,
139         ) {
140             onItemTapped.invoke(Item.Downloads)
141         }
143         val extensionsItem = BrowserMenuImageText(
144             context.getString(R.string.browser_menu_add_ons),
145             R.drawable.ic_addons_extensions,
146             primaryTextColor,
147         ) {
148             onItemTapped.invoke(Item.Extensions)
149         }
151         val manageAccountAndDevicesItem = SimpleBrowserMenuItem(
152             context.getString(R.string.browser_menu_manage_account_and_devices),
153             textColorResource = primaryTextColor,
154         ) {
155             onItemTapped.invoke(Item.ManageAccountAndDevices)
156         }
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),
164             ),
165             isHighlighted = { WhatsNew.shouldHighlightWhatsNew(context) },
166         ) {
167             onItemTapped.invoke(Item.WhatsNew)
168         }
170         val helpItem = BrowserMenuImageText(
171             context.getString(R.string.browser_menu_help),
172             R.drawable.mozac_ic_help_circle_24,
173             primaryTextColor,
174         ) {
175             onItemTapped.invoke(Item.Help)
176         }
178         val customizeHomeItem = BrowserMenuImageText(
179             context.getString(R.string.browser_menu_customize_home_1),
180             R.drawable.ic_customize,
181             primaryTextColor,
182         ) {
183             onItemTapped.invoke(Item.CustomizeHome)
184         }
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,
191             primaryTextColor,
192         ) {
193             onItemTapped.invoke(Item.Settings)
194         }
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()
201             ) {
202                 reconnectToSyncItem
203             } else {
204                 null
205             }
207         val menuItems = listOfNotNull(
208             bookmarksItem,
209             historyItem,
210             downloadsItem,
211             extensionsItem,
212             syncSignInMenuItem(),
213             accountAuthItem,
214             if (Config.channel.isMozillaOnline) manageAccountAndDevicesItem else null,
215             BrowserMenuDivider(),
216             desktopItem,
217             BrowserMenuDivider(),
218             whatsNewItem,
219             helpItem,
220             customizeHomeItem,
221             settingsItem,
222             if (settings.shouldDeleteBrowsingDataOnQuit) quitItem else null,
223         ).also { items ->
224             items.getHighlight()?.let { onHighlightPresent(it) }
225         }
227         return menuItems
228     }
230     init {
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
241             }
242             context.components.backgroundServices.accountManager.register(
243                 object : AccountObserver {
244                     override fun onAuthenticationProblems() {
245                         lifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
246                             onMenuBuilderChanged(
247                                 BrowserMenuBuilder(
248                                     menuItems,
249                                 ),
250                             )
251                         }
252                     }
254                     override fun onAuthenticated(account: OAuthAccount, authType: AuthType) {
255                         lifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
256                             onMenuBuilderChanged(
257                                 BrowserMenuBuilder(
258                                     menuItems,
259                                 ),
260                             )
261                         }
262                     }
264                     override fun onLoggedOut() {
265                         lifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
266                             onMenuBuilderChanged(
267                                 BrowserMenuBuilder(
268                                     menuItems,
269                                 ),
270                             )
271                         }
272                     }
273                 },
274                 lifecycleOwner,
275             )
276         }
277     }