diff --git a/app/build.gradle b/app/build.gradle index 2e22fbaca4..6778cefdad 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -480,7 +480,6 @@ dependencies { implementation Deps.google_ads_id // Required for the Google Advertising ID implementation Deps.google_play_store // Required for in-app reviews - implementation Deps.google_play_core_ktx // Required for in-app reviews androidTestImplementation Deps.uiautomator // Removed pending AndroidX fixes diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index 67c29b552f..c1e6500acf 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -135,6 +135,8 @@ open class FenixApplication : LocaleAwareApplication(), Provider { } initializeWebExtensionSupport() + restoreDownloads() + // Just to make sure it is impossible for any application-services pieces // to invoke parts of itself that require complete megazord initialization // before that process completes, we wait here, if necessary. @@ -161,6 +163,12 @@ open class FenixApplication : LocaleAwareApplication(), Provider { components.appStartupTelemetry.onFenixApplicationOnCreate() } + private fun restoreDownloads() { + if (FeatureFlags.viewDownloads) { + components.useCases.downloadUseCases.restoreDownloads() + } + } + private fun initVisualCompletenessQueueAndQueueTasks() { val queue = components.performance.visualCompletenessQueue.queue diff --git a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt index 2cb6cc0026..d8620a3ccb 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -65,7 +65,6 @@ import mozilla.components.feature.session.SwipeRefreshFeature import mozilla.components.feature.session.behavior.EngineViewBottomBehavior import mozilla.components.feature.sitepermissions.SitePermissions import mozilla.components.feature.sitepermissions.SitePermissionsFeature -import mozilla.components.feature.sitepermissions.SitePermissionsRules import mozilla.components.lib.state.ext.flowScoped import mozilla.components.service.sync.logins.DefaultLoginValidationDelegate import mozilla.components.support.base.feature.PermissionsFeature @@ -82,6 +81,7 @@ import org.mozilla.fenix.OnBackLongPressedListener import org.mozilla.fenix.R import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.readermode.DefaultReaderModeController +import org.mozilla.fenix.components.Components import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.FindInPageIntegration import org.mozilla.fenix.components.StoreProvider @@ -92,6 +92,7 @@ import org.mozilla.fenix.components.toolbar.BrowserInteractor import org.mozilla.fenix.components.toolbar.BrowserToolbarView import org.mozilla.fenix.components.toolbar.BrowserToolbarViewInteractor import org.mozilla.fenix.components.toolbar.DefaultBrowserToolbarController +import org.mozilla.fenix.components.toolbar.DefaultBrowserToolbarMenuController import org.mozilla.fenix.components.toolbar.SwipeRefreshScrollingViewBehavior import org.mozilla.fenix.components.toolbar.ToolbarIntegration import org.mozilla.fenix.components.toolbar.ToolbarPosition @@ -123,8 +124,10 @@ import java.lang.ref.WeakReference @Suppress("TooManyFunctions", "LargeClass") abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, SessionManager.Observer, OnBackLongPressedListener, AccessibilityManager.AccessibilityStateChangeListener { + private lateinit var browserFragmentStore: BrowserFragmentStore private lateinit var browserAnimator: BrowserAnimator + private lateinit var components: Components private var _browserInteractor: BrowserToolbarViewInteractor? = null protected val browserInteractor: BrowserToolbarViewInteractor @@ -170,8 +173,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session container: ViewGroup?, savedInstanceState: Bundle? ): View { - require(arguments != null) - customTabSessionId = arguments?.getString(EXTRA_SESSION_ID) + customTabSessionId = requireArguments().getString(EXTRA_SESSION_ID) // Diagnostic breadcrumb for "Display already aquired" crash: // https://github.com/mozilla-mobile/android-components/issues/7960 @@ -193,6 +195,8 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session ) } + components = requireComponents + return view } @@ -207,6 +211,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session val context = requireContext() val sessionManager = context.components.core.sessionManager val store = context.components.core.store + val activity = requireActivity() as HomeActivity val toolbarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height) @@ -215,6 +220,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session engineView = WeakReference(engineView), swipeRefresh = WeakReference(swipeRefresh), viewLifecycleScope = WeakReference(viewLifecycleOwner.lifecycleScope), + settings = context.components.settings, firstContentfulHappened = ::didFirstContentfulHappen ).apply { beginAnimateInIfNecessary() @@ -226,25 +232,20 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session putExtra(HomeActivity.OPEN_TO_BROWSER, true) } + val readerMenuController = DefaultReaderModeController( + readerViewFeature, + view.readerViewControlsBar, + isPrivate = activity.browsingModeManager.mode.isPrivate + ) val browserToolbarController = DefaultBrowserToolbarController( - activity = requireActivity() as HomeActivity, + activity = activity, navController = findNavController(), - readerModeController = DefaultReaderModeController( - readerViewFeature, - view.readerViewControlsBar, - isPrivate = (activity as HomeActivity).browsingModeManager.mode.isPrivate - ), + metrics = requireComponents.analytics.metrics, + readerModeController = readerMenuController, sessionManager = requireComponents.core.sessionManager, - sessionFeature = sessionFeature, - findInPageLauncher = { findInPageIntegration.withFeature { it.launch() } }, engineView = engineView, - swipeRefresh = swipeRefresh, browserAnimator = browserAnimator, customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) }, - openInFenixIntent = openInFenixIntent, - bookmarkTapped = { viewLifecycleOwner.lifecycleScope.launch { bookmarkTapped(it) } }, - scope = viewLifecycleOwner.lifecycleScope, - tabCollectionStorage = requireComponents.core.tabCollectionStorage, onTabCounterClicked = { thumbnailsFeature.get()?.requestScreenshot() findNavController().nav( @@ -279,9 +280,27 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session ) } ) + val browserToolbarMenuController = DefaultBrowserToolbarMenuController( + activity = activity, + navController = findNavController(), + metrics = requireComponents.analytics.metrics, + settings = context.settings(), + readerModeController = readerMenuController, + sessionManager = requireComponents.core.sessionManager, + sessionFeature = sessionFeature, + findInPageLauncher = { findInPageIntegration.withFeature { it.launch() } }, + swipeRefresh = swipeRefresh, + browserAnimator = browserAnimator, + customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) }, + openInFenixIntent = openInFenixIntent, + bookmarkTapped = { viewLifecycleOwner.lifecycleScope.launch { bookmarkTapped(it) } }, + scope = viewLifecycleOwner.lifecycleScope, + tabCollectionStorage = requireComponents.core.tabCollectionStorage + ) _browserInteractor = BrowserInteractor( - browserToolbarController = browserToolbarController + browserToolbarController, + browserToolbarMenuController ) _browserToolbarView = BrowserToolbarView( @@ -573,9 +592,9 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session context.settings().setSitePermissionSettingListener(viewLifecycleOwner) { // If the user connects to WIFI while on the BrowserFragment, this will update the // SitePermissionsRules (specifically autoplay) accordingly - assignSitePermissionsRules(context) + assignSitePermissionsRules() } - assignSitePermissionsRules(context) + assignSitePermissionsRules() fullScreenFeature.set( feature = FullScreenFeature( @@ -716,7 +735,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session return } - val onTryAgain: (Long) -> Unit = { + val onTryAgain: (String) -> Unit = { savedDownloadState.first?.let { dlState -> store.dispatch( ContentAction.UpdateDownloadAction( @@ -927,15 +946,13 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session * Returns the layout [android.view.Gravity] for the quick settings and ETP dialog. */ protected fun getAppropriateLayoutGravity(): Int = - context?.settings()?.toolbarPosition?.androidGravity ?: Gravity.BOTTOM + components.settings.toolbarPosition.androidGravity /** * Updates the site permissions rules based on user settings. */ - private fun assignSitePermissionsRules(context: Context) { - val settings = context.settings() - - val rules: SitePermissionsRules = settings.getSitePermissionsCustomSettingsRules() + private fun assignSitePermissionsRules() { + val rules = components.settings.getSitePermissionsCustomSettingsRules() sitePermissionsFeature.withFeature { it.sitePermissionsRules = rules @@ -981,7 +998,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session * Returns the current session. */ protected fun getSessionById(): Session? { - val sessionManager = context?.components?.core?.sessionManager ?: return null + val sessionManager = components.core.sessionManager val localCustomTabId = customTabSessionId return if (localCustomTabId != null) { sessionManager.findSessionById(localCustomTabId) @@ -1092,10 +1109,12 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session } private fun didFirstContentfulHappen() = - if (!requireContext().settings().waitToShowPageUntilFirstPaint) true else - context?.components?.core?.store?.state?.findTabOrCustomTabOrSelectedTab( - customTabSessionId - )?.content?.firstContentfulPaint ?: false + if (components.settings.waitToShowPageUntilFirstPaint) { + val tab = components.core.store.state.findTabOrCustomTabOrSelectedTab(customTabSessionId) + tab?.content?.firstContentfulPaint ?: false + } else { + true + } /* * Dereference these views when the fragment view is destroyed to prevent memory leaks diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserAnimator.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserAnimator.kt index 559e594cf7..fcaaadde1d 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserAnimator.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserAnimator.kt @@ -18,6 +18,7 @@ import mozilla.components.concept.engine.EngineView import org.mozilla.fenix.R import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.utils.Settings import java.lang.ref.WeakReference /** @@ -29,6 +30,7 @@ class BrowserAnimator( private val engineView: WeakReference, private val swipeRefresh: WeakReference, private val viewLifecycleScope: WeakReference, + private val settings: Settings, private val firstContentfulHappened: () -> Boolean ) { @@ -39,7 +41,7 @@ class BrowserAnimator( get() = swipeRefresh.get() fun beginAnimateInIfNecessary() { - if (unwrappedSwipeRefresh?.context?.settings()?.waitToShowPageUntilFirstPaint == true) { + if (settings.waitToShowPageUntilFirstPaint) { if (firstContentfulHappened()) { viewLifecycleScope.get()?.launch { delay(ANIMATION_DELAY) diff --git a/app/src/main/java/org/mozilla/fenix/components/ReviewPromptController.kt b/app/src/main/java/org/mozilla/fenix/components/ReviewPromptController.kt index b7f81f3618..19b1f6ec3a 100644 --- a/app/src/main/java/org/mozilla/fenix/components/ReviewPromptController.kt +++ b/app/src/main/java/org/mozilla/fenix/components/ReviewPromptController.kt @@ -7,8 +7,6 @@ package org.mozilla.fenix.components import android.app.Activity import android.content.Context import androidx.annotation.VisibleForTesting -import com.google.android.play.core.ktx.launchReview -import com.google.android.play.core.ktx.requestReview import com.google.android.play.core.review.ReviewManagerFactory import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.withContext @@ -46,12 +44,16 @@ class ReviewPromptController( private val context: Context, private val reviewSettings: ReviewSettings, private val timeNowInMillis: () -> Long = { System.currentTimeMillis() }, - private val tryPromptReview: suspend (Activity) -> Unit = { + private val tryPromptReview: suspend (Activity) -> Unit = { activity -> val manager = ReviewManagerFactory.create(context) - val reviewInfo = manager.requestReview() + val flow = manager.requestReviewFlow() withContext(Main) { - manager.launchReview(it, reviewInfo) + flow.addOnCompleteListener { + if (it.isSuccessful) { + manager.launchReviewFlow(activity, it.result) + } + } } } ) { diff --git a/app/src/main/java/org/mozilla/fenix/components/settings/CounterPreference.kt b/app/src/main/java/org/mozilla/fenix/components/settings/CounterPreference.kt new file mode 100644 index 0000000000..c4722cb2fe --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/components/settings/CounterPreference.kt @@ -0,0 +1,31 @@ +/* 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.components.settings + +import androidx.core.content.edit +import mozilla.components.support.ktx.android.content.PreferencesHolder + +class CounterPreference( + private val holder: PreferencesHolder, + private val key: String, + private val maxCount: Int +) { + + val value get() = holder.preferences.getInt(key, 0) + + fun underMaxCount() = value < maxCount + + fun increment() { + holder.preferences.edit { + putInt(key, value + 1) + } + } +} + +/** + * Property delegate for getting and an int shared preference and incrementing it. + */ +fun PreferencesHolder.counterPreference(key: String, maxCount: Int = -1) = + CounterPreference(this, key, maxCount) diff --git a/app/src/main/java/org/mozilla/fenix/components/settings/FeatureFlagPreference.kt b/app/src/main/java/org/mozilla/fenix/components/settings/FeatureFlagPreference.kt new file mode 100644 index 0000000000..0df122c5f4 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/components/settings/FeatureFlagPreference.kt @@ -0,0 +1,25 @@ +/* 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.components.settings + +import mozilla.components.support.ktx.android.content.PreferencesHolder +import mozilla.components.support.ktx.android.content.booleanPreference +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +private class DummyProperty : ReadWriteProperty { + override fun getValue(thisRef: PreferencesHolder, property: KProperty<*>) = false + override fun setValue(thisRef: PreferencesHolder, property: KProperty<*>, value: Boolean) = Unit +} + +/** + * Property delegate for getting and setting a boolean shared preference gated by a feature flag. + */ +fun featureFlagPreference(key: String, default: Boolean, featureFlag: Boolean) = + if (featureFlag) { + booleanPreference(key, default) + } else { + DummyProperty() + } diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserInteractor.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserInteractor.kt index ff37eba46b..ba8f71b35d 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserInteractor.kt @@ -5,14 +5,15 @@ package org.mozilla.fenix.components.toolbar open class BrowserInteractor( - private val browserToolbarController: BrowserToolbarController + private val browserToolbarController: BrowserToolbarController, + private val menuController: BrowserToolbarMenuController ) : BrowserToolbarViewInteractor { override fun onTabCounterClicked() { browserToolbarController.handleTabCounterClick() } - override fun onTabCounterMenuItemTapped(item: TabCounterMenuItem) { + override fun onTabCounterMenuItemTapped(item: TabCounterMenu.Item) { browserToolbarController.handleTabCounterItemInteraction(item) } @@ -29,7 +30,7 @@ open class BrowserInteractor( } override fun onBrowserToolbarMenuItemTapped(item: ToolbarMenu.Item) { - browserToolbarController.handleToolbarItemInteraction(item) + menuController.handleToolbarItemInteraction(item) } override fun onScrolled(offset: Int) { 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 9e0896ec6c..86f32927cb 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 @@ -4,46 +4,24 @@ package org.mozilla.fenix.components.toolbar -import android.content.Intent -import androidx.annotation.VisibleForTesting import androidx.navigation.NavController -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout -import com.google.android.material.snackbar.Snackbar -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.MainScope -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 -import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.support.ktx.kotlin.isUrl import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.HomeActivity -import org.mozilla.fenix.NavGraphDirections import org.mozilla.fenix.R import org.mozilla.fenix.browser.BrowserAnimator import org.mozilla.fenix.browser.BrowserAnimator.Companion.getToolbarNavOptions import org.mozilla.fenix.browser.BrowserFragmentDirections import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.readermode.ReaderModeController -import org.mozilla.fenix.collections.SaveCollectionStep -import org.mozilla.fenix.components.FenixSnackbar -import org.mozilla.fenix.components.TabCollectionStorage import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.getRootView import org.mozilla.fenix.ext.nav -import org.mozilla.fenix.ext.navigateSafe import org.mozilla.fenix.ext.sessionsOfType -import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit -import org.mozilla.fenix.utils.Do /** * An interface that handles the view manipulation of the BrowserToolbar, triggered by the Interactor @@ -52,52 +30,38 @@ interface BrowserToolbarController { fun handleScroll(offset: Int) fun handleToolbarPaste(text: String) fun handleToolbarPasteAndGo(text: String) - fun handleToolbarItemInteraction(item: ToolbarMenu.Item) fun handleToolbarClick() fun handleTabCounterClick() - fun handleTabCounterItemInteraction(item: TabCounterMenuItem) + fun handleTabCounterItemInteraction(item: TabCounterMenu.Item) fun handleReaderModePressed(enabled: Boolean) } -@Suppress("LargeClass", "TooManyFunctions") class DefaultBrowserToolbarController( private val activity: HomeActivity, private val navController: NavController, + private val metrics: MetricController, private val readerModeController: ReaderModeController, - private val sessionFeature: ViewBoundFeatureWrapper, private val sessionManager: SessionManager, - private val findInPageLauncher: () -> Unit, private val engineView: EngineView, private val browserAnimator: BrowserAnimator, - private val swipeRefresh: SwipeRefreshLayout, private val customTabSession: Session?, - private val openInFenixIntent: Intent, - private val bookmarkTapped: (Session) -> Unit, - private val scope: CoroutineScope, - private val tabCollectionStorage: TabCollectionStorage, + private val useNewSearchExperience: Boolean = FeatureFlags.newSearchExperience, private val onTabCounterClicked: () -> Unit, private val onCloseTab: (Session) -> Unit ) : BrowserToolbarController { - private val useNewSearchExperience - get() = FeatureFlags.newSearchExperience - private val currentSession - get() = customTabSession ?: activity.components.core.sessionManager.selectedSession - - // We hold onto a reference of the inner scope so that we can override this with the - // TestCoroutineScope to ensure sequential execution. If we didn't have this, our tests - // would fail intermittently due to the async nature of coroutine scheduling. - @VisibleForTesting - internal var ioScope: CoroutineScope = CoroutineScope(Dispatchers.IO) + get() = customTabSession ?: sessionManager.selectedSession override fun handleToolbarPaste(text: String) { if (useNewSearchExperience) { navController.nav( - R.id.browserFragment, BrowserFragmentDirections.actionGlobalSearchDialog( + R.id.browserFragment, + BrowserFragmentDirections.actionGlobalSearchDialog( sessionId = currentSession?.id, pastedText = text - ), getToolbarNavOptions(activity) + ), + getToolbarNavOptions(activity) ) } else { browserAnimator.captureEngineViewAndDrawStatically { @@ -128,15 +92,15 @@ class DefaultBrowserToolbarController( } override fun handleToolbarClick() { - activity.components.analytics.metrics.track( - Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER) - ) + metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)) if (useNewSearchExperience) { navController.nav( - R.id.browserFragment, BrowserFragmentDirections.actionGlobalSearchDialog( + R.id.browserFragment, + BrowserFragmentDirections.actionGlobalSearchDialog( currentSession?.id - ), getToolbarNavOptions(activity) + ), + getToolbarNavOptions(activity) ) } else { browserAnimator.captureEngineViewAndDrawStatically { @@ -158,16 +122,16 @@ class DefaultBrowserToolbarController( override fun handleReaderModePressed(enabled: Boolean) { if (enabled) { readerModeController.showReaderView() - activity.components.analytics.metrics.track(Event.ReaderModeOpened) + metrics.track(Event.ReaderModeOpened) } else { readerModeController.hideReaderView() - activity.components.analytics.metrics.track(Event.ReaderModeClosed) + metrics.track(Event.ReaderModeClosed) } } - override fun handleTabCounterItemInteraction(item: TabCounterMenuItem) { + override fun handleTabCounterItemInteraction(item: TabCounterMenu.Item) { when (item) { - is TabCounterMenuItem.CloseTab -> { + is TabCounterMenu.Item.CloseTab -> { sessionManager.selectedSession?.let { // When closing the last tab we must show the undo snackbar in the home fragment if (sessionManager.sessionsOfType(it.private).count() == 1) { @@ -184,8 +148,8 @@ class DefaultBrowserToolbarController( } } } - is TabCounterMenuItem.NewTab -> { - activity.browsingModeManager.mode = BrowsingMode.fromBoolean(item.isPrivate) + is TabCounterMenu.Item.NewTab -> { + activity.browsingModeManager.mode = item.mode navController.popBackStack(R.id.homeFragment, false) } } @@ -195,239 +159,6 @@ class DefaultBrowserToolbarController( engineView.setVerticalClipping(offset) } - @ExperimentalCoroutinesApi - @Suppress("ComplexMethod", "LongMethod") - override fun handleToolbarItemInteraction(item: ToolbarMenu.Item) { - val sessionUseCases = activity.components.useCases.sessionUseCases - trackToolbarItemInteraction(item) - - Do exhaustive when (item) { - is ToolbarMenu.Item.Back -> { - if (item.viewHistory) { - navController.navigate(R.id.action_global_tabHistoryDialogFragment) - } else { - sessionUseCases.goBack.invoke(currentSession) - } - } - is ToolbarMenu.Item.Forward -> { - if (item.viewHistory) { - navController.navigate(R.id.action_global_tabHistoryDialogFragment) - } else { - sessionUseCases.goForward.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() - navController.nav(R.id.browserFragment, directions) - } - ToolbarMenu.Item.SyncedTabs -> browserAnimator.captureEngineViewAndDrawStatically { - navController.nav( - R.id.browserFragment, - BrowserFragmentDirections.actionBrowserFragmentToSyncedTabsFragment() - ) - } - is ToolbarMenu.Item.RequestDesktop -> sessionUseCases.requestDesktopSite.invoke( - item.isChecked, - currentSession - ) - ToolbarMenu.Item.AddToTopSites -> { - scope.launch { - ioScope.launch { - currentSession?.let { - with(activity.components.useCases.topSitesUseCase) { - addPinnedSites(it.title, it.url) - } - } - }.join() - - FenixSnackbar.make( - view = swipeRefresh, - duration = Snackbar.LENGTH_SHORT, - isDisplayedWithBrowserToolbar = true - ) - .setText( - swipeRefresh.context.getString(R.string.snackbar_added_to_top_sites) - ) - .show() - } - } - ToolbarMenu.Item.AddToHomeScreen, ToolbarMenu.Item.InstallToHomeScreen -> { - activity.settings().installPwaOpened = true - MainScope().launch { - with(activity.components.useCases.webAppUseCases) { - if (isInstallable()) { - addToHomescreen() - } else { - val directions = - BrowserFragmentDirections.actionBrowserFragmentToCreateShortcutFragment() - navController.navigateSafe(R.id.browserFragment, directions) - } - } - } - } - ToolbarMenu.Item.Share -> { - val directions = NavGraphDirections.actionGlobalShareFragment( - data = arrayOf( - ShareData( - url = currentSession?.url, - title = currentSession?.title - ) - ), - showPage = true - ) - navController.navigate(directions) - } - - ToolbarMenu.Item.FindInPage -> { - findInPageLauncher() - activity.components.analytics.metrics.track(Event.FindInPageOpened) - } - - ToolbarMenu.Item.AddonsManager -> browserAnimator.captureEngineViewAndDrawStatically { - navController.nav( - R.id.browserFragment, - BrowserFragmentDirections.actionGlobalAddonsManagementFragment() - ) - } - ToolbarMenu.Item.SaveToCollection -> { - activity.components.analytics.metrics - .track(Event.CollectionSaveButtonPressed(TELEMETRY_BROWSER_IDENTIFIER)) - - currentSession?.let { currentSession -> - val directions = - BrowserFragmentDirections.actionGlobalCollectionCreationFragment( - tabIds = arrayOf(currentSession.id), - selectedTabIds = arrayOf(currentSession.id), - saveCollectionStep = if (tabCollectionStorage.cachedTabCollections.isEmpty()) { - SaveCollectionStep.NameCollection - } else { - SaveCollectionStep.SelectCollection - } - ) - navController.nav(R.id.browserFragment, directions) - } - } - ToolbarMenu.Item.OpenInFenix -> { - // Stop the SessionFeature from updating the EngineView and let it release the session - // from the EngineView so that it can immediately be rendered by a different view once - // we switch to the actual browser. - sessionFeature.get()?.release() - - // Strip the CustomTabConfig to turn this Session into a regular tab and then select it - customTabSession!!.customTabConfig = null - sessionManager.select(customTabSession) - - // Switch to the actual browser which should now display our new selected session - activity.startActivity(openInFenixIntent) - - // Close this activity since it is no longer displaying any session - activity.finish() - } - ToolbarMenu.Item.Quit -> { - // We need to show the snackbar while the browsing data is deleting (if "Delete - // browsing data on quit" is activated). After the deletion is over, the snackbar - // is dismissed. - val snackbar: FenixSnackbar? = activity.getRootView()?.let { v -> - FenixSnackbar.make( - view = v, - duration = Snackbar.LENGTH_LONG, - isDisplayedWithBrowserToolbar = true - ) - .setText(v.context.getString(R.string.deleting_browsing_data_in_progress)) - } - - deleteAndQuit(activity, scope, snackbar) - } - ToolbarMenu.Item.ReaderModeAppearance -> { - readerModeController.showControls() - activity.components.analytics.metrics.track(Event.ReaderModeAppearanceOpened) - } - ToolbarMenu.Item.OpenInApp -> { - activity.settings().openInAppOpened = true - - val appLinksUseCases = - activity.components.useCases.appLinksUseCases - val getRedirect = appLinksUseCases.appLinkRedirect - currentSession?.let { - val redirect = getRedirect.invoke(it.url) - redirect.appIntent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK - appLinksUseCases.openAppLink.invoke(redirect.appIntent) - } - } - ToolbarMenu.Item.Bookmark -> { - sessionManager.selectedSession?.let { - bookmarkTapped(it) - } - } - ToolbarMenu.Item.Bookmarks -> browserAnimator.captureEngineViewAndDrawStatically { - navController.nav( - R.id.browserFragment, - BrowserFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id) - ) - } - ToolbarMenu.Item.History -> browserAnimator.captureEngineViewAndDrawStatically { - navController.nav( - R.id.browserFragment, - BrowserFragmentDirections.actionGlobalHistoryFragment() - ) - } - - ToolbarMenu.Item.Downloads -> browserAnimator.captureEngineViewAndDrawStatically { - navController.nav( - R.id.browserFragment, - BrowserFragmentDirections.actionGlobalDownloadsFragment() - ) - } - } - } - - @Suppress("ComplexMethod") - private fun trackToolbarItemInteraction(item: ToolbarMenu.Item) { - val eventItem = when (item) { - is ToolbarMenu.Item.Back -> Event.BrowserMenuItemTapped.Item.BACK - is ToolbarMenu.Item.Forward -> Event.BrowserMenuItemTapped.Item.FORWARD - 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 -> - if (item.isChecked) { - Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_ON - } else { - Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_OFF - } - - ToolbarMenu.Item.FindInPage -> Event.BrowserMenuItemTapped.Item.FIND_IN_PAGE - ToolbarMenu.Item.OpenInFenix -> Event.BrowserMenuItemTapped.Item.OPEN_IN_FENIX - ToolbarMenu.Item.Share -> Event.BrowserMenuItemTapped.Item.SHARE - ToolbarMenu.Item.SaveToCollection -> Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION - ToolbarMenu.Item.AddToTopSites -> Event.BrowserMenuItemTapped.Item.ADD_TO_TOP_SITES - ToolbarMenu.Item.AddToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN - ToolbarMenu.Item.SyncedTabs -> Event.BrowserMenuItemTapped.Item.SYNC_TABS - ToolbarMenu.Item.InstallToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN - ToolbarMenu.Item.Quit -> Event.BrowserMenuItemTapped.Item.QUIT - ToolbarMenu.Item.ReaderModeAppearance -> - Event.BrowserMenuItemTapped.Item.READER_MODE_APPEARANCE - ToolbarMenu.Item.OpenInApp -> Event.BrowserMenuItemTapped.Item.OPEN_IN_APP - ToolbarMenu.Item.Bookmark -> Event.BrowserMenuItemTapped.Item.BOOKMARK - ToolbarMenu.Item.AddonsManager -> Event.BrowserMenuItemTapped.Item.ADDONS_MANAGER - ToolbarMenu.Item.Bookmarks -> Event.BrowserMenuItemTapped.Item.BOOKMARKS - ToolbarMenu.Item.History -> Event.BrowserMenuItemTapped.Item.HISTORY - ToolbarMenu.Item.Downloads -> Event.BrowserMenuItemTapped.Item.DOWNLOADS - } - - activity.components.analytics.metrics.track(Event.BrowserMenuItemTapped(eventItem)) - } - companion object { internal const val TELEMETRY_BROWSER_IDENTIFIER = "browserMenu" } diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt new file mode 100644 index 0000000000..ddb6cb0d50 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt @@ -0,0 +1,315 @@ +/* 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.components.toolbar + +import android.content.Intent +import androidx.annotation.VisibleForTesting +import androidx.navigation.NavController +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import com.google.android.material.snackbar.Snackbar +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.MainScope +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.prompt.ShareData +import mozilla.components.feature.session.SessionFeature +import mozilla.components.support.base.feature.ViewBoundFeatureWrapper +import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.NavGraphDirections +import org.mozilla.fenix.R +import org.mozilla.fenix.browser.BrowserAnimator +import org.mozilla.fenix.browser.BrowserFragmentDirections +import org.mozilla.fenix.browser.readermode.ReaderModeController +import org.mozilla.fenix.collections.SaveCollectionStep +import org.mozilla.fenix.components.FenixSnackbar +import org.mozilla.fenix.components.TabCollectionStorage +import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.components.metrics.MetricController +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.getRootView +import org.mozilla.fenix.ext.nav +import org.mozilla.fenix.ext.navigateSafe +import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit +import org.mozilla.fenix.utils.Do +import org.mozilla.fenix.utils.Settings + +/** + * An interface that handles events from the BrowserToolbar menu, triggered by the Interactor + */ +interface BrowserToolbarMenuController { + fun handleToolbarItemInteraction(item: ToolbarMenu.Item) +} + +@Suppress("LargeClass") +class DefaultBrowserToolbarMenuController( + private val activity: HomeActivity, + private val navController: NavController, + private val metrics: MetricController, + private val settings: Settings, + private val readerModeController: ReaderModeController, + private val sessionFeature: ViewBoundFeatureWrapper, + private val sessionManager: SessionManager, + private val findInPageLauncher: () -> Unit, + private val browserAnimator: BrowserAnimator, + private val swipeRefresh: SwipeRefreshLayout, + private val customTabSession: Session?, + private val openInFenixIntent: Intent, + private val bookmarkTapped: (Session) -> Unit, + private val scope: CoroutineScope, + private val tabCollectionStorage: TabCollectionStorage +) : BrowserToolbarMenuController { + + private val currentSession + get() = customTabSession ?: sessionManager.selectedSession + + // We hold onto a reference of the inner scope so that we can override this with the + // TestCoroutineScope to ensure sequential execution. If we didn't have this, our tests + // would fail intermittently due to the async nature of coroutine scheduling. + @VisibleForTesting + internal var ioScope: CoroutineScope = CoroutineScope(Dispatchers.IO) + + @Suppress("ComplexMethod", "LongMethod") + override fun handleToolbarItemInteraction(item: ToolbarMenu.Item) { + val sessionUseCases = activity.components.useCases.sessionUseCases + trackToolbarItemInteraction(item) + + Do exhaustive when (item) { + is ToolbarMenu.Item.Back -> { + if (item.viewHistory) { + navController.navigate( + BrowserFragmentDirections.actionGlobalTabHistoryDialogFragment() + ) + } else { + sessionUseCases.goBack.invoke(currentSession) + } + } + is ToolbarMenu.Item.Forward -> { + if (item.viewHistory) { + navController.navigate( + BrowserFragmentDirections.actionGlobalTabHistoryDialogFragment() + ) + } else { + sessionUseCases.goForward.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() + navController.nav(R.id.browserFragment, directions) + } + ToolbarMenu.Item.SyncedTabs -> browserAnimator.captureEngineViewAndDrawStatically { + navController.nav( + R.id.browserFragment, + BrowserFragmentDirections.actionBrowserFragmentToSyncedTabsFragment() + ) + } + is ToolbarMenu.Item.RequestDesktop -> sessionUseCases.requestDesktopSite.invoke( + item.isChecked, + currentSession + ) + ToolbarMenu.Item.AddToTopSites -> { + scope.launch { + ioScope.launch { + currentSession?.let { + with(activity.components.useCases.topSitesUseCase) { + addPinnedSites(it.title, it.url) + } + } + }.join() + + FenixSnackbar.make( + view = swipeRefresh, + duration = Snackbar.LENGTH_SHORT, + isDisplayedWithBrowserToolbar = true + ) + .setText( + swipeRefresh.context.getString(R.string.snackbar_added_to_top_sites) + ) + .show() + } + } + ToolbarMenu.Item.AddToHomeScreen, ToolbarMenu.Item.InstallToHomeScreen -> { + settings.installPwaOpened = true + MainScope().launch { + with(activity.components.useCases.webAppUseCases) { + if (isInstallable()) { + addToHomescreen() + } else { + val directions = + BrowserFragmentDirections.actionBrowserFragmentToCreateShortcutFragment() + navController.navigateSafe(R.id.browserFragment, directions) + } + } + } + } + ToolbarMenu.Item.Share -> { + val directions = NavGraphDirections.actionGlobalShareFragment( + data = arrayOf( + ShareData( + url = currentSession?.url, + title = currentSession?.title + ) + ), + showPage = true + ) + navController.navigate(directions) + } + + ToolbarMenu.Item.FindInPage -> { + findInPageLauncher() + metrics.track(Event.FindInPageOpened) + } + + ToolbarMenu.Item.AddonsManager -> browserAnimator.captureEngineViewAndDrawStatically { + navController.nav( + R.id.browserFragment, + BrowserFragmentDirections.actionGlobalAddonsManagementFragment() + ) + } + ToolbarMenu.Item.SaveToCollection -> { + metrics + .track(Event.CollectionSaveButtonPressed(TELEMETRY_BROWSER_IDENTIFIER)) + + currentSession?.let { currentSession -> + val directions = + BrowserFragmentDirections.actionGlobalCollectionCreationFragment( + tabIds = arrayOf(currentSession.id), + selectedTabIds = arrayOf(currentSession.id), + saveCollectionStep = if (tabCollectionStorage.cachedTabCollections.isEmpty()) { + SaveCollectionStep.NameCollection + } else { + SaveCollectionStep.SelectCollection + } + ) + navController.nav(R.id.browserFragment, directions) + } + } + ToolbarMenu.Item.OpenInFenix -> { + // Stop the SessionFeature from updating the EngineView and let it release the session + // from the EngineView so that it can immediately be rendered by a different view once + // we switch to the actual browser. + sessionFeature.get()?.release() + + // Strip the CustomTabConfig to turn this Session into a regular tab and then select it + customTabSession!!.customTabConfig = null + sessionManager.select(customTabSession) + + // Switch to the actual browser which should now display our new selected session + activity.startActivity(openInFenixIntent) + + // Close this activity since it is no longer displaying any session + activity.finish() + } + ToolbarMenu.Item.Quit -> { + // We need to show the snackbar while the browsing data is deleting (if "Delete + // browsing data on quit" is activated). After the deletion is over, the snackbar + // is dismissed. + val snackbar: FenixSnackbar? = activity.getRootView()?.let { v -> + FenixSnackbar.make( + view = v, + duration = Snackbar.LENGTH_LONG, + isDisplayedWithBrowserToolbar = true + ) + .setText(v.context.getString(R.string.deleting_browsing_data_in_progress)) + } + + deleteAndQuit(activity, scope, snackbar) + } + ToolbarMenu.Item.ReaderModeAppearance -> { + readerModeController.showControls() + metrics.track(Event.ReaderModeAppearanceOpened) + } + ToolbarMenu.Item.OpenInApp -> { + settings.openInAppOpened = true + + val appLinksUseCases = activity.components.useCases.appLinksUseCases + val getRedirect = appLinksUseCases.appLinkRedirect + currentSession?.let { + val redirect = getRedirect.invoke(it.url) + redirect.appIntent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK + appLinksUseCases.openAppLink.invoke(redirect.appIntent) + } + } + ToolbarMenu.Item.Bookmark -> { + sessionManager.selectedSession?.let { + bookmarkTapped(it) + } + } + ToolbarMenu.Item.Bookmarks -> browserAnimator.captureEngineViewAndDrawStatically { + navController.nav( + R.id.browserFragment, + BrowserFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id) + ) + } + ToolbarMenu.Item.History -> browserAnimator.captureEngineViewAndDrawStatically { + navController.nav( + R.id.browserFragment, + BrowserFragmentDirections.actionGlobalHistoryFragment() + ) + } + + ToolbarMenu.Item.Downloads -> browserAnimator.captureEngineViewAndDrawStatically { + navController.nav( + R.id.browserFragment, + BrowserFragmentDirections.actionGlobalDownloadsFragment() + ) + } + } + } + + @Suppress("ComplexMethod") + private fun trackToolbarItemInteraction(item: ToolbarMenu.Item) { + val eventItem = when (item) { + is ToolbarMenu.Item.Back -> Event.BrowserMenuItemTapped.Item.BACK + is ToolbarMenu.Item.Forward -> Event.BrowserMenuItemTapped.Item.FORWARD + 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 -> + if (item.isChecked) { + Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_ON + } else { + Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_OFF + } + + ToolbarMenu.Item.FindInPage -> Event.BrowserMenuItemTapped.Item.FIND_IN_PAGE + ToolbarMenu.Item.OpenInFenix -> Event.BrowserMenuItemTapped.Item.OPEN_IN_FENIX + ToolbarMenu.Item.Share -> Event.BrowserMenuItemTapped.Item.SHARE + ToolbarMenu.Item.SaveToCollection -> Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION + ToolbarMenu.Item.AddToTopSites -> Event.BrowserMenuItemTapped.Item.ADD_TO_TOP_SITES + ToolbarMenu.Item.AddToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN + ToolbarMenu.Item.SyncedTabs -> Event.BrowserMenuItemTapped.Item.SYNC_TABS + ToolbarMenu.Item.InstallToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN + ToolbarMenu.Item.Quit -> Event.BrowserMenuItemTapped.Item.QUIT + ToolbarMenu.Item.ReaderModeAppearance -> + Event.BrowserMenuItemTapped.Item.READER_MODE_APPEARANCE + ToolbarMenu.Item.OpenInApp -> Event.BrowserMenuItemTapped.Item.OPEN_IN_APP + ToolbarMenu.Item.Bookmark -> Event.BrowserMenuItemTapped.Item.BOOKMARK + ToolbarMenu.Item.AddonsManager -> Event.BrowserMenuItemTapped.Item.ADDONS_MANAGER + ToolbarMenu.Item.Bookmarks -> Event.BrowserMenuItemTapped.Item.BOOKMARKS + ToolbarMenu.Item.History -> Event.BrowserMenuItemTapped.Item.HISTORY + ToolbarMenu.Item.Downloads -> Event.BrowserMenuItemTapped.Item.DOWNLOADS + } + + metrics.track(Event.BrowserMenuItemTapped(eventItem)) + } + + companion object { + internal const val TELEMETRY_BROWSER_IDENTIFIER = "browserMenu" + } +} 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 8801075662..510b067f9c 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 @@ -44,7 +44,7 @@ interface BrowserToolbarViewInteractor { fun onBrowserToolbarClicked() fun onBrowserToolbarMenuItemTapped(item: ToolbarMenu.Item) fun onTabCounterClicked() - fun onTabCounterMenuItemTapped(item: TabCounterMenuItem) + fun onTabCounterMenuItemTapped(item: TabCounterMenu.Item) fun onScrolled(offset: Int) fun onReaderModePressed(enabled: Boolean) } diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounterMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounterMenu.kt new file mode 100644 index 0000000000..6076dbc54e --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounterMenu.kt @@ -0,0 +1,100 @@ +/* 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.components.toolbar + +import android.content.Context +import androidx.annotation.VisibleForTesting +import mozilla.components.browser.menu2.BrowserMenuController +import mozilla.components.concept.menu.MenuController +import mozilla.components.concept.menu.candidate.DividerMenuCandidate +import mozilla.components.concept.menu.candidate.DrawableMenuIcon +import mozilla.components.concept.menu.candidate.MenuCandidate +import mozilla.components.concept.menu.candidate.TextMenuCandidate +import mozilla.components.concept.menu.candidate.TextStyle +import mozilla.components.support.ktx.android.content.getColorFromAttr +import org.mozilla.fenix.R +import org.mozilla.fenix.browser.browsingmode.BrowsingMode +import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.components.metrics.MetricController + +class TabCounterMenu( + context: Context, + private val metrics: MetricController, + private val onItemTapped: (Item) -> Unit +) { + + sealed class Item { + object CloseTab : Item() + data class NewTab(val mode: BrowsingMode) : Item() + } + + val menuController: MenuController by lazy { BrowserMenuController() } + + private val newTabItem: TextMenuCandidate + private val newPrivateTabItem: TextMenuCandidate + private val closeTabItem: TextMenuCandidate + + init { + val primaryTextColor = context.getColorFromAttr(R.attr.primaryText) + val textStyle = TextStyle(color = primaryTextColor) + + newTabItem = TextMenuCandidate( + text = context.getString(R.string.browser_menu_new_tab), + start = DrawableMenuIcon( + context, + R.drawable.ic_new, + tint = primaryTextColor + ), + textStyle = textStyle + ) { + metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_TAB)) + onItemTapped(Item.NewTab(BrowsingMode.Normal)) + } + + newPrivateTabItem = TextMenuCandidate( + text = context.getString(R.string.home_screen_shortcut_open_new_private_tab_2), + start = DrawableMenuIcon( + context, + R.drawable.ic_private_browsing, + tint = primaryTextColor + ), + textStyle = textStyle + ) { + metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_PRIVATE_TAB)) + onItemTapped(Item.NewTab(BrowsingMode.Private)) + } + + closeTabItem = TextMenuCandidate( + text = context.getString(R.string.close_tab), + start = DrawableMenuIcon( + context, + R.drawable.ic_close, + tint = primaryTextColor + ), + textStyle = textStyle + ) { + metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.CLOSE_TAB)) + onItemTapped(Item.CloseTab) + } + } + + @VisibleForTesting + internal fun menuItems(showOnly: BrowsingMode?): List { + return when (showOnly) { + BrowsingMode.Normal -> listOf(newTabItem) + BrowsingMode.Private -> listOf(newPrivateTabItem) + null -> listOf( + newTabItem, + newPrivateTabItem, + DividerMenuCandidate(), + closeTabItem + ) + } + } + + fun updateMenu(showOnly: BrowsingMode? = null) { + menuController.submitList(menuItems(showOnly)) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounterToolbarButton.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounterToolbarButton.kt index e841d7c699..0b743e744d 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounterToolbarButton.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounterToolbarButton.kt @@ -4,27 +4,18 @@ package org.mozilla.fenix.components.toolbar -import android.content.Context -import android.util.TypedValue import android.view.View import android.view.ViewGroup import androidx.lifecycle.LifecycleOwner import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.map -import mozilla.components.browser.menu.BrowserMenu -import mozilla.components.browser.menu.BrowserMenuBuilder -import mozilla.components.browser.menu.item.BrowserMenuDivider -import mozilla.components.browser.menu.item.BrowserMenuImageText import mozilla.components.browser.state.selector.getNormalOrPrivateTabs import mozilla.components.concept.toolbar.Toolbar import mozilla.components.lib.state.ext.flowScoped +import mozilla.components.support.ktx.android.content.res.resolveAttribute import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged -import org.mozilla.fenix.R -import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.theme.ThemeManager import java.lang.ref.WeakReference /** @@ -34,18 +25,25 @@ import java.lang.ref.WeakReference class TabCounterToolbarButton( private val lifecycleOwner: LifecycleOwner, private val isPrivate: Boolean, - private val onItemTapped: (TabCounterMenuItem) -> Unit = {}, + private val onItemTapped: (TabCounterMenu.Item) -> Unit = {}, private val showTabs: () -> Unit ) : Toolbar.Action { + private var reference: WeakReference = WeakReference(null) override fun createView(parent: ViewGroup): View { - parent.context.components.core.store.flowScoped(lifecycleOwner) { flow -> + val store = parent.context.components.core.store + val metrics = parent.context.components.analytics.metrics + + store.flowScoped(lifecycleOwner) { flow -> flow.map { state -> state.getNormalOrPrivateTabs(isPrivate).size } .ifChanged() .collect { tabs -> updateCount(tabs) } } + val menu = TabCounterMenu(parent.context, metrics, onItemTapped) + menu.updateMenu() + val view = TabCounter(parent.context).apply { reference = WeakReference(this) setOnClickListener { @@ -53,28 +51,23 @@ class TabCounterToolbarButton( } setOnLongClickListener { - getTabContextMenu(it.context).show(it) + menu.menuController.show(anchor = it) true } addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View?) { - setCount(context.components.core.store.state.getNormalOrPrivateTabs(isPrivate).size) + setCount(store.state.getNormalOrPrivateTabs(isPrivate).size) } - override fun onViewDetachedFromWindow(v: View?) { /* no-op */ - } + override fun onViewDetachedFromWindow(v: View?) { /* no-op */ } }) } // Set selectableItemBackgroundBorderless - val outValue = TypedValue() - parent.context.theme.resolveAttribute( - android.R.attr.selectableItemBackgroundBorderless, - outValue, - true - ) - view.setBackgroundResource(outValue.resourceId) + view.setBackgroundResource(parent.context.theme.resolveAttribute( + android.R.attr.selectableItemBackgroundBorderless + )) return view } @@ -83,46 +76,4 @@ class TabCounterToolbarButton( private fun updateCount(count: Int) { reference.get()?.setCountWithAnimation(count) } - - private fun getTabContextMenu(context: Context): BrowserMenu { - val primaryTextColor = ThemeManager.resolveAttribute(R.attr.primaryText, context) - val metrics = context.components.analytics.metrics - val menuItems = listOf( - BrowserMenuImageText( - label = context.getString(R.string.browser_menu_new_tab), - imageResource = R.drawable.ic_new, - iconTintColorResource = primaryTextColor, - textColorResource = primaryTextColor - ) { - metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_TAB)) - onItemTapped(TabCounterMenuItem.NewTab(false)) - }, - BrowserMenuImageText( - label = context.getString(R.string.home_screen_shortcut_open_new_private_tab_2), - imageResource = R.drawable.ic_private_browsing, - iconTintColorResource = primaryTextColor, - textColorResource = primaryTextColor - ) { - metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_PRIVATE_TAB)) - onItemTapped(TabCounterMenuItem.NewTab(true)) - }, - BrowserMenuDivider(), - BrowserMenuImageText( - label = context.getString(R.string.close_tab), - imageResource = R.drawable.ic_close, - iconTintColorResource = primaryTextColor, - textColorResource = primaryTextColor - ) { - metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.CLOSE_TAB)) - onItemTapped(TabCounterMenuItem.CloseTab) - } - ) - - return BrowserMenuBuilder( - when (context.settings().toolbarPosition) { - ToolbarPosition.BOTTOM -> menuItems.reversed() - ToolbarPosition.TOP -> menuItems - } - ).build(context) - } } diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt index ceb2c21a0d..3a96e33375 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt @@ -132,14 +132,17 @@ class DefaultToolbarIntegration( ) } - val onTabCounterMenuItemTapped = { item: TabCounterMenuItem -> - interactor.onTabCounterMenuItemTapped(item) - } - val tabsAction = - TabCounterToolbarButton(lifecycleOwner, isPrivate, onTabCounterMenuItemTapped) { + val tabsAction = TabCounterToolbarButton( + lifecycleOwner, + isPrivate, + onItemTapped = { + interactor.onTabCounterMenuItemTapped(it) + }, + showTabs = { toolbar.hideKeyboard() interactor.onTabCounterClicked() } + ) toolbar.addBrowserAction(tabsAction) val engineForSpeculativeConnects = if (!isPrivate) engine else null diff --git a/app/src/main/java/org/mozilla/fenix/downloads/DynamicDownloadDialog.kt b/app/src/main/java/org/mozilla/fenix/downloads/DynamicDownloadDialog.kt index cbf6b949c7..826a477e60 100644 --- a/app/src/main/java/org/mozilla/fenix/downloads/DynamicDownloadDialog.kt +++ b/app/src/main/java/org/mozilla/fenix/downloads/DynamicDownloadDialog.kt @@ -27,7 +27,7 @@ class DynamicDownloadDialog( private val container: ViewGroup, private val downloadState: DownloadState?, private val didFail: Boolean, - private val tryAgain: (Long) -> Unit, + private val tryAgain: (String) -> Unit, private val onCannotOpenFile: () -> Unit, private val view: View, private val toolbarHeight: Int, diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index bcacb3ea6d..d9eb1fecb7 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -52,9 +52,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import mozilla.appservices.places.BookmarkRoot -import mozilla.components.browser.menu.BrowserMenu -import mozilla.components.browser.menu.BrowserMenuBuilder -import mozilla.components.browser.menu.item.BrowserMenuImageText import mozilla.components.browser.menu.view.MenuButton import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager @@ -86,6 +83,7 @@ import org.mozilla.fenix.components.TabCollectionStorage import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.tips.FenixTipManager import org.mozilla.fenix.components.tips.providers.MigrationTipProvider +import org.mozilla.fenix.components.toolbar.TabCounterMenu import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.hideToolbar @@ -311,7 +309,7 @@ class HomeFragment : Fragment() { } } - @SuppressWarnings("LongMethod") + @Suppress("LongMethod", "ComplexMethod") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -348,8 +346,21 @@ class HomeFragment : Fragment() { } createHomeMenu(requireContext(), WeakReference(view.menuButton)) + val tabCounterMenu = TabCounterMenu( + view.context, + metrics = view.context.components.analytics.metrics + ) { + if (it is TabCounterMenu.Item.NewTab) { + (activity as HomeActivity).browsingModeManager.mode = it.mode + } + } + val inverseBrowsingMode = when ((activity as HomeActivity).browsingModeManager.mode) { + BrowsingMode.Normal -> BrowsingMode.Private + BrowsingMode.Private -> BrowsingMode.Normal + } + tabCounterMenu.updateMenu(showOnly = inverseBrowsingMode) view.tab_button.setOnLongClickListener { - createTabCounterMenu(requireContext()).show(view.tab_button) + tabCounterMenu.menuController.show(anchor = it) true } @@ -710,50 +721,6 @@ class HomeFragment : Fragment() { nav(R.id.homeFragment, directions, getToolbarNavOptions(requireContext())) } - private fun openInNormalTab(url: String) { - (activity as HomeActivity).openToBrowserAndLoad( - searchTermOrURL = url, - newTab = true, - from = BrowserDirection.FromHome - ) - } - - private fun createTabCounterMenu(context: Context): BrowserMenu { - val primaryTextColor = ThemeManager.resolveAttribute(R.attr.primaryText, context) - val isPrivate = (activity as HomeActivity).browsingModeManager.mode == BrowsingMode.Private - val menuItems = listOf( - BrowserMenuImageText( - label = context.getString( - if (isPrivate) { - R.string.browser_menu_new_tab - } else { - R.string.home_screen_shortcut_open_new_private_tab_2 - } - ), - imageResource = if (isPrivate) { - R.drawable.ic_new - } else { - R.drawable.ic_private_browsing - }, - iconTintColorResource = primaryTextColor, - textColorResource = primaryTextColor - ) { - requireComponents.analytics.metrics.track( - Event.TabCounterMenuItemTapped( - if (isPrivate) { - Event.TabCounterMenuItemTapped.Item.NEW_TAB - } else { - Event.TabCounterMenuItemTapped.Item.NEW_PRIVATE_TAB - } - ) - ) - (activity as HomeActivity).browsingModeManager.mode = - BrowsingMode.fromBoolean(!isPrivate) - } - ) - return BrowserMenuBuilder(menuItems).build(context) - } - @SuppressWarnings("ComplexMethod", "LongMethod") private fun createHomeMenu(context: Context, menuButtonView: WeakReference) = HomeMenu( diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt index 955dc0844e..01877979e4 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt @@ -113,8 +113,11 @@ class SearchFragment : Fragment(), UserInteractionHandler { searchController ) - awesomeBarView = AwesomeBarView(requireContext(), searchInteractor, - view.findViewById(R.id.awesomeBar)) + awesomeBarView = AwesomeBarView( + activity, + searchInteractor, + view.findViewById(R.id.awesomeBar) + ) setShortcutsChangedListener(CustomSearchEngineStore.PREF_FILE_SEARCH_ENGINES) setShortcutsChangedListener(FenixSearchEngineProvider.PREF_FILE_SEARCH_ENGINES) diff --git a/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt b/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt index 8ba8f3e202..d95323aa21 100644 --- a/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt +++ b/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt @@ -4,7 +4,6 @@ package org.mozilla.fenix.search.awesomebar -import android.content.Context import androidx.appcompat.content.res.AppCompatResources.getDrawable import androidx.core.graphics.BlendModeColorFilterCompat.createBlendModeColorFilterCompat import androidx.core.graphics.BlendModeCompat.SRC_IN @@ -25,7 +24,7 @@ import mozilla.components.feature.tabs.TabsUseCases import mozilla.components.support.ktx.android.content.getColorFromAttr import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R -import org.mozilla.fenix.ext.asActivity +import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.ext.components import org.mozilla.fenix.search.SearchEngineSource import org.mozilla.fenix.search.SearchFragmentState @@ -34,7 +33,7 @@ import org.mozilla.fenix.search.SearchFragmentState * View that contains and configures the BrowserAwesomeBar */ class AwesomeBarView( - private val context: Context, + private val activity: HomeActivity, val interactor: AwesomeBarInteractor, val view: BrowserAwesomeBar ) { @@ -90,21 +89,20 @@ class AwesomeBarView( init { view.itemAnimator = null - val components = context.components - val primaryTextColor = context.getColorFromAttr(R.attr.primaryText) + val components = activity.components + val primaryTextColor = activity.getColorFromAttr(R.attr.primaryText) - val draw = getDrawable(context, R.drawable.ic_link)!! - draw.colorFilter = createBlendModeColorFilterCompat(primaryTextColor, SRC_IN) - - val engineForSpeculativeConnects = - if (!isBrowsingModePrivate()) components.core.engine else null + val engineForSpeculativeConnects = when (activity.browsingModeManager.mode) { + BrowsingMode.Normal -> components.core.engine + BrowsingMode.Private -> null + } sessionProvider = SessionSuggestionProvider( - context.resources, + activity.resources, components.core.store, selectTabUseCase, components.core.icons, - getDrawable(context, R.drawable.ic_search_results_tab), + getDrawable(activity, R.drawable.ic_search_results_tab), excludeSelectedSession = true ) @@ -121,17 +119,17 @@ class AwesomeBarView( bookmarksStorage = components.core.bookmarksStorage, loadUrlUseCase = loadUrlUseCase, icons = components.core.icons, - indicatorIcon = getDrawable(context, R.drawable.ic_search_results_bookmarks), + indicatorIcon = getDrawable(activity, R.drawable.ic_search_results_bookmarks), engine = engineForSpeculativeConnects ) - val searchBitmap = getDrawable(context, R.drawable.ic_search)!!.apply { + val searchBitmap = getDrawable(activity, R.drawable.ic_search)!!.apply { colorFilter = createBlendModeColorFilterCompat(primaryTextColor, SRC_IN) }.toBitmap() defaultSearchSuggestionProvider = SearchSuggestionProvider( - context = context, + context = activity, searchEngineManager = components.search.searchEngineManager, searchUseCase = searchUseCase, fetchClient = components.core.client, @@ -146,7 +144,7 @@ class AwesomeBarView( defaultSearchActionProvider = SearchActionProvider( searchEngineGetter = suspend { - components.search.searchEngineManager.getDefaultSearchEngineAsync(context) + components.search.searchEngineManager.getDefaultSearchEngineAsync(activity) }, searchUseCase = searchUseCase, icon = searchBitmap, @@ -156,7 +154,7 @@ class AwesomeBarView( shortcutsEnginePickerProvider = ShortcutsSuggestionProvider( searchEngineProvider = components.search.provider, - context = context, + context = activity, selectShortcutEngine = interactor::onSearchShortcutEngineSelected, selectShortcutEngineSettings = interactor::onClickSearchEngineSettings ) @@ -221,7 +219,7 @@ class AwesomeBarView( providersToAdd.addAll(getSelectedSearchSuggestionProvider(state)) } - if (!isBrowsingModePrivate()) { + if (activity.browsingModeManager.mode == BrowsingMode.Normal) { providersToAdd.add(sessionProvider) } @@ -245,18 +243,13 @@ class AwesomeBarView( providersToRemove.addAll(getSelectedSearchSuggestionProvider(state)) } - if (isBrowsingModePrivate()) { + if (activity.browsingModeManager.mode == BrowsingMode.Private) { providersToRemove.add(sessionProvider) } return providersToRemove } - private fun isBrowsingModePrivate(): Boolean { - return (context.asActivity() as? HomeActivity)?.browsingModeManager?.mode?.isPrivate - ?: false - } - private fun getSelectedSearchSuggestionProvider(state: SearchFragmentState): List { return when (state.searchEngineSource) { is SearchEngineSource.Default -> listOf( @@ -278,18 +271,20 @@ class AwesomeBarView( private fun getSuggestionProviderForEngine(engine: SearchEngine): List { return searchSuggestionProviderMap.getOrPut(engine) { - val components = context.components - val primaryTextColor = context.getColorFromAttr(R.attr.primaryText) + val components = activity.components + val primaryTextColor = activity.getColorFromAttr(R.attr.primaryText) - val searchBitmap = getDrawable(context, R.drawable.ic_search)?.apply { + val searchBitmap = getDrawable(activity, R.drawable.ic_search)!!.apply { colorFilter = createBlendModeColorFilterCompat(primaryTextColor, SRC_IN) - }?.toBitmap() + }.toBitmap() - val engineForSpeculativeConnects = - if (!isBrowsingModePrivate()) components.core.engine else null + val engineForSpeculativeConnects = when (activity.browsingModeManager.mode) { + BrowsingMode.Normal -> components.core.engine + BrowsingMode.Private -> null + } val searchEngine = - components.search.provider.installedSearchEngines(context).list.find { it.name == engine.name } - ?: components.search.provider.getDefaultEngine(context) + components.search.provider.installedSearchEngines(activity).list.find { it.name == engine.name } + ?: components.search.provider.getDefaultEngine(activity) listOf( SearchActionProvider( diff --git a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt index 7d0454ba83..c67ed7b96e 100644 --- a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt @@ -99,12 +99,13 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { ): View? { val args by navArgs() val view = inflater.inflate(R.layout.fragment_search_dialog, container, false) + val activity = requireActivity() as HomeActivity requireComponents.analytics.metrics.track(Event.InteractWithSearchURLArea) store = SearchDialogFragmentStore( createInitialSearchFragmentState( - activity as HomeActivity, + activity, requireComponents, tabId = args.sessionId, pastedText = args.pastedText, @@ -114,7 +115,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { interactor = SearchDialogInteractor( SearchDialogController( - activity = requireActivity() as HomeActivity, + activity = activity, sessionManager = requireComponents.core.sessionManager, store = store, navController = findNavController(), @@ -137,7 +138,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { ).also(::addSearchButton) awesomeBarView = AwesomeBarView( - requireContext(), + activity, interactor, view.awesome_bar ) diff --git a/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt index 0d15672bd7..4fcfa280f8 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt @@ -5,14 +5,8 @@ package org.mozilla.fenix.settings import android.os.Bundle -import androidx.lifecycle.lifecycleScope -import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreference -import com.google.android.play.core.ktx.launchReview -import com.google.android.play.core.ktx.requestReview -import com.google.android.play.core.review.ReviewManagerFactory -import kotlinx.coroutines.launch import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.R import org.mozilla.fenix.ext.settings @@ -48,16 +42,5 @@ class SecretSettingsFragment : PreferenceFragmentCompat() { isChecked = context.settings().syncedTabsInTabsTray onPreferenceChangeListener = SharedPreferenceUpdater() } - - requirePreference(R.string.pref_key_temp_review_prompt).apply { - setOnPreferenceClickListener { - viewLifecycleOwner.lifecycleScope.launch { - val manager = ReviewManagerFactory.create(requireContext()) - val reviewInfo = manager.requestReview() - manager.launchReview(requireActivity(), reviewInfo) - } - true - } - } } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuit.kt b/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuit.kt index b4cf230188..6f87de29d7 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuit.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuit.kt @@ -26,6 +26,7 @@ fun deleteAndQuit(activity: Activity, coroutineScope: CoroutineScope, snackbar: activity.components.useCases.tabsUseCases.removeAllTabs, activity.components.core.historyStorage, activity.components.core.permissionStorage, + activity.components.core.icons, activity.components.core.engine, coroutineContext ) @@ -53,7 +54,7 @@ fun deleteAndQuit(activity: Activity, coroutineScope: CoroutineScope, snackbar: private suspend fun DeleteBrowsingDataController.deleteType(type: DeleteBrowsingDataOnQuitType) { when (type) { DeleteBrowsingDataOnQuitType.TABS -> deleteTabs() - DeleteBrowsingDataOnQuitType.HISTORY -> deleteHistoryAndDOMStorages() + DeleteBrowsingDataOnQuitType.HISTORY -> deleteBrowsingData() DeleteBrowsingDataOnQuitType.COOKIES -> deleteCookies() DeleteBrowsingDataOnQuitType.CACHE -> deleteCachedFiles() DeleteBrowsingDataOnQuitType.PERMISSIONS -> withContext(IO) { diff --git a/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataController.kt b/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataController.kt index b8894cc49b..575e8e9691 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataController.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataController.kt @@ -6,6 +6,7 @@ package org.mozilla.fenix.settings.deletebrowsingdata import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import mozilla.components.browser.icons.BrowserIcons import mozilla.components.concept.engine.Engine import mozilla.components.concept.storage.HistoryStorage import mozilla.components.feature.tabs.TabsUseCases @@ -15,7 +16,6 @@ import kotlin.coroutines.CoroutineContext interface DeleteBrowsingDataController { suspend fun deleteTabs() suspend fun deleteBrowsingData() - suspend fun deleteHistoryAndDOMStorages() suspend fun deleteCookies() suspend fun deleteCachedFiles() suspend fun deleteSitePermissions() @@ -25,6 +25,7 @@ class DefaultDeleteBrowsingDataController( private val removeAllTabs: TabsUseCases.RemoveAllTabsUseCase, private val historyStorage: HistoryStorage, private val permissionStorage: PermissionStorage, + private val iconsStorage: BrowserIcons, private val engine: Engine, private val coroutineContext: CoroutineContext = Dispatchers.Main ) : DeleteBrowsingDataController { @@ -36,14 +37,11 @@ class DefaultDeleteBrowsingDataController( } override suspend fun deleteBrowsingData() { - deleteHistoryAndDOMStorages() - } - - override suspend fun deleteHistoryAndDOMStorages() { withContext(coroutineContext) { engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.DOM_STORAGES)) + historyStorage.deleteEverything() + iconsStorage.clear() } - historyStorage.deleteEverything() } override suspend fun deleteCookies() { diff --git a/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt index 5513411447..36081afcc8 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt @@ -45,6 +45,7 @@ class DeleteBrowsingDataFragment : Fragment(R.layout.fragment_delete_browsing_da requireContext().components.useCases.tabsUseCases.removeAllTabs, requireContext().components.core.historyStorage, requireContext().components.core.permissionStorage, + requireContext().components.core.icons, requireContext().components.core.engine ) settings = requireContext().settings() diff --git a/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsExceptionsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsExceptionsFragment.kt index 813e24591a..f941ce29bc 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsExceptionsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsExceptionsFragment.kt @@ -118,8 +118,8 @@ class SitePermissionsExceptionsFragment : } } - override fun onClick(view: View?) { - val sitePermissions = view?.tag as SitePermissions + override fun onClick(view: View) { + val sitePermissions = view.tag as SitePermissions val directions = SitePermissionsExceptionsFragmentDirections .actionSitePermissionsToExceptionsToSitePermissionsDetails(sitePermissions) nav(R.id.sitePermissionsExceptionsFragment, directions) diff --git a/app/src/main/java/org/mozilla/fenix/utils/FeatureFlagPreference.kt b/app/src/main/java/org/mozilla/fenix/utils/FeatureFlagPreference.kt deleted file mode 100644 index d0830bd73b..0000000000 --- a/app/src/main/java/org/mozilla/fenix/utils/FeatureFlagPreference.kt +++ /dev/null @@ -1,29 +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.utils - -import mozilla.components.support.ktx.android.content.PreferencesHolder -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -fun featureFlagPreference( - key: String, - default: Boolean, - featureFlag: Boolean -): ReadWriteProperty = - FeatureFlagPreferencePreference(key, default, featureFlag) - -private class FeatureFlagPreferencePreference( - private val key: String, - private val default: Boolean, - private val featureFlag: Boolean -) : ReadWriteProperty { - - override fun getValue(thisRef: PreferencesHolder, property: KProperty<*>): Boolean = - featureFlag && thisRef.preferences.getBoolean(key, default) - - override fun setValue(thisRef: PreferencesHolder, property: KProperty<*>, value: Boolean) = - thisRef.preferences.edit().putBoolean(key, value).apply() -} diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index e8a4cb7c15..731db9f72a 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -30,6 +30,8 @@ import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.R import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.components.metrics.MozillaProductDetector +import org.mozilla.fenix.components.settings.counterPreference +import org.mozilla.fenix.components.settings.featureFlagPreference import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.getPreferenceKey @@ -50,14 +52,9 @@ private const val AUTOPLAY_USER_SETTING = "AUTOPLAY_USER_SETTING" class Settings(private val appContext: Context) : PreferencesHolder { companion object { - const val showLoginsSecureWarningSyncMaxCount = 1 - const val showLoginsSecureWarningMaxCount = 1 - const val trackingProtectionOnboardingMaximumCount = 1 - const val pwaVisitsToShowPromptMaxCount = 3 const val topSitesMaxCount = 16 const val FENIX_PREFERENCES = "fenix_preferences" - private const val showSearchWidgetCFRMaxCount = 3 private const val BLOCKED_INT = 0 private const val ASK_TO_ALLOW_INT = 1 private const val ALLOWED_INT = 2 @@ -177,38 +174,26 @@ class Settings(private val appContext: Context) : PreferencesHolder { true ) - private val activeSearchCount by intPreference( - appContext.getPreferenceKey(R.string.pref_key_search_count), - default = 0 + private val activeSearchCount = counterPreference( + appContext.getPreferenceKey(R.string.pref_key_search_count) ) - fun incrementActiveSearchCount() { - preferences.edit().putInt( - appContext.getPreferenceKey(R.string.pref_key_search_count), - activeSearchCount + 1 - ).apply() - } + fun incrementActiveSearchCount() = activeSearchCount.increment() private val isActiveSearcher: Boolean - get() = activeSearchCount > 2 + get() = activeSearchCount.value > 2 fun shouldDisplaySearchWidgetCFR(): Boolean = isActiveSearcher && - searchWidgetCFRDismissCount < showSearchWidgetCFRMaxCount && + searchWidgetCFRDismissCount.underMaxCount() && !searchWidgetInstalled && !searchWidgetCFRManuallyDismissed - private val searchWidgetCFRDisplayCount by intPreference( - appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_display_count), - default = 0 + private val searchWidgetCFRDisplayCount = counterPreference( + appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_display_count) ) - fun incrementSearchWidgetCFRDisplayed() { - preferences.edit().putInt( - appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_display_count), - searchWidgetCFRDisplayCount + 1 - ).apply() - } + fun incrementSearchWidgetCFRDisplayed() = searchWidgetCFRDisplayCount.increment() private val searchWidgetCFRManuallyDismissed by booleanPreference( appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_manually_dismissed), @@ -222,17 +207,12 @@ class Settings(private val appContext: Context) : PreferencesHolder { ).apply() } - private val searchWidgetCFRDismissCount by intPreference( + private val searchWidgetCFRDismissCount = counterPreference( appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_dismiss_count), - default = 0 + maxCount = 3 ) - fun incrementSearchWidgetCFRDismissed() { - preferences.edit().putInt( - appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_dismiss_count), - searchWidgetCFRDismissCount + 1 - ).apply() - } + fun incrementSearchWidgetCFRDismissed() = searchWidgetCFRDismissCount.increment() val isInSearchWidgetExperiment by booleanPreference( appContext.getPreferenceKey(R.string.pref_key_is_in_search_widget_experiment), @@ -298,17 +278,16 @@ class Settings(private val appContext: Context) : PreferencesHolder { val shouldShowTrackingProtectionOnboarding: Boolean get() = !isOverrideTPPopupsForPerformanceTest && - (trackingProtectionOnboardingCount < trackingProtectionOnboardingMaximumCount && + (trackingProtectionOnboardingCount.underMaxCount() && !trackingProtectionOnboardingShownThisSession) var showSecretDebugMenuThisSession = false - var showNotificationsSetting = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O val shouldShowSecurityPinWarningSync: Boolean - get() = loginsSecureWarningSyncCount < showLoginsSecureWarningSyncMaxCount + get() = loginsSecureWarningSyncCount.underMaxCount() val shouldShowSecurityPinWarning: Boolean - get() = loginsSecureWarningCount < showLoginsSecureWarningMaxCount + get() = loginsSecureWarningCount.underMaxCount() var shouldUseLightTheme by booleanPreference( appContext.getPreferenceKey(R.string.pref_key_light_theme), @@ -549,12 +528,6 @@ class Settings(private val appContext: Context) : PreferencesHolder { return touchExplorationIsEnabled || switchServiceIsEnabled } - val toolbarSettingString: String - get() = when { - shouldUseBottomToolbar -> appContext.getString(R.string.preference_bottom_toolbar) - else -> appContext.getString(R.string.preference_top_toolbar) - } - fun getDeleteDataOnQuit(type: DeleteBrowsingDataOnQuitType): Boolean = preferences.getBoolean(type.getPreferenceKey(appContext), false) @@ -576,30 +549,20 @@ class Settings(private val appContext: Context) : PreferencesHolder { ).apply() @VisibleForTesting(otherwise = PRIVATE) - internal val loginsSecureWarningSyncCount by intPreference( + internal val loginsSecureWarningSyncCount = counterPreference( appContext.getPreferenceKey(R.string.pref_key_logins_secure_warning_sync), - default = 0 + maxCount = 1 ) @VisibleForTesting(otherwise = PRIVATE) - internal val loginsSecureWarningCount by intPreference( + internal val loginsSecureWarningCount = counterPreference( appContext.getPreferenceKey(R.string.pref_key_logins_secure_warning), - default = 0 + maxCount = 1 ) - fun incrementShowLoginsSecureWarningCount() { - preferences.edit().putInt( - appContext.getPreferenceKey(R.string.pref_key_logins_secure_warning), - loginsSecureWarningCount + 1 - ).apply() - } + fun incrementShowLoginsSecureWarningCount() = loginsSecureWarningCount.increment() - fun incrementShowLoginsSecureWarningSyncCount() { - preferences.edit().putInt( - appContext.getPreferenceKey(R.string.pref_key_logins_secure_warning_sync), - loginsSecureWarningSyncCount + 1 - ).apply() - } + fun incrementShowLoginsSecureWarningSyncCount() = loginsSecureWarningSyncCount.increment() val shouldShowSearchSuggestions by booleanPreference( appContext.getPreferenceKey(R.string.pref_key_show_search_suggestions), @@ -621,21 +584,16 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = false ) - fun incrementVisitedInstallableCount() { - preferences.edit().putInt( - appContext.getPreferenceKey(R.string.pref_key_install_pwa_visits), - pwaInstallableVisitCount + 1 - ).apply() - } + fun incrementVisitedInstallableCount() = pwaInstallableVisitCount.increment() @VisibleForTesting(otherwise = PRIVATE) - internal val pwaInstallableVisitCount by intPreference( + internal val pwaInstallableVisitCount = counterPreference( appContext.getPreferenceKey(R.string.pref_key_install_pwa_visits), - default = 0 + maxCount = 3 ) private val userNeedsToVisitInstallableSites: Boolean - get() = pwaInstallableVisitCount < pwaVisitsToShowPromptMaxCount + get() = pwaInstallableVisitCount.underMaxCount() val shouldShowPwaOnboarding: Boolean get() { @@ -644,9 +602,8 @@ class Settings(private val appContext: Context) : PreferencesHolder { // ShortcutManager::pinnedShortcuts is only available on Oreo+ if (!userKnowsAboutPwas && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val alreadyHavePwaInstalled = - appContext.getSystemService(ShortcutManager::class.java) - .pinnedShortcuts.size > 0 + val manager = appContext.getSystemService(ShortcutManager::class.java) + val alreadyHavePwaInstalled = manager != null && manager.pinnedShortcuts.size > 0 // Users know about PWAs onboarding if they already have PWAs installed. userKnowsAboutPwas = alreadyHavePwaInstalled @@ -661,17 +618,14 @@ class Settings(private val appContext: Context) : PreferencesHolder { ) @VisibleForTesting(otherwise = PRIVATE) - internal val trackingProtectionOnboardingCount by intPreference( + internal val trackingProtectionOnboardingCount = counterPreference( appContext.getPreferenceKey(R.string.pref_key_tracking_protection_onboarding), - 0 + maxCount = 1 ) fun incrementTrackingProtectionOnboardingCount() { trackingProtectionOnboardingShownThisSession = true - preferences.edit().putInt( - appContext.getPreferenceKey(R.string.pref_key_tracking_protection_onboarding), - trackingProtectionOnboardingCount + 1 - ).apply() + trackingProtectionOnboardingCount.increment() } fun getSitePermissionsPhoneFeatureAction( @@ -708,7 +662,7 @@ class Settings(private val appContext: Context) : PreferencesHolder { default: Int ) = preferences.getInt(AUTOPLAY_USER_SETTING, default) - fun getSitePermissionsPhoneFeatureAutoplayAction( + private fun getSitePermissionsPhoneFeatureAutoplayAction( feature: PhoneFeature, default: AutoplayAction = AutoplayAction.BLOCKED ) = preferences.getInt(feature.getPreferenceKey(appContext), default.toInt()).toAutoplayAction() @@ -790,23 +744,16 @@ class Settings(private val appContext: Context) : PreferencesHolder { 0 ) - fun incrementNumTimesPrivateModeOpened() { - preferences.edit().putInt( - appContext.getPreferenceKey(R.string.pref_key_private_mode_opened), - numTimesPrivateModeOpened + 1 - ).apply() - } + fun incrementNumTimesPrivateModeOpened() = numTimesPrivateModeOpened.increment() private var showedPrivateModeContextualFeatureRecommender by booleanPreference( appContext.getPreferenceKey(R.string.pref_key_showed_private_mode_cfr), default = false ) - private val numTimesPrivateModeOpened: Int - get() = preferences.getInt( - appContext.getPreferenceKey(R.string.pref_key_private_mode_opened), - 0 - ) + private val numTimesPrivateModeOpened = counterPreference( + appContext.getPreferenceKey(R.string.pref_key_private_mode_opened) + ) val showPrivateModeContextualFeatureRecommender: Boolean get() { @@ -814,9 +761,11 @@ class Settings(private val appContext: Context) : PreferencesHolder { .getInstalledMozillaProducts(appContext as Application) .contains(MozillaProductDetector.MozillaProducts.FOCUS.productName) - val showCondition = - (numTimesPrivateModeOpened == CFR_COUNT_CONDITION_FOCUS_INSTALLED && focusInstalled) || - (numTimesPrivateModeOpened == CFR_COUNT_CONDITION_FOCUS_NOT_INSTALLED && !focusInstalled) + val showCondition = if (focusInstalled) { + numTimesPrivateModeOpened.value == CFR_COUNT_CONDITION_FOCUS_INSTALLED + } else { + numTimesPrivateModeOpened.value == CFR_COUNT_CONDITION_FOCUS_NOT_INSTALLED + } if (showCondition && !showedPrivateModeContextualFeatureRecommender) { showedPrivateModeContextualFeatureRecommender = true diff --git a/app/src/main/res/drawable/ic_microphone_widget.xml b/app/src/main/res/drawable/ic_microphone_widget.xml index 7cbf04262f..42d29e1d28 100644 --- a/app/src/main/res/drawable/ic_microphone_widget.xml +++ b/app/src/main/res/drawable/ic_microphone_widget.xml @@ -4,5 +4,5 @@ - + diff --git a/app/src/main/res/drawable/rounded_white_corners.xml b/app/src/main/res/drawable/rounded_search_widget_background.xml similarity index 84% rename from app/src/main/res/drawable/rounded_white_corners.xml rename to app/src/main/res/drawable/rounded_search_widget_background.xml index 88f681fd08..e37f2029b8 100644 --- a/app/src/main/res/drawable/rounded_white_corners.xml +++ b/app/src/main/res/drawable/rounded_search_widget_background.xml @@ -4,6 +4,6 @@ - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search_dialog.xml b/app/src/main/res/layout/fragment_search_dialog.xml index 6a6686f053..ddef5fdec5 100644 --- a/app/src/main/res/layout/fragment_search_dialog.xml +++ b/app/src/main/res/layout/fragment_search_dialog.xml @@ -86,7 +86,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:importantForAccessibility="no" - app:tint="?primaryText" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" diff --git a/app/src/main/res/layout/search_widget_extra_small_v1.xml b/app/src/main/res/layout/search_widget_extra_small_v1.xml index 4dd9c31de3..3ee0f9f36d 100644 --- a/app/src/main/res/layout/search_widget_extra_small_v1.xml +++ b/app/src/main/res/layout/search_widget_extra_small_v1.xml @@ -8,7 +8,7 @@ android:id="@id/button_search_widget_new_tab" android:layout_width="match_parent" android:layout_height="50dp" - android:background="@drawable/rounded_white_corners" + android:background="@drawable/rounded_search_widget_background" android:layout_gravity="center"> + android:background="@drawable/rounded_search_widget_background"> + android:background="@drawable/rounded_search_widget_background"> diff --git a/app/src/main/res/values-an/strings.xml b/app/src/main/res/values-an/strings.xml index 400ac29d78..6270f5a6e9 100644 --- a/app/src/main/res/values-an/strings.xml +++ b/app/src/main/res/values-an/strings.xml @@ -78,6 +78,28 @@ Agora no + + + Puetz configurar Firefox pa que ubra automaticament los vinclos en as aplicacions. + + Ir ta los achustes + + Descartar + + + La camara precisa acceso. Ves ta los achustes d\'Android, toca "permisos" y toca "permitir". + + Ir ta los achustes + + Descartar + + + Configura las pestanyas ubiertas pa que se zarren automaticament si no s\'han mirau en os zaguers días, semanas u meses. + + Veyer las opcions + + Descartar + Nueva pestanya @@ -258,6 +280,8 @@ Barra de ferramientas Tema + + Inicio Personalizar @@ -523,6 +547,8 @@ Ubrir pestanyas Borrar + + Borrar de l\'historial %1$s (modo privau) @@ -727,10 +753,8 @@ Coleccions Menú d’a colección - - Colecciona las cosetas que t’importan - Agrupa busquedas, puestos y pestanyas semellants pa un acceso rapido mas tarde. + Replega lo que t\'importa.\nAlza chuntas las busquedas similars, puestos y pestanyas pa acceder mas rapidament dimpués. Triar pestanyas @@ -974,8 +998,8 @@ Tiens preguntas sobre lo redisenyo de %s? Quiers saber qué ha cambiau? Obtiene respuestas aquí - - Quita-le lo millor provecho a %s. + + Encomienza la sincronización d\'as adrezas d\'interés, las contrasenyas y muito mas con a tuya cuenta d\'o Firefox. Saber-ne mas Vale, entendiu + + Amostrar los puestos mas visitaus + + + Eliminar - + + Quita-le lo millor provecho a %s. + + + Colecciona las cosetas que t’importan + + Agrupa busquedas, puestos y pestanyas semellants pa un acceso rapido mas tarde. + diff --git a/app/src/main/res/values-ast/strings.xml b/app/src/main/res/values-ast/strings.xml index 9aba29ae53..d6c0f501af 100644 --- a/app/src/main/res/values-ast/strings.xml +++ b/app/src/main/res/values-ast/strings.xml @@ -433,6 +433,22 @@ Zarrar + + %d llingüetes + + %d llingüeta + + + A mano + + Dempués d\'un día + + Dempués d\'una selmana + + Dempués d\'un mes + Llingüetes abiertes @@ -449,6 +465,8 @@ Guardar nuna coleición Compartir toles llingüetes + + Llingüetes zarraes apocayá Zarrar toles llingüetes @@ -657,10 +675,6 @@ Coleiciones - - Coleiciona les coses que t\'importen - - Agrupa guetes, sitios y llingüetes similares p\'acceder aína a ellos dempués. Esbilla de llingüetes @@ -907,8 +921,6 @@ ¿Tienes entrugues tocante al rediseñu de %s?¿Quies saber qué camudó? Consigui rempuestes equí - - Aprovecha %s al máximu. Aniciando sesión… @@ -1386,4 +1398,12 @@ Val, entendílo - + + Aprovecha %s al máximu. + + + Coleiciona les coses que t\'importen + + Agrupa guetes, sitios y llingüetes similares p\'acceder aína a ellos dempués. + diff --git a/app/src/main/res/values-co/strings.xml b/app/src/main/res/values-co/strings.xml index 0fc16de7fc..331dc17006 100644 --- a/app/src/main/res/values-co/strings.xml +++ b/app/src/main/res/values-co/strings.xml @@ -25,6 +25,30 @@ %1$s unghjette aperte. Picchichjà per cambià d’unghjetta. + + %1$d selezziunatu(i) + + Aghjunghje una nova cullezzione + + Nome + + Selezziunà a cullezzione + + + Piantà u modu di selezzione multiple + + Arregistrà l’unghjette selezziunate in una cullezzione + + %1$s selezziunatu + + %1$s diselezziunatu + + Fine di u modu di selezzione multiple + + Modu di selezzione multiple attivatu, selezziunate l’unghjette à arregistrà in una cullezzione + + Selezziunatu + %1$s hè sviluppatu da Adam Novak. @@ -58,6 +82,12 @@ Micca subitu + + Ricusà + + + Ricusà + Nova unghjetta @@ -150,8 +180,8 @@ Numerizà - - Mutore di ricerca + + Mutore di ricerca Preferenze di u mutore di ricerca @@ -284,9 +314,14 @@ Apre i liami in appiecazioni + + Ghjestiunariu esternu di scaricamentu Moduli addiziunali + + Nutificazioni + Sincrunizà avà @@ -513,6 +548,9 @@ %1$s (navigazione privata) + + Arregistrà + Squassà a crunulogia @@ -551,6 +589,13 @@ Alcuna crunulogia + + + Alcunu scaricamentu quì + + %1$d selezziunatu(i) + Per disgrazia, %1$s ùn pò micca caricà a pagina. @@ -708,10 +753,8 @@ Cullezzioni Listinu di a cullezzione - - Racuglite ciò chì conta per voi - Gruppate inseme ricerche simile, i siti è l’unghjette per un accessu future più prestu. + Racuglite ciò chì conta per voi.\nGruppate inseme ricerche simile, i siti è l’unghjette per un accessu future più prestu. Selezziunà unghjette @@ -961,9 +1004,10 @@ Avete dumande apprupositu di u rinnovu di %s ? Vulete sapè ciò chì hà cambiatu ? Truvate risposte quì - - Ottene u più bellu da %s. + + Sincrunizate l’indette, e parolle d’entrata è ancu di più cù u vostru contu Firefox. + + Sapene di più @@ -1443,9 +1487,7 @@ L’identificazione di cunnessione cù stu nome d’utilizatore esiste dighjà - - Cunnettatevi cù un contu Firefox. - + Cunnettate un altru apparechju. Ci vole à autenticassi torna. @@ -1460,6 +1502,9 @@ Cunnettassi à Sync + + Alcuna unghjetta aperta + Cunfina di siti principale tocca @@ -1468,13 +1513,15 @@ Iè, aghju capitu - - - Accurtatoghji - - Circà cù - - Per sta volta, circà cù : - - Affissà l’accurtatoghji di ricerca + + Caccià + + + Ottene u più bellu da %s. + + + Racuglite ciò chì conta per voi + + Gruppate inseme ricerche simile, i siti è l’unghjette per un accessu future più prestu. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 6038a23bc4..f38f3ec77b 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -291,6 +291,8 @@ Symbolleiste Theme + + Startseite Anpassen @@ -1549,6 +1551,9 @@ Ok, verstanden + + Meistbesuchte Seiten anzeigen + Entfernen diff --git a/app/src/main/res/values-dsb/strings.xml b/app/src/main/res/values-dsb/strings.xml index 08f5c7fc71..e2dcc76d0e 100644 --- a/app/src/main/res/values-dsb/strings.xml +++ b/app/src/main/res/values-dsb/strings.xml @@ -80,6 +80,28 @@ Nic něnto + + + Móžośo Firefox tak nastajiś, aby se wótkazy awtomatiski w nałoženjach wócynili. + + K nastajenjam + + Zachyśiś + + + Pśistup ku kamerje trjebny. Wócyńśo nastajenja Android, pótusniśo zapisk Pšawa a pótusniśo zapisk Dowóliś. + + K nastajenjam + + Zachyśiś + + + Nastajśo te wócynjone rejtariki tak, aby se awtomatiski zacynili, kótarež njejsu se slědny źeń, slědny tyźeń abo slědny mjasec woglědali. + + Nastajenja pokazaś + + Zachyśiś + Nowy rejtarik @@ -264,6 +286,8 @@ Symbolowa rědka Drastwa + + Startowy bok Pśiměriś @@ -470,6 +494,31 @@ Zacyniś + + Rowno zacynjone rejtariki + + Wšu historiju pokazaś + + Rejtariki: %d + + Rejtariki: %d + + How njejsu rowno zacynjone rejtarki + + + + Rejtariki zacyniś + + Manuelnje + + Pó jadnom dnju + + Pó jadnom tyźenju + + Pó jadnom mjasecu + Wócynjone rejtariki @@ -489,6 +538,10 @@ Do zběrki składowaś Wšykne rejtariki źěliś + + Rowno zacynjone rejtariki + + Nastajenja rejtarikow Wšykne rejtariki zacyniś @@ -535,6 +588,8 @@ Wótwónoźeś + + Z historije lašowaś %1$s (priwatny modus) @@ -1497,6 +1552,18 @@ W pórěźe, som zrozměł + + Nejcesćej woglědane sedła pokazaś + Wótwónoźeś + + + Wuwónoźćo nejlěpše z %s. + + + Gromaźćo wěcy, kótarež su wam wažne + + Zrědujśo pódobne pytanja, sedła a rejtariki za póznjejšy malsny pśistup. diff --git a/app/src/main/res/values-en-rCA/strings.xml b/app/src/main/res/values-en-rCA/strings.xml index 6c8d55b59a..272a64f6e6 100644 --- a/app/src/main/res/values-en-rCA/strings.xml +++ b/app/src/main/res/values-en-rCA/strings.xml @@ -284,6 +284,8 @@ Toolbar Theme + + Home Customize @@ -488,6 +490,31 @@ Close + + Recently closed tabs + + Show full history + + %d tabs + + %d tab + + No recently closed tabs here + + + + Close tabs + + Manually + + After one day + + After one week + + After one month + Open tabs @@ -507,6 +534,10 @@ Save to collection Share all tabs + + Recently closed tabs + + Tab settings Close all tabs @@ -1506,6 +1537,9 @@ OK, Got It + + Show most visited sites + Remove diff --git a/app/src/main/res/values-es-rCL/strings.xml b/app/src/main/res/values-es-rCL/strings.xml index 3ea221ac35..0ed3cb05f7 100644 --- a/app/src/main/res/values-es-rCL/strings.xml +++ b/app/src/main/res/values-es-rCL/strings.xml @@ -78,6 +78,28 @@ Ahora no + + + Puedes configurar Firefox para que abra automáticamente enlaces en aplicaciones. + + Ir a ajustes + + Ocultar + + + Se necesita acceso a la cámara. Ve a los ajustes de Android, toca en permisos y luego en permitir. + + Ir a ajustes + + Ocultar + + + Configura que las pestañas abiertas se cierren automáticamente si no han sido vistas en el último día, semana o mes. + + Ver opciones + + Ocultar + Nueva pestaña @@ -533,6 +555,8 @@ Eliminar + + Eliminar del historial %1$s (modo privado) @@ -1493,4 +1517,13 @@ Eliminar + + + Saca el máximo provecho a %s. + + + Recolecta lo que te importa + + Agrupa búsquedas, sitios y pestañas similares para acceder a ellos rápidamente. diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 27ebe2ac4f..94f1465d28 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -81,6 +81,28 @@ Orain ez + + + Loturak aplikazioetan automatikoki irekitzeko ezar dezakezu Firefox + + Joan ezarpenetara + + Baztertu + + + Kamerarako sarbidea behar da. Joan Androideko ezarpenetara, sakatu baimenetan eta sakatu baimendu. + + Joan ezarpenetara + + Baztertu + + + Ezarri irekitako fitxak automatikoki ixtea, azken egun, aste edo hilabetean ikusi ez badira + + Ikusi aukerak + + Baztertu + Fitxa berria @@ -545,6 +567,8 @@ Kendu + + Ezabatu historiatik %1$s (modu pribatua) @@ -752,10 +776,8 @@ Bildumak Bildumaren menua - - Bildu zuretzat garrantzizkoa dena - Multzokatu antzerako bilaketak, guneak eta fitxak sarbide azkarrago baterako. + Bildu zure interesekoa dena.\nMultzokatu antzerako bilaketak, guneak eta fitxak geroago sarbide zuzena izateko. Hautatu fitxak @@ -1004,8 +1026,8 @@ Birdiseinatutako %s(r)i buruzko galderak dituzu? Zer aldatu den jakin nahi duzu? Eskuratu erantzunak hemen - - Atera %s(r)i ahalik eta zuku gehiena. + + Hasi sinkronizatzen laster-markak, historia eta gehiago zure Firefox kontua erabiliz. Argibide gehiago Ados, ulertuta - + + Kendu + + + Atera %s(r)i ahalik eta zuku gehiena. + + + Bildu zuretzat garrantzizkoa dena + + Multzokatu antzerako bilaketak, guneak eta fitxak sarbide azkarrago baterako. + diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index f2b3b0ef66..4dd55b7c9d 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -290,6 +290,8 @@ Työkalupalkki Teema + + Koti Mukauta @@ -1532,6 +1534,9 @@ Selvä + + Näytä vierailluimmat sivustot + Poista diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c957fd851c..861364e0c5 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -79,6 +79,29 @@ Plus tard + + + Vous pouvez configurer Firefox pour ouvrir automatiquement les liens dans des applications. + + Ouvrir les paramètres + + + Ignorer + + + Accès à la caméra nécessaire. Accédez aux paramètres Android, appuyez sur Autorisations, puis sur Autoriser. + + Ouvrir les paramètres + + Ignorer + + + Configurez les onglets ouverts pour qu’ils se ferment automatiquement lorsqu’ils n’ont pas été vus depuis les derniers jours, semaines ou mois. + + Afficher les options + + Ignorer + Nouvel onglet @@ -543,6 +566,8 @@ Supprimer + + Supprimer de l’historique %1$s (navigation privée) @@ -1527,4 +1552,13 @@ Cependant, il peut être moins stable. Téléchargez la version bêta de notre n Supprimer + + + Tirez le meilleur parti de %s. + + + Rassemblez ce qui compte pour vous + + Regroupez des recherches, des sites et des onglets similaires pour y accéder rapidement plus tard. diff --git a/app/src/main/res/values-fy-rNL/strings.xml b/app/src/main/res/values-fy-rNL/strings.xml index d175b3a244..5d4eb44201 100644 --- a/app/src/main/res/values-fy-rNL/strings.xml +++ b/app/src/main/res/values-fy-rNL/strings.xml @@ -81,6 +81,28 @@ No net + + + Jo kinne Firefox ynstelle om keppelingen automatysk yn apps te iepenjen. + + Nei ynstellingen + + Slute + + + Kameratagong fereaske. Gean nei jo Android-ynstellingen, tik op machtigingen en tik op toestaan. + + Nei ynstellingen + + Slute + + + Stel iepen ljepblêden yn om automatysk te sluten as se net yn de ôfrûne dei, wike of moanne besjoen binne. + + Byldopsjes + + Slute + Nij ljepblêd @@ -535,6 +557,8 @@ Fuortsmite + + Fuortsmite út skiednis %1$s (priveemodus) @@ -1495,4 +1519,13 @@ Fuortsmite + + + Helje it measte út %s. + + + Sammelje de dingen dy\'t wichtich foar jo binne + + Groepearje fergelykbere sykopdrachten, websites en ljepblêden foar flugge tagong letter. diff --git a/app/src/main/res/values-gn/strings.xml b/app/src/main/res/values-gn/strings.xml index a74d351468..f76853bb79 100644 --- a/app/src/main/res/values-gn/strings.xml +++ b/app/src/main/res/values-gn/strings.xml @@ -82,6 +82,24 @@ Ani ko’ág̃a + + + Embohekokuaa Firefox ombojuruja hag̃ua ijehegui juajuha tembipuru’ípe. + + Eho ñembohekópe + + Mboyke + + + Eho ñembohekópe + + Mboyke + + + Ehecha jeporavorã + + Mboyke + Tendayke pyahu @@ -541,6 +559,8 @@ Mboguete + + Emboguete tembiasakuégui %1$s (Ayvu Ñemigua) @@ -1522,4 +1542,9 @@ Mboguete - + + + Eguenohẽ %s-gui eikotevẽva. + + diff --git a/app/src/main/res/values-hsb/strings.xml b/app/src/main/res/values-hsb/strings.xml index f9f0eaf9e7..b3eca81f0e 100644 --- a/app/src/main/res/values-hsb/strings.xml +++ b/app/src/main/res/values-hsb/strings.xml @@ -80,16 +80,23 @@ Nic nětko + + + Móžeće Firefox tak nastajić, zo bychu so wotkazy awtomatisce w nałoženjach wočinili. K nastajenjam Zaćisnyć + + Přistup ke kamerje trěbny. Wočińće nastajenja Android, podótkńće so zapiska Prawa a podótkńće so zapiska Dowolić. K nastajenjam Zaćisnyć + + Nastajće te wočinjene rajtarki takle, zo bychu so awtomatisce začinili, kotrež njejsu sej posledni dźeń, posledni tydźeń abo posledni měsac wobhladali. Nastajenja pokazać @@ -279,6 +286,8 @@ Symbolowa lajsta Drasta + + Startowa strona Přiměrić @@ -485,6 +494,32 @@ Začinić + + Runje začinjene rajtarki + + Wšu historiju pokazać + + Rajtarki: %d + + + Rajtarki: %d + + Tu žane runje začinjene rajtarki njejsu + + + + Rajtarki začinić + + Manuelnje + + Po jednym dnju + + Po jednym tydźenju + + Po jednym měsacu + Wočinjene rajtarki @@ -504,6 +539,10 @@ Do zběrki składować Wšě rajtarki dźělić + + Runje začinjene rajtarki + + Nastajenja rajtarkow Wšě rajtarki začinić @@ -1512,6 +1551,9 @@ W porjadku, sym zrozumił + + Najhusćišo wopytowane sydła pokazać + Wotstronić diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index f8c6b9cee5..5abfd479ca 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -287,6 +287,8 @@ Eszköztár Téma + + Kezdőlap Testreszabás @@ -1529,6 +1531,9 @@ Rendben, értem + + A leglátogatottabb oldalak megjelenítése + Eltávolítás diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index ed95c1c089..a9b2ad9fd3 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -81,6 +81,28 @@ Jangan sekarang + + + Anda dapat mengatur Firefox untuk membuka tautan secara otomatis dalam aplikasi. + + Buka pengaturan + + Tutup + + + Akses kamera diperlukan. Buka pengaturan Android, ketuk izin, dan ketuk izinkan. + + Buka pengaturan + + Tutup + + + Setel tab yang terbuka untuk menutup secara otomatis yang belum pernah dilihat pada beberapa hari, minggu, atau bulan terakhir. + + Lihat pengaturan + + Tutup + Tab baru @@ -271,6 +293,8 @@ Bilah alat Tema + + Beranda Ubahsuai @@ -548,6 +572,8 @@ Hapus + + Hapus dari riwayat %1$s (Mode Privat) @@ -756,10 +782,8 @@ Koleksi Menu koleksi - - Kumpulkan hal-hal yang penting bagi Anda - Kelompokkan pencarian, situs, dan tab yang serupa agar dapat diakses cepat nanti. + Kumpulkan hal-hal yang penting bagi Anda.\nKelompokkan pencarian, situs, dan tab serupa untuk akses cepat nanti. Pilih Tab @@ -1012,8 +1036,8 @@ Punya pertanyaan mengenai desain ulang %s? Ingin tahu apa saja yang berubah? Dapatkan jawabannya di sini - - Dapatkan hasil maksimal dari %s. + + Mulai sinkronkan markah, kata sandi, dan lainnya dengan akun Firefox Anda. Pelajari lebih lanjut Oke, Paham! - + + Tampilkan situs yang paling sering dikunjungi + + + Hapus + + + Dapatkan hasil maksimal dari %s. + + + Kumpulkan hal-hal yang penting bagi Anda + + Kelompokkan pencarian, situs, dan tab yang serupa agar dapat diakses cepat nanti. + diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml index c5194dadbe..49238c5a0e 100644 --- a/app/src/main/res/values-kab/strings.xml +++ b/app/src/main/res/values-kab/strings.xml @@ -288,6 +288,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Afeggag n yifecka Asentel + + Asebter agejdan Sagen @@ -497,6 +499,31 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Mdel + + Iccaren imedlen melmi kan + + Sken amazray meṛṛa + + %d n yiccaren + + %d n yiccer + + Ulac iccaren imedlen melmi kan + + + + Mdel iccaren + + S ufus + + Seld yiwen n wass + + Seld yiwen n umalas + + Seld yiwen n waggur + Iccaren yeldin @@ -516,6 +543,10 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Sekles ɣer tagrumma Bḍu akk accaren + + Iccaren imedlen melmi kan + + Iɣewwaren n yiccer Mdel akk iccaren @@ -1531,6 +1562,9 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara IH, awi-t-id + + Sken-d ismal ittwarzan aṭas + Kkes diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 419b269d3e..3f36975538 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -296,6 +296,8 @@ 도구 모음 테마 + + 사용자 지정 @@ -506,6 +508,32 @@ 닫기 + + 최근에 닫은 탭 + + 모든 기록 보기 + + %d개 탭 + + %d개 탭 + + + 최근에 닫은 탭 없음 + + + + 탭 닫기 + + 수동 + + 하루 후 + + 일주일 후 + + 한 달 후 + 열린 탭 @@ -525,6 +553,10 @@ 모음집에 저장 모든 탭 공유 + + 최근에 닫은 탭 + + 탭 설정 모든 탭 닫기 @@ -967,7 +999,7 @@ 열린 탭 - 탭 %d개 + %d개 탭 방문 기록 및 사이트 데이터 @@ -1567,6 +1599,9 @@ 확인 + + 자주 방문한 사이트 표시 + 삭제 diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index ebfcaf134a..2dd357b826 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -288,6 +288,8 @@ Verktøylinje Tema + + Hjem Tilpass @@ -497,6 +499,31 @@ Lukk + + Nylig lukkede faner + + Vis full historikk + + %d faner + + %d fane + + Ingen nylig lukkede faner her + + + + Lukk faner + + Manuelt + + Etter en dag + + Etter en uke + + Etter en måned + Åpne faner @@ -517,6 +544,10 @@ Lagre i samling Del alle faner + + Nylig lukkede faner + + Fane-innstillinger Lukk alle faner @@ -1540,6 +1571,9 @@ OK, jeg skjønner + + Vis mest besøkte nettsteder + Fjern diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index f7656c92bd..5f0f08d118 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -87,6 +87,11 @@ @color/collection_icon_color_green_dark_theme @color/collection_icon_color_yellow_dark_theme + + @color/inset_dark_theme + @color/primary_text_dark_theme + @color/primary_text_dark_theme + @color/primary_text_dark_theme diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 3642836ed5..6c235e3f03 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -84,6 +84,28 @@ Niet nu + + + U kunt Firefox instellen om koppelingen automatisch in apps te openen. + + Naar instellingen + + Sluiten + + + Cameratoegang vereist. Ga naar uw Android-instellingen, tik op machtigingen en tik op toestaan. + + Naar instellingen + + Sluiten + + + Stel open tabbladen in om automatisch te sluiten als ze niet in de afgelopen dag, week of maand zijn bekeken. + + Beeldopties + + Sluiten + Nieuw tabblad @@ -543,6 +565,8 @@ Verwijderen + + Verwijderen uit geschiedenis %1$s (privémodus) @@ -1506,4 +1530,13 @@ Verwijderen + + + Haal het meeste uit %s. + + + Verzamel de dingen die belangrijk voor u zijn + + Groepeer vergelijkbare zoekopdrachten, websites en tabbladen voor snelle toegang later. diff --git a/app/src/main/res/values-nn-rNO/strings.xml b/app/src/main/res/values-nn-rNO/strings.xml index 56f4f1eff0..48edf8bbe2 100644 --- a/app/src/main/res/values-nn-rNO/strings.xml +++ b/app/src/main/res/values-nn-rNO/strings.xml @@ -82,6 +82,19 @@ Ikkje no + + Ignorer + + + Gå til Innstillingar + + Ignorer + + + Vis alternativ + + Ignorer + Ny fane @@ -267,6 +280,8 @@ Verktøylinje Tema + + Heim Tilpass @@ -474,6 +489,27 @@ Lat att + + Nyleg attlatne faner + + %d faner + + %d fane + + + + Lat att faner + + Manuelt + + Etter ein dag + + Etter ei veke + + Etter ein månad + Opne faner @@ -493,6 +529,10 @@ Lagre i samling Del alle faner + + Nyleg attlatne faner + + Fane-innstillinger Lat att alle faner @@ -540,6 +580,8 @@ Fjern + + Slett frå historikk %1$s (privatmodus) @@ -1502,4 +1544,5 @@ Fjern - + + diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 643f336d2a..c1907995c6 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -286,6 +286,8 @@ Barra de ferramentas Tema + + Tela inicial Personalizar @@ -492,6 +494,31 @@ Fechar + + Abas fechadas recentemente + + Mostrar todo o histórico + + %d abas + + %d aba + + Nenhuma aba fechada recentemente aqui + + + + Fechar abas + + Manualmente + + Após um dia + + Após uma semana + + Após um mês + Abas abertas @@ -511,6 +538,10 @@ Salvar em coleção Compartilhar todas as abas + + Abas fechadas recentemente + + Configurações de abas Fechar todas as abas @@ -1520,6 +1551,9 @@ OK, entendi + + Mostrar os sites mais visitados + Remover diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6d8f1ad484..7ac50da9c1 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -82,6 +82,15 @@ Не сейчас + + Перейти в настройки + + + Перейти в настройки + + + Открыть настройки + Новая вкладка @@ -545,6 +554,8 @@ Удалить + + Удалить из истории %1$s (Приватный просмотр) @@ -1525,4 +1536,9 @@ Убрать - + + + Получите максимум от %s. + + diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml index 39291135be..e9639334eb 100644 --- a/app/src/main/res/values-sq/strings.xml +++ b/app/src/main/res/values-sq/strings.xml @@ -23,6 +23,29 @@ %1$s skeda të hapura. Prekeni që të ndërroni skeda. + + %1$d të përzgjedhura + + Shtoni koleksion të ri + + Emër + + Përzgjidhni koleksion + + Dil nga mënyra përzgjedhje e shumëfishtë + + Ruaji te koleksioni skedat e përzgjedhura + + U përzgjodh %1$s + + U shpërzgjodh %1$s + + U dol nga mënyra përzgjedhje e shumëfishtë + + U hy në mënyrën përzgjedhje e shumëfishtë, përzgjidhni skeda që të ruhen te një koleksion + + E përzgjedhur + %1$s prodhohet nga Adam Novak. @@ -54,6 +77,28 @@ Jo tani + + + Mund ta ujdisni Firefox-in të hapë automatikisht lidhje në aplikacione. + + Shko te rregullimet + + Hidhe tej + + + Lypset hyrje në kamera. Kaloni te rregullimet e Android-it, prekni Leje, dhe prekni Lejoje. + + Shko te rregullimet + + Hidhe tej + + + Caktoni mbyllje automatike skedash të hapura që nuk janë parë ditën, javën ose muajin e shkuar. + + Shihni mundësitë + + Hidhe tej + Skedë e re @@ -146,8 +191,8 @@ Skanoje - - Motor Kërkimesh + + Motor kërkimesh Rregullime motorësh kërkimesh @@ -278,9 +323,14 @@ Hapi lidhjet në aplikacione + + Përgjegjës i jashtëm shkarkimesh Shtesa + + Njoftime + Njëkohësoje tani @@ -505,9 +555,14 @@ Hiqe + + Fshije prej historiku %1$s (Mënyrë Private) + + Ruaje + Fshije historikun @@ -546,6 +601,13 @@ S’ka historik këtu + + + S’ka shkarkime këtu + + %1$d të përzgjedhura + Na ndjeni. %1$s s’mund ta ngarkojë atë faqe. @@ -700,10 +762,8 @@ Koleksione Menu koleksionesh - - Koleksiononi gjërat që kanë rëndësi për ju - Gruponi tok kërkime, sajte dhe skeda të ngjashme, për përdorim të shpejtë më pas. + Koleksiononi gjërat që kanë rëndësi për ju.\nGruponi tok kërkime të ngjashme, sajte, dhe skeda, për përdorim më të shpejtë më pas. Përzgjidhni Skeda @@ -952,9 +1012,10 @@ Keni pyetje rreth %s-it të rikonceptuar? Doni të dini se ç’është ndryshuar? Merrni përgjigje këtu - - Përfitoni maksimumin nga %s. + + Filloni të njëkohësoni faqerojtës, fjalëkalime, etj, me llogarinë tuaj Firefox. + + Mësoni më tepër @@ -1434,9 +1495,7 @@ Ka tashmë një palë kredenciale me këtë emër përdoruesi - - Lidhuni me Llogaritë Firefox. - + Lidhni pajisje tjetër. Ju lutemi, ribëni mirëfilltësimin. @@ -1449,6 +1508,9 @@ Bëni hyrjen që të sjëkohësoni + + S’ka skeda të hapura + U mbërrit te kufi sajtesh @@ -1457,13 +1519,15 @@ OK, e mora vesh - - - Shkurtore - - Kërkoni me - - Këtë herë kërko me: - - Shfaqni shkurtore kërkimi + + Hiqe + + + Përfitoni maksimumin nga %s. + + + Koleksiononi gjërat që kanë rëndësi për ju + + Gruponi tok kërkime, sajte dhe skeda të ngjashme, për përdorim të shpejtë më pas. diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index ffd3966fd6..84a2f1fd1e 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -283,6 +283,8 @@ Verktygsfält Tema + + Hem Anpassa @@ -493,6 +495,29 @@ Stäng + + Nyligen stängda flikar + + Visa fullständig historik + + %d flikar + + %d flik + + + + Stäng flikar + + Manuellt + + Efter en dag + + Efter en vecka + + Efter en månad + Öppna flikar @@ -512,6 +537,8 @@ Spara i samling Dela alla flikar + + Nyligen stängda flikar Stäng alla flikar @@ -1527,6 +1554,9 @@ Ok, jag förstår + + Visa mest besökta webbplatser + Ta bort @@ -1534,4 +1564,6 @@ The first parameter is the name of the app (e.g. Firefox Preview) --> Få ut det mesta av %s. + + Samla de saker som är viktiga för dig diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 48f2dfb2af..be7d994d93 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -289,6 +289,8 @@ Панель інструментів Тема + + Домівка Пристосування @@ -499,6 +501,31 @@ Закрити + + Недавно закриті вкладки + + Показати всю історію + + %d вкладок + + %d вкладка + + Немає нещодавно закритих вкладок + + + + Закрити вкладки + + Вручну + + Через день + + Через тиждень + + Через місяць + Відкриті вкладки @@ -518,6 +545,10 @@ Зберегти до збірки Поділитися всіма вкладками + + Недавно закриті вкладки + + Налаштування вкладок Закрити всі вкладки @@ -1532,6 +1563,9 @@ Гаразд, зрозуміло + + Показати найвідвідуваніші сайти + Вилучити diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index aa2e767355..883ed5af99 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -83,6 +83,11 @@ 现在不要 + + + 您可以将 Firefox 设为链接的打开方式。 + + 转至设置 知道了 @@ -93,6 +98,8 @@ 知道了 + + 设置自动关闭过去一天、一周或一个月未查看的已打开标签页。 查看选项 @@ -326,7 +333,7 @@ 账户设置 - 在应用程序中打开链接 + 用外部应用打开链接 外部下载管理器 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index cf4f4265b4..8b5a561171 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -290,6 +290,8 @@ 工具列 佈景主題 + + 主畫面 自訂 @@ -1543,6 +1545,9 @@ 好,知道了! + + 顯示最常造訪的網站 + 移除 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 8469a6d0b0..58c98a8db1 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -389,6 +389,8 @@ #312A65 + @color/white_color + #FF000000 #737373 diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index e54e278291..580b3cc2de 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -207,5 +207,4 @@ pref_key_login_exceptions pref_key_show_collections_home - pref_key_temp_review_prompt diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bb4bb70f05..e8617f4591 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -483,6 +483,31 @@ Close + + Recently closed tabs + + Show full history + + %d tabs + + %d tab + + No recently closed tabs here + + + + Close tabs + + Manually + + After one day + + After one week + + After one month + Open tabs @@ -502,6 +527,10 @@ Save to collection Share all tabs + + Recently closed tabs + + Tab settings Close all tabs diff --git a/app/src/main/res/xml/secret_settings_preferences.xml b/app/src/main/res/xml/secret_settings_preferences.xml index dd2830bf3f..3a20cef48b 100644 --- a/app/src/main/res/xml/secret_settings_preferences.xml +++ b/app/src/main/res/xml/secret_settings_preferences.xml @@ -19,6 +19,4 @@ android:key="@string/pref_key_synced_tabs_tabs_tray" android:title="@string/preferences_debug_synced_tabs_tabs_tray" app:iconSpaceReserved="false" /> - diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounterMenuItem.kt b/app/src/test/java/org/mozilla/fenix/browser/browsingmode/SimpleBrowsingModeManager.kt similarity index 52% rename from app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounterMenuItem.kt rename to app/src/test/java/org/mozilla/fenix/browser/browsingmode/SimpleBrowsingModeManager.kt index a74853af5d..872c06c023 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounterMenuItem.kt +++ b/app/src/test/java/org/mozilla/fenix/browser/browsingmode/SimpleBrowsingModeManager.kt @@ -2,9 +2,8 @@ * 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.components.toolbar +package org.mozilla.fenix.browser.browsingmode -sealed class TabCounterMenuItem { - object CloseTab : TabCounterMenuItem() - class NewTab(val isPrivate: Boolean) : TabCounterMenuItem() -} +data class SimpleBrowsingModeManager( + override var mode: BrowsingMode +) : BrowsingModeManager diff --git a/app/src/test/java/org/mozilla/fenix/components/settings/CounterPreferenceTest.kt b/app/src/test/java/org/mozilla/fenix/components/settings/CounterPreferenceTest.kt new file mode 100644 index 0000000000..cee183392f --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/components/settings/CounterPreferenceTest.kt @@ -0,0 +1,63 @@ +/* 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.components.settings + +import android.content.SharedPreferences +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.verify +import mozilla.components.support.ktx.android.content.PreferencesHolder +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +class CounterPreferenceTest { + + @MockK private lateinit var prefs: SharedPreferences + @MockK private lateinit var editor: SharedPreferences.Editor + + @Before + fun setup() { + MockKAnnotations.init(this) + every { prefs.getInt("key", 0) } returns 0 + every { prefs.edit() } returns editor + every { editor.putInt("key", any()) } returns editor + every { editor.apply() } just Runs + } + + @Test + fun `update value after increment`() { + val holder = CounterHolder() + + assertEquals(0, holder.property.value) + holder.property.increment() + + verify { editor.putInt("key", 1) } + } + + @Test + fun `check if value is under max count`() { + val holder = CounterHolder(maxCount = 2) + + every { prefs.getInt("key", 0) } returns 0 + assertEquals(0, holder.property.value) + assertTrue(holder.property.underMaxCount()) + + every { prefs.getInt("key", 0) } returns 2 + assertEquals(2, holder.property.value) + assertFalse(holder.property.underMaxCount()) + } + + private inner class CounterHolder(maxCount: Int = -1) : PreferencesHolder { + override val preferences = prefs + + val property = counterPreference("key", maxCount) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/components/settings/FeatureFlagPreferenceTest.kt b/app/src/test/java/org/mozilla/fenix/components/settings/FeatureFlagPreferenceTest.kt new file mode 100644 index 0000000000..764a7e3836 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/components/settings/FeatureFlagPreferenceTest.kt @@ -0,0 +1,67 @@ +/* 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.components.settings + +import android.content.SharedPreferences +import io.mockk.Called +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.verify +import mozilla.components.support.ktx.android.content.PreferencesHolder +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +class FeatureFlagPreferenceTest { + + @MockK private lateinit var prefs: SharedPreferences + @MockK private lateinit var editor: SharedPreferences.Editor + + @Before + fun setup() { + MockKAnnotations.init(this) + every { prefs.getBoolean("key", false) } returns true + every { prefs.edit() } returns editor + every { editor.putBoolean("key", any()) } returns editor + every { editor.apply() } just Runs + } + + @Test + fun `acts like boolean preference if feature flag is true`() { + val holder = FeatureFlagHolder(featureFlag = true) + + assertTrue(holder.property) + verify { prefs.getBoolean("key", false) } + + holder.property = false + verify { editor.putBoolean("key", false) } + } + + @Test + fun `no-op if feature flag is false`() { + val holder = FeatureFlagHolder(featureFlag = false) + + assertFalse(holder.property) + holder.property = true + holder.property = false + + verify { prefs wasNot Called } + verify { editor wasNot Called } + } + + private inner class FeatureFlagHolder(featureFlag: Boolean) : PreferencesHolder { + override val preferences = prefs + + var property by featureFlagPreference( + "key", + default = false, + featureFlag = featureFlag + ) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserInteractorTest.kt index 474ffc4b48..29b955e6dc 100644 --- a/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserInteractorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserInteractorTest.kt @@ -1,5 +1,7 @@ package org.mozilla.fenix.components.toolbar +import io.mockk.MockKAnnotations +import io.mockk.impl.annotations.RelaxedMockK import io.mockk.mockk import io.mockk.verify import org.junit.Before @@ -7,14 +9,16 @@ import org.junit.Test class BrowserInteractorTest { - lateinit var browserToolbarController: BrowserToolbarController + @RelaxedMockK lateinit var browserToolbarController: BrowserToolbarController + @RelaxedMockK lateinit var browserToolbarMenuController: BrowserToolbarMenuController lateinit var interactor: BrowserInteractor @Before fun setup() { - browserToolbarController = mockk(relaxed = true) + MockKAnnotations.init(this) interactor = BrowserInteractor( - browserToolbarController + browserToolbarController, + browserToolbarMenuController ) } @@ -26,7 +30,7 @@ class BrowserInteractorTest { @Test fun onTabCounterMenuItemTapped() { - val item: TabCounterMenuItem = mockk() + val item: TabCounterMenu.Item = mockk() interactor.onTabCounterMenuItemTapped(item) verify { browserToolbarController.handleTabCounterItemInteraction(item) } @@ -60,6 +64,6 @@ class BrowserInteractorTest { interactor.onBrowserToolbarMenuItemTapped(item) - verify { browserToolbarController.handleToolbarItemInteraction(item) } + verify { browserToolbarMenuController.handleToolbarItemInteraction(item) } } } 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 1d38f6a9d6..a25367093c 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 @@ -4,9 +4,8 @@ package org.mozilla.fenix.components.toolbar -import android.content.Intent import androidx.navigation.NavController -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import androidx.navigation.NavOptions import io.mockk.MockKAnnotations import io.mockk.Runs import io.mockk.every @@ -14,149 +13,101 @@ import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.RelaxedMockK import io.mockk.just import io.mockk.mockk -import io.mockk.mockkObject -import io.mockk.mockkStatic import io.mockk.slot -import io.mockk.unmockkStatic import io.mockk.verify import io.mockk.verifyOrder -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest -import mozilla.appservices.places.BookmarkRoot import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager -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 -import mozilla.components.feature.session.SessionFeature import mozilla.components.feature.session.SessionUseCases -import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.tabs.TabsUseCases import mozilla.components.feature.top.sites.TopSitesUseCases -import mozilla.components.support.base.feature.ViewBoundFeatureWrapper -import mozilla.components.support.test.rule.MainCoroutineRule -import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mozilla.fenix.HomeActivity -import org.mozilla.fenix.NavGraphDirections import org.mozilla.fenix.R import org.mozilla.fenix.browser.BrowserAnimator import org.mozilla.fenix.browser.BrowserFragmentDirections import org.mozilla.fenix.browser.browsingmode.BrowsingMode -import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager -import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager +import org.mozilla.fenix.browser.browsingmode.SimpleBrowsingModeManager import org.mozilla.fenix.browser.readermode.ReaderModeController -import org.mozilla.fenix.collections.SaveCollectionStep -import org.mozilla.fenix.components.Analytics -import org.mozilla.fenix.components.FenixSnackbar -import org.mozilla.fenix.components.TabCollectionStorage import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.directionsEq -import org.mozilla.fenix.ext.nav import org.mozilla.fenix.helpers.FenixRobolectricTestRunner -import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit -@OptIn(ExperimentalCoroutinesApi::class) @RunWith(FenixRobolectricTestRunner::class) class DefaultBrowserToolbarControllerTest { - @get:Rule - val coroutinesTestRule = MainCoroutineRule() - - @MockK private lateinit var swipeRefreshLayout: SwipeRefreshLayout @RelaxedMockK private lateinit var activity: HomeActivity - @RelaxedMockK private lateinit var analytics: Analytics - @RelaxedMockK private lateinit var navController: NavController - @RelaxedMockK private lateinit var findInPageLauncher: () -> Unit - @RelaxedMockK private lateinit var bookmarkTapped: (Session) -> Unit + @MockK(relaxUnitFun = true) private lateinit var navController: NavController @RelaxedMockK private lateinit var onTabCounterClicked: () -> Unit @RelaxedMockK private lateinit var onCloseTab: (Session) -> Unit @RelaxedMockK private lateinit var sessionManager: SessionManager - @RelaxedMockK private lateinit var engineView: EngineView - @RelaxedMockK private lateinit var currentSession: Session - @RelaxedMockK private lateinit var openInFenixIntent: Intent + @MockK(relaxUnitFun = true) private lateinit var engineView: EngineView + @MockK private lateinit var currentSession: Session @RelaxedMockK private lateinit var metrics: MetricController @RelaxedMockK private lateinit var searchUseCases: SearchUseCases @RelaxedMockK private lateinit var sessionUseCases: SessionUseCases @RelaxedMockK private lateinit var browserAnimator: BrowserAnimator - @RelaxedMockK private lateinit var snackbar: FenixSnackbar - @RelaxedMockK private lateinit var tabCollectionStorage: TabCollectionStorage @RelaxedMockK private lateinit var topSitesUseCase: TopSitesUseCases @RelaxedMockK private lateinit var readerModeController: ReaderModeController - @RelaxedMockK private lateinit var sessionFeatureWrapper: ViewBoundFeatureWrapper - @RelaxedMockK private lateinit var sessionFeature: SessionFeature - private val store: BrowserStore = BrowserStore(initialState = BrowserState( - listOf( - createTab("https://www.mozilla.org", id = "reader-inactive-tab"), - createTab("https://www.mozilla.org", id = "reader-active-tab", readerState = ReaderState(active = true)) - )) - ) @Before fun setUp() { MockKAnnotations.init(this) - mockkStatic( - "org.mozilla.fenix.settings.deletebrowsingdata.DeleteAndQuitKt" - ) - every { deleteAndQuit(any(), any(), any()) } just Runs - - mockkObject(FenixSnackbar.Companion) - every { FenixSnackbar.make(any(), any(), any(), any()) } returns snackbar - - every { activity.components.analytics } returns analytics - every { analytics.metrics } returns metrics every { activity.components.useCases.sessionUseCases } returns sessionUseCases every { activity.components.useCases.searchUseCases } returns searchUseCases every { activity.components.useCases.topSitesUseCase } returns topSitesUseCase - every { activity.components.core.sessionManager } returns sessionManager - every { activity.components.core.store } returns store every { sessionManager.selectedSession } returns currentSession - every { sessionFeatureWrapper.get() } returns sessionFeature + every { navController.currentDestination } returns mockk { + every { id } returns R.id.browserFragment + } + every { currentSession.id } returns "1" + every { currentSession.private } returns false + every { currentSession.searchTerms = any() } just Runs val onComplete = slot<() -> Unit>() every { browserAnimator.captureEngineViewAndDrawStatically(capture(onComplete)) } answers { onComplete.captured.invoke() } } - @After - fun tearDown() { - unmockkStatic("org.mozilla.fenix.settings.deletebrowsingdata.DeleteAndQuitKt") + @Test + fun handleBrowserToolbarPaste() { + val pastedText = "Mozilla" + val controller = createController(useNewSearchExperience = false) + controller.handleToolbarPaste(pastedText) + + val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment( + sessionId = "1", + pastedText = pastedText + ) + + verify { navController.navigate(directions, any()) } } @Test - fun handleBrowserToolbarPaste() = runBlockingTest { - every { currentSession.id } returns "1" - + fun handleBrowserToolbarPaste_useNewSearchExperience() { val pastedText = "Mozilla" - val controller = createController(scope = this) + val controller = createController(useNewSearchExperience = true) controller.handleToolbarPaste(pastedText) - verify { - val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment( - sessionId = "1", - pastedText = pastedText - ) - navController.nav(R.id.browserFragment, directions) - } + val directions = BrowserFragmentDirections.actionGlobalSearchDialog( + sessionId = "1", + pastedText = pastedText + ) + + verify { navController.navigate(directions, any()) } } @Test - fun handleBrowserToolbarPasteAndGoSearch() = runBlockingTest { + fun handleBrowserToolbarPasteAndGoSearch() { val pastedText = "Mozilla" - val controller = createController(scope = this) + val controller = createController() controller.handleToolbarPasteAndGo(pastedText) verifyOrder { currentSession.searchTerms = "Mozilla" @@ -165,10 +116,10 @@ class DefaultBrowserToolbarControllerTest { } @Test - fun handleBrowserToolbarPasteAndGoUrl() = runBlockingTest { + fun handleBrowserToolbarPasteAndGoUrl() { val pastedText = "https://mozilla.org" - val controller = createController(scope = this) + val controller = createController() controller.handleToolbarPasteAndGo(pastedText) verifyOrder { currentSession.searchTerms = "" @@ -177,391 +128,59 @@ class DefaultBrowserToolbarControllerTest { } @Test - fun handleTabCounterClick() = runBlockingTest { - val controller = createController(scope = this) + fun handleTabCounterClick() { + val controller = createController() controller.handleTabCounterClick() verify { onTabCounterClicked() } } @Test - fun `handle reader mode enabled`() = runBlockingTest { - val controller = createController(scope = this) + fun `handle reader mode enabled`() { + val controller = createController() controller.handleReaderModePressed(enabled = true) verify { readerModeController.showReaderView() } } @Test - fun `handle reader mode disabled`() = runBlockingTest { - val controller = createController(scope = this) + fun `handle reader mode disabled`() { + val controller = createController() controller.handleReaderModePressed(enabled = false) verify { readerModeController.hideReaderView() } } @Test - fun handleToolbarClick() = runBlockingTest { - every { currentSession.id } returns "1" - - val controller = createController(scope = this) + fun handleToolbarClick() { + val controller = createController(useNewSearchExperience = false) controller.handleToolbarClick() - verify { metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)) } - verify { - val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment( - sessionId = "1" - ) - navController.nav(R.id.browserFragment, directions) - } - } - - @Test - fun handleToolbarBackPress() = runBlockingTest { - val item = ToolbarMenu.Item.Back(false) - - val controller = createController(scope = this) - controller.handleToolbarItemInteraction(item) - - verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BACK)) } - verify { sessionUseCases.goBack(currentSession) } - } - - @Test - fun handleToolbarBackLongPress() = runBlockingTest { - val item = ToolbarMenu.Item.Back(true) - - val controller = createController(scope = this) - controller.handleToolbarItemInteraction(item) - - verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BACK)) } - verify { navController.navigate(R.id.action_global_tabHistoryDialogFragment) } - } - - @Test - fun handleToolbarForwardPress() = runBlockingTest { - val item = ToolbarMenu.Item.Forward(false) - - val controller = createController(scope = this) - controller.handleToolbarItemInteraction(item) - - verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.FORWARD)) } - verify { sessionUseCases.goForward(currentSession) } - } - - @Test - fun handleToolbarForwardLongPress() = runBlockingTest { - val item = ToolbarMenu.Item.Forward(true) - - val controller = createController(scope = this) - controller.handleToolbarItemInteraction(item) - - verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.FORWARD)) } - verify { navController.navigate(R.id.action_global_tabHistoryDialogFragment) } - } - - @Test - fun handleToolbarReloadPress() = runBlockingTest { - val item = ToolbarMenu.Item.Reload(false) - - 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) } - } - - @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 - - val controller = createController(scope = this) - controller.handleToolbarItemInteraction(item) - - verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.STOP)) } - verify { sessionUseCases.stopLoading(currentSession) } - } - - @Test - fun handleToolbarSettingsPress() = runBlockingTest { - val item = ToolbarMenu.Item.Settings - - val controller = createController(scope = this) - controller.handleToolbarItemInteraction(item) - - verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SETTINGS)) } - verify { - val directions = BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment() - navController.nav(R.id.browserFragment, directions) - } - } - - @Test - fun handleToolbarBookmarkPress() = runBlockingTest { - val item = ToolbarMenu.Item.Bookmark - - val controller = createController(scope = this) - controller.handleToolbarItemInteraction(item) - - verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BOOKMARK)) } - verify { bookmarkTapped(currentSession) } - } - - @Test - fun handleToolbarBookmarksPress() = runBlockingTest { - val item = ToolbarMenu.Item.Bookmarks - - val controller = createController(scope = this) - controller.handleToolbarItemInteraction(item) - - verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BOOKMARKS)) } - verify { - val directions = BrowserFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id) - navController.nav(R.id.browserFragment, directions) - } - } - - @Test - fun handleToolbarHistoryPress() = runBlockingTest { - val item = ToolbarMenu.Item.History - - val controller = createController(scope = this) - controller.handleToolbarItemInteraction(item) - - verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.HISTORY)) } - verify { - val directions = BrowserFragmentDirections.actionGlobalHistoryFragment() - navController.nav(R.id.browserFragment, directions) - } - } - - @Test - fun handleToolbarRequestDesktopOnPress() = runBlockingTest { - val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase = - mockk(relaxed = true) - val item = ToolbarMenu.Item.RequestDesktop(true) - - every { sessionUseCases.requestDesktopSite } returns requestDesktopSiteUseCase - - val controller = createController(scope = this) - controller.handleToolbarItemInteraction(item) - - verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_ON)) } - verify { - requestDesktopSiteUseCase.invoke( - true, - currentSession - ) - } - } - - @Test - fun handleToolbarRequestDesktopOffPress() = runBlockingTest { - val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase = - mockk(relaxed = true) - val item = ToolbarMenu.Item.RequestDesktop(false) - - every { sessionUseCases.requestDesktopSite } returns requestDesktopSiteUseCase - - val controller = createController(scope = this) - controller.handleToolbarItemInteraction(item) - - verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_OFF)) } - verify { - requestDesktopSiteUseCase.invoke( - false, - currentSession - ) - } - } - - @Test - fun handleToolbarAddToTopSitesPressed() = runBlockingTest { - val item = ToolbarMenu.Item.AddToTopSites - val addPinnedSiteUseCase: TopSitesUseCases.AddPinnedSiteUseCase = mockk(relaxed = true) - - every { topSitesUseCase.addPinnedSites } returns addPinnedSiteUseCase - every { - swipeRefreshLayout.context.getString(R.string.snackbar_added_to_top_sites) - } returns "Added to top sites!" - - val controller = createController(scope = this) - controller.handleToolbarItemInteraction(item) - - verify { addPinnedSiteUseCase.invoke(currentSession.title, currentSession.url) } - verify { snackbar.setText("Added to top sites!") } - verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.ADD_TO_TOP_SITES)) } - } - - @Test - fun handleToolbarAddonsManagerPress() = runBlockingTest { - val item = ToolbarMenu.Item.AddonsManager - - val controller = createController(scope = this) - controller.handleToolbarItemInteraction(item) - - verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.ADDONS_MANAGER)) } - } - - @Test - fun handleToolbarAddToHomeScreenPress() = runBlockingTest { - val item = ToolbarMenu.Item.AddToHomeScreen - - val controller = createController(scope = this) - controller.handleToolbarItemInteraction(item) - - verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN)) } - } - - @Test - fun handleToolbarSharePress() = runBlockingTest { - val item = ToolbarMenu.Item.Share - - every { currentSession.url } returns "https://mozilla.org" - every { currentSession.title } returns "Mozilla" - - val controller = createController(scope = this) - controller.handleToolbarItemInteraction(item) - - verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SHARE)) } - verify { - navController.navigate( - directionsEq(NavGraphDirections.actionGlobalShareFragment( - data = arrayOf(ShareData(url = "https://mozilla.org", title = "Mozilla")), - showPage = true - )) - ) - } - } - - @Test - fun handleToolbarFindInPagePress() = runBlockingTest { - val item = ToolbarMenu.Item.FindInPage - - val controller = createController(scope = this) - controller.handleToolbarItemInteraction(item) - - verify { findInPageLauncher() } - verify { metrics.track(Event.FindInPageOpened) } - } - - @Test - fun handleToolbarSaveToCollectionPressWhenAtLeastOneCollectionExists() = runBlockingTest { - val item = ToolbarMenu.Item.SaveToCollection - val cachedTabCollections: List = mockk(relaxed = true) - every { activity.components.useCases.sessionUseCases } returns sessionUseCases - every { activity.components.core.tabCollectionStorage.cachedTabCollections } returns cachedTabCollections - - val controller = createController(scope = this) - controller.handleToolbarItemInteraction(item) - - verify { - metrics.track( - Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION) - ) - } - verify { - metrics.track( - Event.CollectionSaveButtonPressed(DefaultBrowserToolbarController.TELEMETRY_BROWSER_IDENTIFIER) - ) - } - verify { - val directions = - BrowserFragmentDirections.actionGlobalCollectionCreationFragment( - saveCollectionStep = SaveCollectionStep.SelectCollection, - tabIds = arrayOf(currentSession.id), - selectedTabIds = arrayOf(currentSession.id) - ) - navController.nav(R.id.browserFragment, directions) - } - } - - @Test - fun handleToolbarSaveToCollectionPressWhenNoCollectionsExists() = runBlockingTest { - val item = ToolbarMenu.Item.SaveToCollection - val cachedTabCollectionsEmpty: List = emptyList() - every { activity.components.useCases.sessionUseCases } returns sessionUseCases - every { activity.components.core.tabCollectionStorage.cachedTabCollections } returns cachedTabCollectionsEmpty - - val controller = createController(scope = this) - controller.handleToolbarItemInteraction(item) - - verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION)) } - verify { - metrics.track( - Event.CollectionSaveButtonPressed( - DefaultBrowserToolbarController.TELEMETRY_BROWSER_IDENTIFIER - ) - ) - } - verify { - val directions = - BrowserFragmentDirections.actionGlobalCollectionCreationFragment( - saveCollectionStep = SaveCollectionStep.NameCollection, - tabIds = arrayOf(currentSession.id), - selectedTabIds = arrayOf(currentSession.id) - ) - navController.nav(R.id.browserFragment, directions) - } - } - - @Test - fun handleToolbarOpenInFenixPress() = runBlockingTest { - val controller = createController(scope = this, customTabSession = currentSession) - - val item = ToolbarMenu.Item.OpenInFenix - - every { currentSession.customTabConfig } returns mockk() - every { activity.startActivity(any()) } just Runs - - controller.handleToolbarItemInteraction(item) + val expected = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment( + sessionId = "1" + ) - verify { sessionFeature.release() } - verify { currentSession.customTabConfig = null } - verify { sessionManager.select(currentSession) } - verify { activity.startActivity(openInFenixIntent) } - verify { activity.finish() } + verify { metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)) } + verify { navController.navigate(expected, any()) } } @Test - fun handleToolbarQuitPress() = runBlockingTest { - val item = ToolbarMenu.Item.Quit - val testScope = this - - val controller = createController(scope = testScope) + fun handleToolbarClick_useNewSearchExperience() { + val controller = createController(useNewSearchExperience = true) + controller.handleToolbarClick() - controller.handleToolbarItemInteraction(item) + val expected = BrowserFragmentDirections.actionGlobalSearchDialog( + sessionId = "1" + ) - verify { deleteAndQuit(activity, testScope, null) } + verify { metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)) } + verify { navController.navigate(expected, any()) } } @Test - fun handleToolbarCloseTabPressWithLastPrivateSession() = runBlockingTest { - every { currentSession.id } returns "1" - val browsingModeManager = object : BrowsingModeManager { - override var mode = BrowsingMode.Private - } - val item = TabCounterMenuItem.CloseTab + fun handleToolbarCloseTabPressWithLastPrivateSession() { + val browsingModeManager = SimpleBrowsingModeManager(BrowsingMode.Private) + val item = TabCounterMenu.Item.CloseTab val sessions = listOf( mockk { every { private } returns true @@ -572,81 +191,77 @@ class DefaultBrowserToolbarControllerTest { every { sessionManager.sessions } returns sessions every { activity.browsingModeManager } returns browsingModeManager - val controller = createController(scope = this) + val controller = createController() controller.handleTabCounterItemInteraction(item) verify { navController.navigate(BrowserFragmentDirections.actionGlobalHome(sessionToDelete = "1")) } assertEquals(BrowsingMode.Normal, browsingModeManager.mode) } @Test - fun handleToolbarCloseTabPress() = runBlockingTest { + fun handleToolbarCloseTabPress() { val tabsUseCases: TabsUseCases = mockk(relaxed = true) val removeTabUseCase: TabsUseCases.RemoveTabUseCase = mockk(relaxed = true) - val item = TabCounterMenuItem.CloseTab + val item = TabCounterMenu.Item.CloseTab every { sessionManager.sessions } returns emptyList() every { activity.components.useCases.tabsUseCases } returns tabsUseCases every { tabsUseCases.removeTab } returns removeTabUseCase - val controller = createController(scope = this) + val controller = createController() controller.handleTabCounterItemInteraction(item) verify { removeTabUseCase.invoke(currentSession) } } @Test - fun handleToolbarNewTabPress() = runBlockingTest { - val browsingModeManager: BrowsingModeManager = DefaultBrowsingModeManager( - BrowsingMode.Private, - mockk(relaxed = true) - ) {} - val item = TabCounterMenuItem.NewTab(false) + fun handleToolbarNewTabPress() { + val browsingModeManager = SimpleBrowsingModeManager(BrowsingMode.Private) + val item = TabCounterMenu.Item.NewTab(BrowsingMode.Normal) every { activity.browsingModeManager } returns browsingModeManager + every { navController.popBackStack(R.id.homeFragment, any()) } returns true - val controller = createController(scope = this) + val controller = createController() controller.handleTabCounterItemInteraction(item) - assertEquals(BrowsingMode.Normal, activity.browsingModeManager.mode) + assertEquals(BrowsingMode.Normal, browsingModeManager.mode) verify { navController.popBackStack(R.id.homeFragment, false) } } @Test - fun handleToolbarNewPrivateTabPress() = runBlockingTest { - val browsingModeManager: BrowsingModeManager = DefaultBrowsingModeManager( - BrowsingMode.Normal, - mockk(relaxed = true) - ) {} - val item = TabCounterMenuItem.NewTab(true) + fun handleToolbarNewPrivateTabPress() { + val browsingModeManager = SimpleBrowsingModeManager(BrowsingMode.Normal) + val item = TabCounterMenu.Item.NewTab(BrowsingMode.Private) every { activity.browsingModeManager } returns browsingModeManager + every { navController.popBackStack(R.id.homeFragment, any()) } returns true - val controller = createController(scope = this) + val controller = createController() controller.handleTabCounterItemInteraction(item) - assertEquals(BrowsingMode.Private, activity.browsingModeManager.mode) + assertEquals(BrowsingMode.Private, browsingModeManager.mode) verify { navController.popBackStack(R.id.homeFragment, false) } } + @Test + fun handleScroll() { + val controller = createController() + controller.handleScroll(10) + verify { engineView.setVerticalClipping(10) } + } + private fun createController( - scope: CoroutineScope, activity: HomeActivity = this.activity, - customTabSession: Session? = null + customTabSession: Session? = null, + useNewSearchExperience: Boolean = false ) = DefaultBrowserToolbarController( activity = activity, navController = navController, - findInPageLauncher = findInPageLauncher, + metrics = metrics, engineView = engineView, browserAnimator = browserAnimator, customTabSession = customTabSession, - openInFenixIntent = openInFenixIntent, - scope = scope, - swipeRefresh = swipeRefreshLayout, - tabCollectionStorage = tabCollectionStorage, - bookmarkTapped = bookmarkTapped, readerModeController = readerModeController, sessionManager = sessionManager, - sessionFeature = sessionFeatureWrapper, + useNewSearchExperience = useNewSearchExperience, onTabCounterClicked = onTabCounterClicked, onCloseTab = onCloseTab - ).apply { - ioScope = scope - } + ) } diff --git a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarMenuControllerTest.kt b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarMenuControllerTest.kt new file mode 100644 index 0000000000..bd82ee71b4 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarMenuControllerTest.kt @@ -0,0 +1,494 @@ +/* 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.components.toolbar + +import android.content.Intent +import androidx.navigation.NavController +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.impl.annotations.RelaxedMockK +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.slot +import io.mockk.unmockkObject +import io.mockk.unmockkStatic +import io.mockk.verify +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runBlockingTest +import mozilla.appservices.places.BookmarkRoot +import mozilla.components.browser.session.Session +import mozilla.components.browser.session.SessionManager +import mozilla.components.concept.engine.EngineSession +import mozilla.components.concept.engine.prompt.ShareData +import mozilla.components.feature.search.SearchUseCases +import mozilla.components.feature.session.SessionFeature +import mozilla.components.feature.session.SessionUseCases +import mozilla.components.feature.tab.collections.TabCollection +import mozilla.components.feature.top.sites.TopSitesUseCases +import mozilla.components.support.base.feature.ViewBoundFeatureWrapper +import mozilla.components.support.test.rule.MainCoroutineRule +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.NavGraphDirections +import org.mozilla.fenix.R +import org.mozilla.fenix.browser.BrowserAnimator +import org.mozilla.fenix.browser.BrowserFragmentDirections +import org.mozilla.fenix.browser.readermode.ReaderModeController +import org.mozilla.fenix.collections.SaveCollectionStep +import org.mozilla.fenix.components.FenixSnackbar +import org.mozilla.fenix.components.TabCollectionStorage +import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.components.metrics.MetricController +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.directionsEq +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner +import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit +import org.mozilla.fenix.utils.Settings + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(FenixRobolectricTestRunner::class) +class DefaultBrowserToolbarMenuControllerTest { + + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + + @MockK private lateinit var swipeRefreshLayout: SwipeRefreshLayout + @RelaxedMockK private lateinit var activity: HomeActivity + @RelaxedMockK private lateinit var navController: NavController + @RelaxedMockK private lateinit var findInPageLauncher: () -> Unit + @RelaxedMockK private lateinit var bookmarkTapped: (Session) -> Unit + @RelaxedMockK private lateinit var sessionManager: SessionManager + @RelaxedMockK private lateinit var currentSession: Session + @RelaxedMockK private lateinit var openInFenixIntent: Intent + @RelaxedMockK private lateinit var metrics: MetricController + @RelaxedMockK private lateinit var settings: Settings + @RelaxedMockK private lateinit var searchUseCases: SearchUseCases + @RelaxedMockK private lateinit var sessionUseCases: SessionUseCases + @RelaxedMockK private lateinit var browserAnimator: BrowserAnimator + @RelaxedMockK private lateinit var snackbar: FenixSnackbar + @RelaxedMockK private lateinit var tabCollectionStorage: TabCollectionStorage + @RelaxedMockK private lateinit var topSitesUseCase: TopSitesUseCases + @RelaxedMockK private lateinit var readerModeController: ReaderModeController + @MockK private lateinit var sessionFeatureWrapper: ViewBoundFeatureWrapper + @RelaxedMockK private lateinit var sessionFeature: SessionFeature + + @Before + fun setUp() { + MockKAnnotations.init(this) + + mockkStatic( + "org.mozilla.fenix.settings.deletebrowsingdata.DeleteAndQuitKt" + ) + every { deleteAndQuit(any(), any(), any()) } just Runs + + mockkObject(FenixSnackbar.Companion) + every { FenixSnackbar.make(any(), any(), any(), any()) } returns snackbar + + every { activity.components.useCases.sessionUseCases } returns sessionUseCases + every { activity.components.useCases.searchUseCases } returns searchUseCases + every { activity.components.useCases.topSitesUseCase } returns topSitesUseCase + every { sessionManager.selectedSession } returns currentSession + every { sessionFeatureWrapper.get() } returns sessionFeature + every { navController.currentDestination } returns mockk { + every { id } returns R.id.browserFragment + } + every { currentSession.id } returns "1" + + val onComplete = slot<() -> Unit>() + every { browserAnimator.captureEngineViewAndDrawStatically(capture(onComplete)) } answers { onComplete.captured.invoke() } + } + + @After + fun tearDown() { + unmockkStatic("org.mozilla.fenix.settings.deletebrowsingdata.DeleteAndQuitKt") + unmockkObject(FenixSnackbar.Companion) + } + + @Test + fun handleToolbarBackPress() = runBlockingTest { + val item = ToolbarMenu.Item.Back(false) + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BACK)) } + verify { sessionUseCases.goBack(currentSession) } + } + + @Test + fun handleToolbarBackLongPress() = runBlockingTest { + val item = ToolbarMenu.Item.Back(true) + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + val directions = BrowserFragmentDirections.actionGlobalTabHistoryDialogFragment() + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BACK)) } + verify { navController.navigate(directions) } + } + + @Test + fun handleToolbarForwardPress() = runBlockingTest { + val item = ToolbarMenu.Item.Forward(false) + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.FORWARD)) } + verify { sessionUseCases.goForward(currentSession) } + } + + @Test + fun handleToolbarForwardLongPress() = runBlockingTest { + val item = ToolbarMenu.Item.Forward(true) + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + val directions = BrowserFragmentDirections.actionGlobalTabHistoryDialogFragment() + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.FORWARD)) } + verify { navController.navigate(directions) } + } + + @Test + fun handleToolbarReloadPress() = runBlockingTest { + val item = ToolbarMenu.Item.Reload(false) + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.RELOAD)) } + verify { sessionUseCases.reload(currentSession) } + } + + @Test + fun handleToolbarReloadLongPress() = runBlockingTest { + val item = ToolbarMenu.Item.Reload(true) + + 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 + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.STOP)) } + verify { sessionUseCases.stopLoading(currentSession) } + } + + @Test + fun handleToolbarSettingsPress() = runBlockingTest { + val item = ToolbarMenu.Item.Settings + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + val directions = BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment() + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SETTINGS)) } + verify { navController.navigate(directions, null) } + } + + @Test + fun handleToolbarBookmarkPress() = runBlockingTest { + val item = ToolbarMenu.Item.Bookmark + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BOOKMARK)) } + verify { bookmarkTapped(currentSession) } + } + + @Test + fun handleToolbarBookmarksPress() = runBlockingTest { + val item = ToolbarMenu.Item.Bookmarks + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + val directions = BrowserFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BOOKMARKS)) } + verify { navController.navigate(directions, null) } + } + + @Test + fun handleToolbarHistoryPress() = runBlockingTest { + val item = ToolbarMenu.Item.History + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + val directions = BrowserFragmentDirections.actionGlobalHistoryFragment() + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.HISTORY)) } + verify { navController.navigate(directions, null) } + } + + @Test + fun handleToolbarRequestDesktopOnPress() = runBlockingTest { + val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase = + mockk(relaxed = true) + val item = ToolbarMenu.Item.RequestDesktop(true) + + every { sessionUseCases.requestDesktopSite } returns requestDesktopSiteUseCase + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_ON)) } + verify { + requestDesktopSiteUseCase.invoke( + true, + currentSession + ) + } + } + + @Test + fun handleToolbarRequestDesktopOffPress() = runBlockingTest { + val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase = + mockk(relaxed = true) + val item = ToolbarMenu.Item.RequestDesktop(false) + + every { sessionUseCases.requestDesktopSite } returns requestDesktopSiteUseCase + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_OFF)) } + verify { + requestDesktopSiteUseCase.invoke( + false, + currentSession + ) + } + } + + @Test + fun handleToolbarAddToTopSitesPressed() = runBlockingTest { + val item = ToolbarMenu.Item.AddToTopSites + val addPinnedSiteUseCase: TopSitesUseCases.AddPinnedSiteUseCase = mockk(relaxed = true) + + every { topSitesUseCase.addPinnedSites } returns addPinnedSiteUseCase + every { + swipeRefreshLayout.context.getString(R.string.snackbar_added_to_top_sites) + } returns "Added to top sites!" + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + verify { addPinnedSiteUseCase.invoke(currentSession.title, currentSession.url) } + verify { snackbar.setText("Added to top sites!") } + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.ADD_TO_TOP_SITES)) } + } + + @Test + fun handleToolbarAddonsManagerPress() = runBlockingTest { + val item = ToolbarMenu.Item.AddonsManager + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.ADDONS_MANAGER)) } + } + + @Test + fun handleToolbarAddToHomeScreenPress() = runBlockingTest { + val item = ToolbarMenu.Item.AddToHomeScreen + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN)) } + } + + @Test + fun handleToolbarSharePress() = runBlockingTest { + val item = ToolbarMenu.Item.Share + + every { currentSession.url } returns "https://mozilla.org" + every { currentSession.title } returns "Mozilla" + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SHARE)) } + verify { + navController.navigate( + directionsEq(NavGraphDirections.actionGlobalShareFragment( + data = arrayOf(ShareData(url = "https://mozilla.org", title = "Mozilla")), + showPage = true + )) + ) + } + } + + @Test + fun handleToolbarFindInPagePress() = runBlockingTest { + val item = ToolbarMenu.Item.FindInPage + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + verify { findInPageLauncher() } + verify { metrics.track(Event.FindInPageOpened) } + } + + @Test + fun handleToolbarSaveToCollectionPressWhenAtLeastOneCollectionExists() = runBlockingTest { + val item = ToolbarMenu.Item.SaveToCollection + val cachedTabCollections: List = mockk(relaxed = true) + every { tabCollectionStorage.cachedTabCollections } returns cachedTabCollections + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + verify { + metrics.track( + Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION) + ) + } + verify { + metrics.track( + Event.CollectionSaveButtonPressed(DefaultBrowserToolbarController.TELEMETRY_BROWSER_IDENTIFIER) + ) + } + + val directions = BrowserFragmentDirections.actionGlobalCollectionCreationFragment( + saveCollectionStep = SaveCollectionStep.SelectCollection, + tabIds = arrayOf(currentSession.id), + selectedTabIds = arrayOf(currentSession.id) + ) + verify { navController.navigate(directionsEq(directions), null) } + } + + @Test + fun handleToolbarSaveToCollectionPressWhenNoCollectionsExists() = runBlockingTest { + val item = ToolbarMenu.Item.SaveToCollection + val cachedTabCollectionsEmpty: List = emptyList() + every { tabCollectionStorage.cachedTabCollections } returns cachedTabCollectionsEmpty + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION)) } + verify { + metrics.track( + Event.CollectionSaveButtonPressed( + DefaultBrowserToolbarController.TELEMETRY_BROWSER_IDENTIFIER + ) + ) + } + val directions = BrowserFragmentDirections.actionGlobalCollectionCreationFragment( + saveCollectionStep = SaveCollectionStep.NameCollection, + tabIds = arrayOf(currentSession.id), + selectedTabIds = arrayOf(currentSession.id) + ) + verify { navController.navigate(directionsEq(directions), null) } + } + + @Test + fun handleToolbarOpenInFenixPress() = runBlockingTest { + val controller = createController(scope = this, customTabSession = currentSession) + + val item = ToolbarMenu.Item.OpenInFenix + + every { currentSession.customTabConfig } returns mockk() + every { activity.startActivity(any()) } just Runs + + controller.handleToolbarItemInteraction(item) + + verify { sessionFeature.release() } + verify { currentSession.customTabConfig = null } + verify { sessionManager.select(currentSession) } + verify { activity.startActivity(openInFenixIntent) } + verify { activity.finish() } + } + + @Test + fun handleToolbarQuitPress() = runBlockingTest { + val item = ToolbarMenu.Item.Quit + val testScope = this + + val controller = createController(scope = testScope) + + controller.handleToolbarItemInteraction(item) + + verify { deleteAndQuit(activity, testScope, null) } + } + + @Test + fun handleToolbarReaderModeAppearancePress() = runBlockingTest { + val item = ToolbarMenu.Item.ReaderModeAppearance + + val controller = createController(scope = this) + + controller.handleToolbarItemInteraction(item) + + verify { readerModeController.showControls() } + verify { metrics.track(Event.ReaderModeAppearanceOpened) } + } + + @Test + fun handleToolbarOpenInAppPress() = runBlockingTest { + val item = ToolbarMenu.Item.OpenInApp + + val controller = createController(scope = this) + + controller.handleToolbarItemInteraction(item) + + verify { settings.openInAppOpened = true } + } + + private fun createController( + scope: CoroutineScope, + activity: HomeActivity = this.activity, + customTabSession: Session? = null + ) = DefaultBrowserToolbarMenuController( + activity = activity, + navController = navController, + metrics = metrics, + settings = settings, + findInPageLauncher = findInPageLauncher, + browserAnimator = browserAnimator, + customTabSession = customTabSession, + openInFenixIntent = openInFenixIntent, + scope = scope, + swipeRefresh = swipeRefreshLayout, + tabCollectionStorage = tabCollectionStorage, + bookmarkTapped = bookmarkTapped, + readerModeController = readerModeController, + sessionManager = sessionManager, + sessionFeature = sessionFeatureWrapper + ).apply { + ioScope = scope + } +} diff --git a/app/src/test/java/org/mozilla/fenix/components/toolbar/TabCounterMenuTest.kt b/app/src/test/java/org/mozilla/fenix/components/toolbar/TabCounterMenuTest.kt new file mode 100644 index 0000000000..ae69260903 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/components/toolbar/TabCounterMenuTest.kt @@ -0,0 +1,95 @@ +/* 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.components.toolbar + +import android.content.Context +import androidx.appcompat.view.ContextThemeWrapper +import io.mockk.mockk +import io.mockk.verifyAll +import mozilla.components.concept.menu.candidate.DividerMenuCandidate +import mozilla.components.concept.menu.candidate.DrawableMenuIcon +import mozilla.components.concept.menu.candidate.TextMenuCandidate +import mozilla.components.support.test.robolectric.testContext +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.R +import org.mozilla.fenix.browser.browsingmode.BrowsingMode +import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.components.metrics.MetricController +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +@RunWith(FenixRobolectricTestRunner::class) +class TabCounterMenuTest { + + private lateinit var context: Context + private lateinit var metrics: MetricController + private lateinit var onItemTapped: (TabCounterMenu.Item) -> Unit + private lateinit var menu: TabCounterMenu + + @Before + fun setup() { + context = ContextThemeWrapper(testContext, R.style.NormalTheme) + metrics = mockk(relaxed = true) + onItemTapped = mockk(relaxed = true) + menu = TabCounterMenu(context, metrics, onItemTapped) + } + + @Test + fun `all items use primary text color styling`() { + val items = menu.menuItems(showOnly = null) + assertEquals(4, items.size) + + val textItems = items.mapNotNull { it as? TextMenuCandidate } + assertEquals(3, textItems.size) + + val primaryTextColor = context.getColor(R.color.primary_text_normal_theme) + for (item in textItems) { + assertEquals(primaryTextColor, item.textStyle.color) + assertEquals(primaryTextColor, (item.start as DrawableMenuIcon).tint) + } + } + + @Test + fun `return only the new tab item`() { + val items = menu.menuItems(showOnly = BrowsingMode.Normal) + assertEquals(1, items.size) + + val item = items[0] as TextMenuCandidate + assertEquals("New tab", item.text) + item.onClick() + + verifyAll { + metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_TAB)) + onItemTapped(TabCounterMenu.Item.NewTab(BrowsingMode.Normal)) + } + } + + @Test + fun `return only the new private tab item`() { + val items = menu.menuItems(showOnly = BrowsingMode.Private) + assertEquals(1, items.size) + + val item = items[0] as TextMenuCandidate + assertEquals("New private tab", item.text) + item.onClick() + + verifyAll { + metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_PRIVATE_TAB)) + onItemTapped(TabCounterMenu.Item.NewTab(BrowsingMode.Private)) + } + } + + @Test + fun `return two new tab items and a close button`() { + val (newTab, newPrivateTab, divider, closeTab) = menu.menuItems(showOnly = null) + + assertEquals("New tab", (newTab as TextMenuCandidate).text) + assertEquals("New private tab", (newPrivateTab as TextMenuCandidate).text) + assertEquals("Close tab", (closeTab as TextMenuCandidate).text) + assertEquals(DividerMenuCandidate(), divider) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/settings/deletebrowsingdata/DefaultDeleteBrowsingDataControllerTest.kt b/app/src/test/java/org/mozilla/fenix/settings/deletebrowsingdata/DefaultDeleteBrowsingDataControllerTest.kt index 50be4f470e..038d4be4f9 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/deletebrowsingdata/DefaultDeleteBrowsingDataControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/deletebrowsingdata/DefaultDeleteBrowsingDataControllerTest.kt @@ -4,13 +4,15 @@ package org.mozilla.fenix.settings.deletebrowsingdata +import io.mockk.coVerify import io.mockk.mockk +import io.mockk.spyk import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.GlobalScope.coroutineContext -import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.runBlockingTest +import mozilla.components.browser.icons.BrowserIcons import mozilla.components.concept.engine.Engine import mozilla.components.concept.storage.HistoryStorage import mozilla.components.feature.tabs.TabsUseCases @@ -29,6 +31,7 @@ class DefaultDeleteBrowsingDataControllerTest { private var removeAllTabs: TabsUseCases.RemoveAllTabsUseCase = mockk(relaxed = true) private var historyStorage: HistoryStorage = mockk(relaxed = true) private var permissionStorage: PermissionStorage = mockk(relaxed = true) + private var iconsStorage: BrowserIcons = mockk(relaxed = true) private val engine: Engine = mockk(relaxed = true) private lateinit var controller: DefaultDeleteBrowsingDataController @@ -38,6 +41,7 @@ class DefaultDeleteBrowsingDataControllerTest { removeAllTabs = removeAllTabs, historyStorage = historyStorage, permissionStorage = permissionStorage, + iconsStorage = iconsStorage, engine = engine, coroutineContext = coroutineContext ) @@ -55,12 +59,14 @@ class DefaultDeleteBrowsingDataControllerTest { @Test fun deleteBrowsingData() = runBlockingTest { + controller = spyk(controller) controller.deleteBrowsingData() - verify { + coVerify { engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.DOM_STORAGES)) + historyStorage.deleteEverything() + iconsStorage.clear() } - verify { launch { historyStorage.deleteEverything() } } } @Test diff --git a/app/src/test/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuitTest.kt b/app/src/test/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuitTest.kt index 3e3caad62b..5ae94620ca 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuitTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuitTest.kt @@ -6,6 +6,7 @@ package org.mozilla.fenix.settings.deletebrowsingdata +import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import io.mockk.verify @@ -13,6 +14,7 @@ import io.mockk.verifyOrder import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.runBlockingTest +import mozilla.components.browser.icons.BrowserIcons import mozilla.components.browser.storage.sync.PlacesHistoryStorage import mozilla.components.concept.engine.Engine import mozilla.components.feature.tabs.TabsUseCases @@ -41,6 +43,7 @@ class DeleteAndQuitTest { private val tabUseCases: TabsUseCases = mockk(relaxed = true) private val historyStorage: PlacesHistoryStorage = mockk(relaxed = true) private val permissionStorage: PermissionStorage = mockk(relaxed = true) + private val iconsStorage: BrowserIcons = mockk() private val engine: Engine = mockk(relaxed = true) private val removeAllTabsUseCases: TabsUseCases.RemoveAllTabsUseCase = mockk(relaxed = true) private val snackbar = mockk(relaxed = true) @@ -53,6 +56,7 @@ class DeleteAndQuitTest { every { tabUseCases.removeAllTabs } returns removeAllTabsUseCases every { activity.components.core.engine } returns engine every { activity.components.settings } returns settings + every { activity.components.core.icons } returns iconsStorage } @Test @@ -69,8 +73,6 @@ class DeleteAndQuitTest { } verify(exactly = 0) { - historyStorage - engine.clearData( Engine.BrowsingData.select( Engine.BrowsingData.COOKIES @@ -81,6 +83,11 @@ class DeleteAndQuitTest { engine.clearData(Engine.BrowsingData.allCaches()) } + + coVerify(exactly = 0) { + historyStorage.deleteEverything() + iconsStorage.clear() + } } @Ignore("Intermittently failing; will be fixed with #5406.") @@ -115,9 +122,12 @@ class DeleteAndQuitTest { engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.DOM_STORAGES)) - historyStorage - activity.finish() } + + coVerify { + historyStorage.deleteEverything() + iconsStorage.clear() + } } } diff --git a/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt b/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt index 6e1e9a51da..37d82cbef2 100644 --- a/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt +++ b/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt @@ -128,13 +128,13 @@ class SettingsTest { fun showLoginsDialogWarningSync() { // When just created // Then - assertEquals(0, settings.loginsSecureWarningSyncCount) + assertEquals(0, settings.loginsSecureWarningSyncCount.value) // When settings.incrementShowLoginsSecureWarningSyncCount() // Then - assertEquals(1, settings.loginsSecureWarningSyncCount) + assertEquals(1, settings.loginsSecureWarningSyncCount.value) } @Test @@ -154,13 +154,13 @@ class SettingsTest { fun showLoginsDialogWarning() { // When just created // Then - assertEquals(0, settings.loginsSecureWarningCount) + assertEquals(0, settings.loginsSecureWarningCount.value) // When settings.incrementShowLoginsSecureWarningCount() // Then - assertEquals(1, settings.loginsSecureWarningCount) + assertEquals(1, settings.loginsSecureWarningCount.value) } @Test diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 04211a13b5..988ed7b571 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "57.0.20200826190111" + const val VERSION = "57.0.20200829130559" } diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index eae088417f..c10b9775ec 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -43,7 +43,6 @@ object Versions { const val google_ads_id_version = "16.0.0" const val google_play_store_version = "1.8.0" - const val google_play_core_ktx_version = "1.8.1" const val airbnb_lottie = "3.4.0" } @@ -214,7 +213,6 @@ object Deps { // Required for in-app reviews const val google_play_store = "com.google.android.play:core:${Versions.google_play_store_version}" - const val google_play_core_ktx = "com.google.android.play:core-ktx:${Versions.google_play_core_ktx_version}" const val lottie = "com.airbnb.android:lottie:${Versions.airbnb_lottie}"