diff --git a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt index 0804ca5ed..9fcc47586 100644 --- a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt +++ b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt @@ -17,7 +17,7 @@ object FeatureFlags { /** * Allows edit of saved logins. */ - val loginsEdit = Config.channel.isNightlyOrDebug + const val loginsEdit = true /** * Enable tab sync feature diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt index 565224752..54e342daf 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.launch import mozilla.appservices.places.BookmarkRoot import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager +import mozilla.components.concept.engine.EngineSession.LoadUrlFlags import mozilla.components.concept.engine.EngineView import mozilla.components.concept.engine.prompt.ShareData import mozilla.components.feature.session.SessionFeature @@ -177,7 +178,15 @@ class DefaultBrowserToolbarController( sessionUseCases.goForward.invoke(currentSession) } } - ToolbarMenu.Item.Reload -> sessionUseCases.reload.invoke(currentSession) + is ToolbarMenu.Item.Reload -> { + val flags = if (item.bypassCache) { + LoadUrlFlags.select(LoadUrlFlags.BYPASS_CACHE) + } else { + LoadUrlFlags.none() + } + + sessionUseCases.reload.invoke(currentSession, flags = flags) + } ToolbarMenu.Item.Stop -> sessionUseCases.stopLoading.invoke(currentSession) ToolbarMenu.Item.Settings -> browserAnimator.captureEngineViewAndDrawStatically { val directions = BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment() @@ -340,7 +349,7 @@ class DefaultBrowserToolbarController( val eventItem = when (item) { ToolbarMenu.Item.Back -> Event.BrowserMenuItemTapped.Item.BACK is ToolbarMenu.Item.Forward -> Event.BrowserMenuItemTapped.Item.FORWARD - ToolbarMenu.Item.Reload -> Event.BrowserMenuItemTapped.Item.RELOAD + is ToolbarMenu.Item.Reload -> Event.BrowserMenuItemTapped.Item.RELOAD ToolbarMenu.Item.Stop -> Event.BrowserMenuItemTapped.Item.STOP ToolbarMenu.Item.Settings -> Event.BrowserMenuItemTapped.Item.SETTINGS is ToolbarMenu.Item.RequestDesktop -> diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt index 419cd5e9d..e1a9df5fe 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.components.toolbar +import android.view.HapticFeedbackConstants import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -156,6 +157,7 @@ class BrowserToolbarView( customTabSession?.id, shouldReverseItems = toolbarPosition == ToolbarPosition.TOP, onItemTapped = { + it.performHapticIfNeeded(view) interactor.onBrowserToolbarMenuItemTapped(it) } ) @@ -164,7 +166,10 @@ class BrowserToolbarView( context = this, hasAccountProblem = components.backgroundServices.accountManager.accountNeedsReauth(), shouldReverseItems = toolbarPosition == ToolbarPosition.TOP, - onItemTapped = { interactor.onBrowserToolbarMenuItemTapped(it) }, + onItemTapped = { + it.performHapticIfNeeded(view) + interactor.onBrowserToolbarMenuItemTapped(it) + }, lifecycleOwner = lifecycleOwner, sessionManager = sessionManager, store = components.core.store, @@ -244,4 +249,12 @@ class BrowserToolbarView( companion object { private const val TOOLBAR_ELEVATION = 16 } + + private fun ToolbarMenu.Item.performHapticIfNeeded(view: View) { + (this as? ToolbarMenu.Item.Reload)?.also { reload -> + if (reload.bypassCache) { + view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + } + } + } } diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt index 49251ba2e..db8ebb164 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt @@ -98,12 +98,13 @@ class DefaultToolbarMenu( secondaryImageResource = mozilla.components.ui.icons.R.drawable.mozac_ic_stop, secondaryContentDescription = context.getString(R.string.browser_menu_stop), secondaryImageTintResource = primaryTextColor(), - disableInSecondaryState = false + disableInSecondaryState = false, + longClickListener = { onItemTapped.invoke(ToolbarMenu.Item.Reload(bypassCache = true)) } ) { if (session?.loading == true) { onItemTapped.invoke(ToolbarMenu.Item.Stop) } else { - onItemTapped.invoke(ToolbarMenu.Item.Reload) + onItemTapped.invoke(ToolbarMenu.Item.Reload(bypassCache = false)) } } diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt index aeb33dd15..46c422bb9 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt @@ -15,7 +15,7 @@ interface ToolbarMenu { object Share : Item() object Back : Item() data class Forward(val viewHistory: Boolean) : Item() - object Reload : Item() + data class Reload(val bypassCache: Boolean) : Item() object Stop : Item() object OpenInFenix : Item() object SaveToCollection : Item() diff --git a/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarMenu.kt index a5f0a9017..4c69f7877 100644 --- a/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarMenu.kt @@ -91,12 +91,13 @@ class CustomTabToolbarMenu( secondaryImageResource = mozilla.components.ui.icons.R.drawable.mozac_ic_stop, secondaryContentDescription = context.getString(R.string.browser_menu_stop), secondaryImageTintResource = primaryTextColor(), - disableInSecondaryState = false + disableInSecondaryState = false, + longClickListener = { onItemTapped.invoke(ToolbarMenu.Item.Reload(bypassCache = true)) } ) { if (session?.loading == true) { onItemTapped.invoke(ToolbarMenu.Item.Stop) } else { - onItemTapped.invoke(ToolbarMenu.Item.Reload) + onItemTapped.invoke(ToolbarMenu.Item.Reload(bypassCache = false)) } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt index 4b8a25e88..c19d5bc71 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt @@ -73,6 +73,7 @@ class SettingsFragment : PreferenceFragmentCompat() { accountUiView = AccountUiView( fragment = this, + scope = lifecycleScope, accountManager = requireComponents.backgroundServices.accountManager, httpClient = requireComponents.core.client, updateFxASyncOverrideMenu = ::updateFxASyncOverrideMenu @@ -137,6 +138,11 @@ class SettingsFragment : PreferenceFragmentCompat() { creatingFragment = false } + override fun onDestroyView() { + super.onDestroyView() + accountUiView.cancel() + } + private fun update(shouldUpdateAccountUIState: Boolean) { val trackingProtectionPreference = requirePreference(R.string.pref_key_tracking_protection_settings) diff --git a/app/src/main/java/org/mozilla/fenix/settings/account/AccountUiView.kt b/app/src/main/java/org/mozilla/fenix/settings/account/AccountUiView.kt index bb5fa64c9..965ff46cf 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/account/AccountUiView.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/account/AccountUiView.kt @@ -7,10 +7,12 @@ package org.mozilla.fenix.settings.account import android.content.Context import androidx.appcompat.content.res.AppCompatResources import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory -import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import mozilla.components.concept.fetch.Client import mozilla.components.concept.sync.Profile @@ -21,12 +23,12 @@ import org.mozilla.fenix.settings.requirePreference class AccountUiView( fragment: PreferenceFragmentCompat, + private val scope: CoroutineScope, private val accountManager: FxaAccountManager, private val httpClient: Client, private val updateFxASyncOverrideMenu: () -> Unit ) { - private val lifecycleScope = fragment.viewLifecycleOwner.lifecycleScope private val preferenceSignIn = fragment.requirePreference(R.string.pref_key_sign_in) private val preferenceFirefoxAccount = @@ -36,6 +38,8 @@ class AccountUiView( private val accountPreferenceCategory = fragment.requirePreference(R.string.pref_key_account_category) + private var avatarJob: Job? = null + /** * Updates the UI to reflect current account state. * Possible conditions are logged-in without problems, logged-out, and logged-in but needs to re-authenticate. @@ -49,15 +53,16 @@ class AccountUiView( if (account != null && !accountManager.accountNeedsReauth()) { preferenceSignIn.isVisible = false - profile?.avatar?.url?.let { avatarUrl -> - lifecycleScope.launch { - val roundedDrawable = toRoundedDrawable(avatarUrl, context) - preferenceFirefoxAccount.icon = - roundedDrawable ?: AppCompatResources.getDrawable( - context, - R.drawable.ic_account - ) + avatarJob?.cancel() + val avatarUrl = profile?.avatar?.url + if (avatarUrl != null) { + avatarJob = scope.launch { + val roundedAvatarDrawable = toRoundedDrawable(avatarUrl, context) + preferenceFirefoxAccount.icon = roundedAvatarDrawable ?: genericAvatar(context) } + } else { + avatarJob = null + preferenceFirefoxAccount.icon = genericAvatar(context) } preferenceSignIn.onPreferenceClickListener = null @@ -88,6 +93,19 @@ class AccountUiView( } } + /** + * Cancel any running coroutine jobs for loading account images. + */ + fun cancel() { + scope.cancel() + } + + /** + * Returns generic avatar for accounts. + */ + private fun genericAvatar(context: Context) = + AppCompatResources.getDrawable(context, R.drawable.ic_account) + /** * Gets a rounded drawable from a URL if possible, else null. */ diff --git a/app/src/main/java/org/mozilla/fenix/settings/search/AddSearchEngineFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/search/AddSearchEngineFragment.kt index ba40743f8..d3e4aaa56 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/search/AddSearchEngineFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/search/AddSearchEngineFragment.kt @@ -41,7 +41,8 @@ import org.mozilla.fenix.settings.SupportUtils import java.util.Locale @SuppressWarnings("LargeClass", "TooManyFunctions") -class AddSearchEngineFragment : Fragment(), CompoundButton.OnCheckedChangeListener { +class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine), + CompoundButton.OnCheckedChangeListener { private var availableEngines: List = listOf() private var selectedIndex: Int = -1 private val engineViews = mutableListOf() @@ -62,14 +63,6 @@ class AddSearchEngineFragment : Fragment(), CompoundButton.OnCheckedChangeListen selectedIndex = if (availableEngines.isEmpty()) CUSTOM_INDEX else FIRST_INDEX } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_add_search_engine, container, false) - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val layoutInflater = LayoutInflater.from(context) diff --git a/app/src/main/res/layout/fragment_site_permissions_exceptions.xml b/app/src/main/res/layout/fragment_site_permissions_exceptions.xml index cd7bac1de..65bd02787 100644 --- a/app/src/main/res/layout/fragment_site_permissions_exceptions.xml +++ b/app/src/main/res/layout/fragment_site_permissions_exceptions.xml @@ -19,6 +19,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" + tools:listitem="@layout/fragment_site_permissions_exceptions_item" tools:visibility="visible" /> + app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index c33f8f0f4..7e878d0fd 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -38,10 +38,10 @@ app:destination="@id/turnOnSyncFragment" /> + app:destination="@id/addons_management_graph" /> + app:destination="@id/search_engine_graph" /> @@ -80,7 +80,7 @@ app:destination="@id/bookmarkEditFragment" /> + app:destination="@id/addons_management_graph" /> @@ -165,42 +165,6 @@ app:argType="org.mozilla.fenix.settings.PhoneFeature" /> - - - - - - - - - - - - - - - - - - - - - - @@ -799,63 +742,6 @@ android:id="@+id/saveLoginSettingFragment" android:name="org.mozilla.fenix.settings.logins.fragment.SavedLoginsSettingFragment" android:label="SaveLoginSettingFragment" /> - - - - - - - - - - - - - - - - - - - - - - - @@ -870,4 +756,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt index c203fb976..5c69b4bf0 100644 --- a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt @@ -30,6 +30,7 @@ import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.ReaderState import mozilla.components.browser.state.state.createTab import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.engine.EngineView import mozilla.components.concept.engine.prompt.ShareData import mozilla.components.feature.search.SearchUseCases @@ -235,7 +236,7 @@ class DefaultBrowserToolbarControllerTest { @Test fun handleToolbarReloadPress() = runBlockingTest { - val item = ToolbarMenu.Item.Reload + val item = ToolbarMenu.Item.Reload(false) every { activity.components.useCases.sessionUseCases } returns sessionUseCases @@ -246,6 +247,24 @@ class DefaultBrowserToolbarControllerTest { verify { sessionUseCases.reload(currentSession) } } + @Test + fun handleToolbarReloadLongPress() = runBlockingTest { + val item = ToolbarMenu.Item.Reload(true) + + every { activity.components.useCases.sessionUseCases } returns sessionUseCases + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.RELOAD)) } + verify { + sessionUseCases.reload( + currentSession, + EngineSession.LoadUrlFlags.select(EngineSession.LoadUrlFlags.BYPASS_CACHE) + ) + } + } + @Test fun handleToolbarStopPress() = runBlockingTest { val item = ToolbarMenu.Item.Stop