From 896c1b787534e41cc948f9c646e760adac39a071 Mon Sep 17 00:00:00 2001 From: mcarare Date: Tue, 22 Sep 2020 12:08:50 +0300 Subject: [PATCH 01/36] For #15268: Use safe navigation to addon details fragments. --- .../java/org/mozilla/fenix/addons/AddonsManagementView.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementView.kt b/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementView.kt index 2a6370ed6..4e9e24cab 100644 --- a/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementView.kt +++ b/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementView.kt @@ -7,6 +7,8 @@ package org.mozilla.fenix.addons import androidx.navigation.NavController import mozilla.components.feature.addons.Addon import mozilla.components.feature.addons.ui.AddonsManagerAdapterDelegate +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.navigateSafe /** * View used for managing add-ons. @@ -37,7 +39,7 @@ class AddonsManagementView( AddonsManagementFragmentDirections.actionAddonsManagementFragmentToInstalledAddonDetails( addon ) - navController.navigate(directions) + navController.navigateSafe(R.id.addonsManagementFragment, directions) } private fun showDetailsFragment(addon: Addon) { @@ -45,7 +47,7 @@ class AddonsManagementView( AddonsManagementFragmentDirections.actionAddonsManagementFragmentToAddonDetailsFragment( addon ) - navController.navigate(directions) + navController.navigateSafe(R.id.addonsManagementFragment, directions) } private fun showNotYetSupportedAddonFragment(unsupportedAddons: List) { From e3b5dcbfec1aaad6058222123e11102865b8ce52 Mon Sep 17 00:00:00 2001 From: mcarare Date: Tue, 22 Sep 2020 13:16:58 +0300 Subject: [PATCH 02/36] For #15268: Update unit tests for safe navigation to details fragment. --- .../fenix/addons/AddonsManagementViewTest.kt | 56 +++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/app/src/test/java/org/mozilla/fenix/addons/AddonsManagementViewTest.kt b/app/src/test/java/org/mozilla/fenix/addons/AddonsManagementViewTest.kt index 901910ed0..11ee765ce 100644 --- a/app/src/test/java/org/mozilla/fenix/addons/AddonsManagementViewTest.kt +++ b/app/src/test/java/org/mozilla/fenix/addons/AddonsManagementViewTest.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.addons import androidx.navigation.NavController +import androidx.navigation.NavDestination import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.RelaxedMockK @@ -15,6 +16,8 @@ import mozilla.components.feature.addons.ui.AddonsManagerAdapterDelegate import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mozilla.fenix.R +import org.mozilla.fenix.addons.AddonsManagementFragmentDirections.Companion.actionAddonsManagementFragmentToAddonDetailsFragment import org.mozilla.fenix.addons.AddonsManagementFragmentDirections.Companion.actionAddonsManagementFragmentToInstalledAddonDetails import org.mozilla.fenix.ext.directionsEq import org.mozilla.fenix.helpers.FenixRobolectricTestRunner @@ -37,12 +40,16 @@ class AddonsManagementViewTest { val addon = mockk { every { isInstalled() } returns true } + + every { navController.currentDestination } returns NavDestination("").apply { + id = R.id.addonsManagementFragment + } + managementView.onAddonItemClicked(addon) + val expected = actionAddonsManagementFragmentToInstalledAddonDetails(addon) verify { - navController.navigate( - directionsEq(actionAddonsManagementFragmentToInstalledAddonDetails(addon)) - ) + navController.navigate(directionsEq(expected)) } } @@ -51,14 +58,55 @@ class AddonsManagementViewTest { val addon = mockk { every { isInstalled() } returns false } + + every { navController.currentDestination } returns NavDestination("").apply { + id = R.id.addonsManagementFragment + } + managementView.onAddonItemClicked(addon) - val expected = AddonsManagementFragmentDirections.actionAddonsManagementFragmentToAddonDetailsFragment(addon) + val expected = actionAddonsManagementFragmentToAddonDetailsFragment(addon) verify { navController.navigate(directionsEq(expected)) } } + @Test + fun `onAddonItemClicked on not installed addon does not navigate if not currently on addonsManagementFragment`() { + val addon = mockk { + every { isInstalled() } returns false + } + + every { navController.currentDestination } returns NavDestination("").apply { + id = R.id.settingsFragment + } + + managementView.onAddonItemClicked(addon) + + val expected = actionAddonsManagementFragmentToAddonDetailsFragment(addon) + verify(exactly = 0) { + navController.navigate(directionsEq(expected)) + } + } + + @Test + fun `onAddonItemClicked on installed addon does not navigate if not currently on addonsManagementFragment`() { + val addon = mockk { + every { isInstalled() } returns true + } + + every { navController.currentDestination } returns NavDestination("").apply { + id = R.id.settingsFragment + } + + managementView.onAddonItemClicked(addon) + + val expected = actionAddonsManagementFragmentToAddonDetailsFragment(addon) + verify(exactly = 0) { + navController.navigate(directionsEq(expected)) + } + } + @Test fun `onInstallAddonButtonClicked shows permission dialog`() { val addon = mockk() From 9464d4ebd55a2d4ba7fa37d2fc072b0b779016bd Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Tue, 22 Sep 2020 14:18:08 +0000 Subject: [PATCH 03/36] Update Android Components version to 60.0.20200922130109. --- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 36b4427cd..a562cf645 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 = "60.0.20200921130100" + const val VERSION = "60.0.20200922130109" } From 030d6a5b36b2f0c3c91a87673e256f4b7667aa1c Mon Sep 17 00:00:00 2001 From: ekager Date: Fri, 18 Sep 2020 22:34:47 -0700 Subject: [PATCH 04/36] For #15121 - Ensure tabs removed while on browser select an available parent session --- .../java/org/mozilla/fenix/browser/BaseBrowserFragment.kt | 7 ++++++- .../fenix/components/toolbar/BrowserToolbarController.kt | 4 +++- .../toolbar/DefaultBrowserToolbarControllerTest.kt | 7 +------ 3 files changed, 10 insertions(+), 8 deletions(-) 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 4b662e4b1..cff3b5a4d 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -940,7 +940,12 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session true } else { if (session.hasParentSession) { - requireComponents.useCases.tabsUseCases.removeTab(session) + // The removeTab use case does not currently select a parent session, so + // we are using sessionManager.remove + requireComponents.core.sessionManager.remove( + session, + selectParentIfExists = true + ) } // We want to return to home if this session didn't have a parent session to select. val goToOverview = !session.hasParentSession 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 5b70eae28..56d2c3711 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 @@ -145,7 +145,9 @@ class DefaultBrowserToolbarController( ) } else { onCloseTab.invoke(it) - activity.components.useCases.tabsUseCases.removeTab.invoke(it) + // The removeTab use case does not currently select a parent session, so + // we are using sessionManager.remove + sessionManager.remove(it, selectParentIfExists = true) } } } 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 124cf312a..4bd127419 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 @@ -21,7 +21,6 @@ import mozilla.components.browser.session.SessionManager import mozilla.components.concept.engine.EngineView import mozilla.components.feature.search.SearchUseCases import mozilla.components.feature.session.SessionUseCases -import mozilla.components.feature.tabs.TabsUseCases import mozilla.components.feature.top.sites.TopSitesUseCases import org.junit.Assert.assertEquals import org.junit.Before @@ -200,17 +199,13 @@ class DefaultBrowserToolbarControllerTest { @Test fun handleToolbarCloseTabPress() { - val tabsUseCases: TabsUseCases = mockk(relaxed = true) - val removeTabUseCase: TabsUseCases.RemoveTabUseCase = mockk(relaxed = true) 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() controller.handleTabCounterItemInteraction(item) - verify { removeTabUseCase.invoke(currentSession) } + verify { sessionManager.remove(currentSession, selectParentIfExists = true) } } @Test From a9075ff87be29c10f77db699f197694694ff2a2d Mon Sep 17 00:00:00 2001 From: ekager Date: Mon, 21 Sep 2020 11:23:06 -0700 Subject: [PATCH 05/36] For #15272 - Remove bottom margin for toolbar on enter fullscreen --- .../main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt | 2 ++ 1 file changed, 2 insertions(+) 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 cff3b5a4d..b8921157d 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -1108,6 +1108,8 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session .show() activity?.enterToImmersiveMode() browserToolbarView.view.isVisible = false + val browserEngine = swipeRefresh.layoutParams as CoordinatorLayout.LayoutParams + browserEngine.bottomMargin = 0 engineView.setDynamicToolbarMaxHeight(0) browserToolbarView.expand() From 94d9a2a1ed50fdfa71175e4db136adcbd0389b17 Mon Sep 17 00:00:00 2001 From: mcarare Date: Mon, 21 Sep 2020 15:01:20 +0300 Subject: [PATCH 06/36] For #15263: Properly update open links in app preference. --- .../main/java/org/mozilla/fenix/settings/SettingsFragment.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt index b00a82695..6f0719487 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt @@ -321,6 +321,8 @@ class SettingsFragment : PreferenceFragmentCompat() { val preferenceRemoteDebugging = findPreference(debuggingKey) val preferenceMakeDefaultBrowser = requirePreference(R.string.pref_key_make_default_browser) + val preferenceOpenLinksInExternalApp = + findPreference(getPreferenceKey(R.string.pref_key_open_links_in_external_app)) preferencePrivateBrowsing.icon.mutate().apply { setTint(requireContext().getColorFromAttr(R.attr.primaryText)) @@ -345,6 +347,8 @@ class SettingsFragment : PreferenceFragmentCompat() { preferenceMakeDefaultBrowser.onPreferenceClickListener = getClickListenerForMakeDefaultBrowser() + preferenceOpenLinksInExternalApp?.onPreferenceChangeListener = SharedPreferenceUpdater() + val preferenceFxAOverride = findPreference(getPreferenceKey(R.string.pref_key_override_fxa_server)) val preferenceSyncOverride = From 24983af94e8a916113b81c86ad3a84510ed0ddbb Mon Sep 17 00:00:00 2001 From: ekager Date: Mon, 21 Sep 2020 18:16:02 -0700 Subject: [PATCH 07/36] For #15291 - Limit current CFRs to show max one every 3 days --- .../mozilla/fenix/browser/BrowserFragment.kt | 2 +- .../browser/OpenInAppOnboardingObserver.kt | 3 +- .../org/mozilla/fenix/cfr/SearchWidgetCFR.kt | 3 +- .../org/mozilla/fenix/home/HomeFragment.kt | 15 +++++---- .../fenix/shortcut/PwaOnboardingObserver.kt | 3 +- .../TrackingProtectionOverlay.kt | 11 ++++--- .../java/org/mozilla/fenix/utils/Settings.kt | 31 +++++++++++++------ app/src/main/res/values/preference_keys.xml | 1 + .../TrackingProtectionOverlayTest.kt | 12 +++---- .../org/mozilla/fenix/utils/SettingsTest.kt | 22 ++++++++++--- 10 files changed, 69 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt index 11310a2fe..ebd51b122 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -160,7 +160,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { } session?.register(toolbarSessionObserver, viewLifecycleOwner, autoPause = true) - if (settings.shouldShowOpenInAppBanner && session != null) { + if (settings.shouldShowOpenInAppCfr && session != null) { openInAppOnboardingObserver = OpenInAppOnboardingObserver( context = context, navController = findNavController(), diff --git a/app/src/main/java/org/mozilla/fenix/browser/OpenInAppOnboardingObserver.kt b/app/src/main/java/org/mozilla/fenix/browser/OpenInAppOnboardingObserver.kt index 82e6e5aea..43be8b169 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/OpenInAppOnboardingObserver.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/OpenInAppOnboardingObserver.kt @@ -42,7 +42,7 @@ class OpenInAppOnboardingObserver( if (!loading && !settings.openLinksInExternalApp && - settings.shouldShowOpenInAppBanner && + settings.shouldShowOpenInAppCfr && appLink(session.url).hasExternalApp() ) { infoBanner = InfoBanner( @@ -60,6 +60,7 @@ class OpenInAppOnboardingObserver( infoBanner?.showBanner() sessionDomainForDisplayedBanner = session.url.tryGetHostFromUrl() + settings.lastCfrShownTimeInMillis = System.currentTimeMillis() settings.shouldShowOpenInAppBanner = false } } diff --git a/app/src/main/java/org/mozilla/fenix/cfr/SearchWidgetCFR.kt b/app/src/main/java/org/mozilla/fenix/cfr/SearchWidgetCFR.kt index 07d017ce0..2b37185b0 100644 --- a/app/src/main/java/org/mozilla/fenix/cfr/SearchWidgetCFR.kt +++ b/app/src/main/java/org/mozilla/fenix/cfr/SearchWidgetCFR.kt @@ -35,7 +35,7 @@ class SearchWidgetCFR( fun displayIfNecessary() { if (settings.isInSearchWidgetExperiment && - settings.shouldDisplaySearchWidgetCFR() && + settings.shouldDisplaySearchWidgetCfr() && !isShown ) { isShown = true @@ -45,6 +45,7 @@ class SearchWidgetCFR( @Suppress("InflateParams") private fun showSearchWidgetCFR() { + settings.lastCfrShownTimeInMillis = System.currentTimeMillis() settings.incrementSearchWidgetCFRDisplayed() val searchWidgetCFRDialog = Dialog(context) 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 932f38a98..12a926898 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -320,7 +320,8 @@ class HomeFragment : Fragment() { ) view.homeAppBar.updateLayoutParams { - topMargin = resources.getDimensionPixelSize(R.dimen.home_fragment_top_toolbar_header_margin) + topMargin = + resources.getDimensionPixelSize(R.dimen.home_fragment_top_toolbar_header_margin) } } ToolbarPosition.BOTTOM -> { @@ -471,7 +472,8 @@ class HomeFragment : Fragment() { private fun removeAllTabsAndShowSnackbar(sessionCode: String) { val tabs = sessionManager.sessionsOfType(private = sessionCode == ALL_PRIVATE_TABS).toList() val selectedIndex = sessionManager - .selectedSession?.let { sessionManager.sessions.indexOf(it) } ?: SessionManager.NO_SELECTION + .selectedSession?.let { sessionManager.sessions.indexOf(it) } + ?: SessionManager.NO_SELECTION val snapshot = tabs .map(sessionManager::createSessionSnapshot) @@ -597,8 +599,8 @@ class HomeFragment : Fragment() { }, owner = this@HomeFragment.viewLifecycleOwner) } - if (context.settings().showPrivateModeContextualFeatureRecommender && - browsingModeManager.mode.isPrivate + if (browsingModeManager.mode.isPrivate && + context.settings().showPrivateModeCfr ) { recommendPrivateBrowsingShortcut() } @@ -683,8 +685,8 @@ class HomeFragment : Fragment() { } private fun recommendPrivateBrowsingShortcut() { - context?.let { - val layout = LayoutInflater.from(it) + context?.let { context -> + val layout = LayoutInflater.from(context) .inflate(R.layout.pbm_shortcut_popup, null) val privateBrowsingRecommend = PopupWindow( @@ -712,6 +714,7 @@ class HomeFragment : Fragment() { // We want to show the popup only after privateBrowsingButton is available. // Otherwise, we will encounter an activity token error. privateBrowsingButton.post { + context.settings().lastCfrShownTimeInMillis = System.currentTimeMillis() privateBrowsingRecommend.showAsDropDown( privateBrowsingButton, 0, CFR_Y_OFFSET, Gravity.TOP or Gravity.END ) diff --git a/app/src/main/java/org/mozilla/fenix/shortcut/PwaOnboardingObserver.kt b/app/src/main/java/org/mozilla/fenix/shortcut/PwaOnboardingObserver.kt index 10dd4132f..cc30b7421 100644 --- a/app/src/main/java/org/mozilla/fenix/shortcut/PwaOnboardingObserver.kt +++ b/app/src/main/java/org/mozilla/fenix/shortcut/PwaOnboardingObserver.kt @@ -24,10 +24,11 @@ class PwaOnboardingObserver( override fun onLoadingStateChanged(session: Session, loading: Boolean) { if (!loading && webAppUseCases.isInstallable() && !settings.userKnowsAboutPwas) { settings.incrementVisitedInstallableCount() - if (settings.shouldShowPwaOnboarding) { + if (settings.shouldShowPwaCfr) { val directions = BrowserFragmentDirections.actionBrowserFragmentToPwaOnboardingDialogFragment() navController.nav(R.id.browserFragment, directions) + settings.lastCfrShownTimeInMillis = System.currentTimeMillis() settings.userKnowsAboutPwas = true } } diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionOverlay.kt b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionOverlay.kt index 285e4d454..2902b2392 100644 --- a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionOverlay.kt +++ b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionOverlay.kt @@ -43,9 +43,9 @@ class TrackingProtectionOverlay( } private fun shouldShowTrackingProtectionOnboarding(session: Session) = - settings.shouldShowTrackingProtectionOnboarding && - session.trackerBlockingEnabled && - session.trackersBlocked.isNotEmpty() + session.trackerBlockingEnabled && + session.trackersBlocked.isNotEmpty() && + settings.shouldShowTrackingProtectionCfr @Suppress("MagicNumber", "InflateParams") private fun showTrackingProtectionOnboarding() { @@ -57,9 +57,9 @@ class TrackingProtectionOverlay( if (event.action == MotionEvent.ACTION_DOWN) { metrics.track(Event.ContextualHintETPOutsideTap) } - return super.onTouchEvent(event) - } + return super.onTouchEvent(event) } + } val layout = LayoutInflater.from(context) .inflate(R.layout.tracking_protection_onboarding_popup, null) @@ -121,6 +121,7 @@ class TrackingProtectionOverlay( metrics.track(Event.ContextualHintETPDisplayed) trackingOnboardingDialog.show() + settings.lastCfrShownTimeInMillis = System.currentTimeMillis() settings.incrementTrackingProtectionOnboardingCount() } 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 713976beb..ff4bed759 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -62,6 +62,7 @@ class Settings(private val appContext: Context) : PreferencesHolder { private const val CFR_COUNT_CONDITION_FOCUS_NOT_INSTALLED = 3 const val ONE_DAY_MS = 60 * 60 * 24 * 1000L + const val THREE_DAYS_MS = 3 * ONE_DAY_MS const val ONE_WEEK_MS = 60 * 60 * 24 * 7 * 1000L const val ONE_MONTH_MS = (60 * 60 * 24 * 365 * 1000L) / 12 @@ -115,6 +116,14 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = 0L ) + var lastCfrShownTimeInMillis by longPreference( + appContext.getPreferenceKey(R.string.pref_key_last_cfr_shown_time), + default = 0L + ) + + val canShowCfr: Boolean + get() = (System.currentTimeMillis() - lastCfrShownTimeInMillis) > THREE_DAYS_MS + var waitToShowPageUntilFirstPaint by featureFlagPreference( appContext.getPreferenceKey(R.string.pref_key_wait_first_paint), default = false, @@ -191,11 +200,10 @@ class Settings(private val appContext: Context) : PreferencesHolder { private val isActiveSearcher: Boolean get() = activeSearchCount.value > 2 - fun shouldDisplaySearchWidgetCFR(): Boolean = - isActiveSearcher && - searchWidgetCFRDismissCount.underMaxCount() && - !searchWidgetInstalled && - !searchWidgetCFRManuallyDismissed + fun shouldDisplaySearchWidgetCfr(): Boolean = canShowCfr && isActiveSearcher && + searchWidgetCFRDismissCount.underMaxCount() && + !searchWidgetInstalled && + !searchWidgetCFRManuallyDismissed private val searchWidgetCFRDisplayCount = counterPreference( appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_display_count) @@ -284,8 +292,8 @@ class Settings(private val appContext: Context) : PreferencesHolder { private var trackingProtectionOnboardingShownThisSession = false var isOverrideTPPopupsForPerformanceTest = false - val shouldShowTrackingProtectionOnboarding: Boolean - get() = !isOverrideTPPopupsForPerformanceTest && + val shouldShowTrackingProtectionCfr: Boolean + get() = !isOverrideTPPopupsForPerformanceTest && canShowCfr && (trackingProtectionOnboardingCount.underMaxCount() && !trackingProtectionOnboardingShownThisSession) @@ -650,8 +658,9 @@ class Settings(private val appContext: Context) : PreferencesHolder { private val userNeedsToVisitInstallableSites: Boolean get() = pwaInstallableVisitCount.underMaxCount() - val shouldShowPwaOnboarding: Boolean + val shouldShowPwaCfr: Boolean get() { + if (!canShowCfr) return false // We only want to show this on the 3rd time a user visits a site if (userNeedsToVisitInstallableSites) return false @@ -677,6 +686,9 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = true ) + val shouldShowOpenInAppCfr: Boolean + get() = canShowCfr && shouldShowOpenInAppBanner + @VisibleForTesting(otherwise = PRIVATE) internal val trackingProtectionOnboardingCount = counterPreference( appContext.getPreferenceKey(R.string.pref_key_tracking_protection_onboarding), @@ -833,8 +845,9 @@ class Settings(private val appContext: Context) : PreferencesHolder { appContext.getPreferenceKey(R.string.pref_key_private_mode_opened) ) - val showPrivateModeContextualFeatureRecommender: Boolean + val showPrivateModeCfr: Boolean get() { + if (!canShowCfr) return false val focusInstalled = MozillaProductDetector .getInstalledMozillaProducts(appContext as Application) .contains(MozillaProductDetector.MozillaProducts.FOCUS.productName) diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index fd75d44f1..0e7933e31 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -60,6 +60,7 @@ pref_key_install_pwa_visits pref_key_times_app_opened pref_key_last_review_prompt_shown_time + pref_key_last_cfr_shown_time pref_key_telemetry diff --git a/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionOverlayTest.kt b/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionOverlayTest.kt index b408aa945..81050a3e4 100644 --- a/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionOverlayTest.kt +++ b/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionOverlayTest.kt @@ -44,7 +44,7 @@ class TrackingProtectionOverlayTest { @Test fun `no-op when loading`() { - every { settings.shouldShowTrackingProtectionOnboarding } returns true + every { settings.shouldShowTrackingProtectionCfr } returns true every { session.trackerBlockingEnabled } returns true every { session.trackersBlocked } returns listOf(mockk()) @@ -54,7 +54,7 @@ class TrackingProtectionOverlayTest { @Test fun `no-op when should not show onboarding`() { - every { settings.shouldShowTrackingProtectionOnboarding } returns false + every { settings.shouldShowTrackingProtectionCfr } returns false overlay.onLoadingStateChanged(session, loading = false) verify(exactly = 0) { settings.incrementTrackingProtectionOnboardingCount() } @@ -62,7 +62,7 @@ class TrackingProtectionOverlayTest { @Test fun `no-op when tracking protection disabled`() { - every { settings.shouldShowTrackingProtectionOnboarding } returns true + every { settings.shouldShowTrackingProtectionCfr } returns true every { session.trackerBlockingEnabled } returns false overlay.onLoadingStateChanged(session, loading = false) @@ -71,7 +71,7 @@ class TrackingProtectionOverlayTest { @Test fun `no-op when no trackers blocked`() { - every { settings.shouldShowTrackingProtectionOnboarding } returns true + every { settings.shouldShowTrackingProtectionCfr } returns true every { session.trackerBlockingEnabled } returns true every { session.trackersBlocked } returns emptyList() @@ -82,7 +82,7 @@ class TrackingProtectionOverlayTest { @Test fun `show onboarding when trackers are blocked`() { every { toolbar.hasWindowFocus() } returns true - every { settings.shouldShowTrackingProtectionOnboarding } returns true + every { settings.shouldShowTrackingProtectionCfr } returns true every { session.trackerBlockingEnabled } returns true every { session.trackersBlocked } returns listOf(mockk()) @@ -93,7 +93,7 @@ class TrackingProtectionOverlayTest { @Test fun `no-op when toolbar doesn't have focus`() { every { toolbar.hasWindowFocus() } returns false - every { settings.shouldShowTrackingProtectionOnboarding } returns true + every { settings.shouldShowTrackingProtectionCfr } returns true every { session.trackerBlockingEnabled } returns true every { session.trackersBlocked } returns listOf(mockk()) 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 480e8285b..456b946e8 100644 --- a/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt +++ b/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt @@ -117,6 +117,20 @@ class SettingsTest { assertFalse(settings.isRemoteDebuggingEnabled) } + @Test + fun canShowCfrTest() { + // When just created + // Then + assertEquals(0L, settings.lastCfrShownTimeInMillis) + assertTrue(settings.canShowCfr) + + // When + settings.lastCfrShownTimeInMillis = System.currentTimeMillis() + + // Then + assertFalse(settings.canShowCfr) + } + @Test fun isTelemetryEnabled() { // When just created @@ -394,25 +408,25 @@ class SettingsTest { fun showPwaFragment() { // When just created // Then - assertFalse(settings.shouldShowPwaOnboarding) + assertFalse(settings.shouldShowPwaCfr) // When visited once settings.incrementVisitedInstallableCount() // Then - assertFalse(settings.shouldShowPwaOnboarding) + assertFalse(settings.shouldShowPwaCfr) // When visited twice settings.incrementVisitedInstallableCount() // Then - assertFalse(settings.shouldShowPwaOnboarding) + assertFalse(settings.shouldShowPwaCfr) // When visited thrice settings.incrementVisitedInstallableCount() // Then - assertTrue(settings.shouldShowPwaOnboarding) + assertTrue(settings.shouldShowPwaCfr) } @Test From 657ce60f8d6df597748caaa50b42efe9d9031243 Mon Sep 17 00:00:00 2001 From: aprabhakara Date: Wed, 23 Sep 2020 01:23:26 +0530 Subject: [PATCH 08/36] For #15262: Updated margins --- app/src/main/res/layout/fragment_turn_on_sync.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/layout/fragment_turn_on_sync.xml b/app/src/main/res/layout/fragment_turn_on_sync.xml index c933921d4..649aa056f 100644 --- a/app/src/main/res/layout/fragment_turn_on_sync.xml +++ b/app/src/main/res/layout/fragment_turn_on_sync.xml @@ -23,7 +23,7 @@ android:text="@string/sign_in_with_camera" android:textAppearance="@style/Header16TextStyle" android:textColor="?primaryText" - android:textSize="18sp" + android:textSize="20sp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -58,7 +58,7 @@ android:id="@+id/signInScanButton" style="@style/PositiveButton" android:text="@string/sign_in_ready_for_scan" - android:layout_marginVertical="16dp" + android:layout_marginVertical="24dp" app:icon="@drawable/ic_qr" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -68,9 +68,9 @@ android:id="@+id/signInEmailButton" style="@style/NeutralButton" android:text="@string/sign_in_with_email" - android:layout_margin="0dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" + android:layout_marginVertical="12dp" app:layout_constraintTop_toBottomOf="@id/signInScanButton" app:layout_constraintBottom_toTopOf="@id/createAccount"/> @@ -83,6 +83,7 @@ app:layout_constraintTop_toBottomOf="@id/signInEmailButton" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" + android:layout_marginVertical="24dp" tools:text="@string/sign_in_create_account_text"/> From baccb153b734c9f0f8680da7e785336595519f18 Mon Sep 17 00:00:00 2001 From: person808 Date: Tue, 22 Sep 2020 14:36:10 -0700 Subject: [PATCH 09/36] For #15219 - Fix tab tray handle size. Also use dimens for the tab history handle. --- app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt | 4 ++-- app/src/main/res/layout/component_tabhistory.xml | 4 ++-- app/src/main/res/layout/component_tabstray.xml | 4 ++-- app/src/main/res/values/dimens.xml | 5 +++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt index f0736b329..948fb9e88 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt @@ -433,14 +433,14 @@ class TabTrayView( if (multiselect) { R.dimen.tab_tray_multiselect_handle_height } else { - R.dimen.tab_tray_normal_handle_height + R.dimen.bottom_sheet_handle_height } ) topMargin = view.resources.getDimensionPixelSize( if (multiselect) { R.dimen.tab_tray_multiselect_handle_top_margin } else { - R.dimen.tab_tray_normal_handle_top_margin + R.dimen.bottom_sheet_handle_top_margin } ) } diff --git a/app/src/main/res/layout/component_tabhistory.xml b/app/src/main/res/layout/component_tabhistory.xml index 3095029a2..073753f5c 100644 --- a/app/src/main/res/layout/component_tabhistory.xml +++ b/app/src/main/res/layout/component_tabhistory.xml @@ -13,8 +13,8 @@ 8dp 8dp + 3dp + 8dp + 56dp @@ -172,9 +175,7 @@ 69dp 4dp 11dp - 11dp 0dp - 8dp 24dp 28dp From 601e847094d2d849a85d4fb2dfb40b34dc1eef5f Mon Sep 17 00:00:00 2001 From: Christian Sadilek Date: Tue, 22 Sep 2020 18:26:58 -0400 Subject: [PATCH 10/36] Configure M4 AMO collection for all builds --- app/build.gradle | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index dfcc663d9..2174f507f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,7 +31,7 @@ android { testInstrumentationRunnerArguments clearPackageData: 'true' resValue "bool", "IS_DEBUG", "false" buildConfigField "boolean", "USE_RELEASE_VERSIONING", "false" - buildConfigField "String", "AMO_COLLECTION", "\"3204bb44a6ef44d39ee34917f28055\"" + buildConfigField "String", "AMO_COLLECTION", "\"83a9cccfe6e24a34bd7b155ff9ee32\"" def deepLinkSchemeValue = "fenix-dev" buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\"" manifestPlaceholders = [ @@ -59,14 +59,12 @@ android { shrinkResources false minifyEnabled false applicationIdSuffix ".fenix.debug" - buildConfigField "String", "AMO_COLLECTION", "\"83a9cccfe6e24a34bd7b155ff9ee32\"" resValue "bool", "IS_DEBUG", "true" pseudoLocalesEnabled true } nightly releaseTemplate >> { applicationIdSuffix ".fenix" buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true" - buildConfigField "String", "AMO_COLLECTION", "\"83a9cccfe6e24a34bd7b155ff9ee32\"" def deepLinkSchemeValue = "fenix-nightly" buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\"" manifestPlaceholders = ["deepLinkScheme": deepLinkSchemeValue] From c1bf66618e17cd739f999221d9cd16ebf01955ec Mon Sep 17 00:00:00 2001 From: mcarare Date: Wed, 23 Sep 2020 11:12:31 +0300 Subject: [PATCH 11/36] #274-android-l10n: Update create account string to avoid partial translations. --- .../fenix/settings/account/TurnOnSyncFragment.kt | 15 +++------------ app/src/main/res/values/strings.xml | 7 ++----- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt index 67ca920ed..99fe35db9 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt @@ -6,7 +6,6 @@ package org.mozilla.fenix.settings.account import android.Manifest import android.os.Bundle -import android.text.Spanned import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -26,7 +25,6 @@ import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.ext.addUnderline import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar @@ -125,17 +123,10 @@ class TurnOnSyncFragment : Fragment(), AccountObserver { DefaultSyncController(activity = activity as HomeActivity) ) - val createAccountActionText = getString(R.string.sign_in_create_account_link) - val fullText = getString(R.string.sign_in_create_account_text, createAccountActionText) - val spanStart = fullText.indexOf(createAccountActionText, 0, false) - val spanEnd = spanStart + createAccountActionText.length - view.createAccount.apply { - text = fullText - addUnderline( - spanStart, - spanEnd, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + text = HtmlCompat.fromHtml( + getString(R.string.sign_in_create_account_text), + HtmlCompat.FROM_HTML_MODE_LEGACY ) setOnClickListener(createAccountClickListener) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 79fbfe7c7..4a9e2c55b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1144,11 +1144,8 @@ Sign in with your camera Use email instead - - No account? %s to sync Firefox between devices. - - Create one + + Create one to sync Firefox between devices.]]> Firefox will stop syncing with your account, but won’t delete any of your browsing data on this device. From 943455658bf90df9c7139512790e065fc7292e0d Mon Sep 17 00:00:00 2001 From: Sebastian Kaspari Date: Wed, 23 Sep 2020 15:11:48 +0200 Subject: [PATCH 12/36] Issue #13270: Do not launch into Fennec's task anymore. Initially we did this to avoid a duplicated task right after the migration from Fennec. We'd end up with the original task from Fennec and the new one from Fenix; with the Fennec task still showing Fennec in the app switcher, but launching Fenix once selected. Anyhow, now on Android 11 this causes the Fenix task to get duplicated. The simple fix is to not do any of that anymore. This may re-introduce the problem with the Fennec migration, but: * We are at 100% rollout for quite some time. There are still users migrating, but the impact of the bug is much lower. * The bug after the migration was only temporary. This bug here is happening every time you launch Fenix. So I'd rather fix this than a possible inconvenience right after the migration. --- app/src/migration/AndroidManifest.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/migration/AndroidManifest.xml b/app/src/migration/AndroidManifest.xml index b89be3825..a90db330d 100644 --- a/app/src/migration/AndroidManifest.xml +++ b/app/src/migration/AndroidManifest.xml @@ -19,12 +19,10 @@ From d45e482373026f48280f3d72b38f2c0965ec52d2 Mon Sep 17 00:00:00 2001 From: Jocelyne <38375996+joc-a@users.noreply.github.com> Date: Wed, 23 Sep 2020 22:10:49 +0300 Subject: [PATCH 13/36] For #11800: Hide reveal and clear password icons if the password is empty when editing a saved login (#15244) Co-authored-by: Jocelyne Abi Haidar --- .../logins/fragment/EditLoginFragment.kt | 35 +++++++++++++++---- .../mozac_ic_warning_with_bottom_padding.xml | 9 +++++ 2 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 app/src/main/res/drawable/mozac_ic_warning_with_bottom_padding.xml diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt index 2117f93fc..c65654fe0 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.settings.logins.fragment +import android.content.res.ColorStateList import android.os.Bundle import android.text.Editable import android.text.InputType @@ -13,6 +14,8 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.View import androidx.appcompat.view.menu.ActionMenuItemView +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController @@ -114,6 +117,8 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { usernameChanged = false passwordChanged = false + + clearUsernameTextButton.isEnabled = oldLogin.username.isNotEmpty() } private fun formatEditableValues() { @@ -140,7 +145,6 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { passwordText.isCursorVisible = true passwordText.hasFocus() inputLayoutPassword.hasFocus() - it.isEnabled = false } revealPasswordButton.setOnClickListener { togglePasswordReveal(passwordText, revealPasswordButton) @@ -168,13 +172,14 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { validUsername = true inputLayoutUsername.error = null inputLayoutUsername.errorIconDrawable = null + clearUsernameTextButton.isVisible = true } else -> { usernameChanged = true - clearUsernameTextButton.isEnabled = true setDupeError() } } + clearUsernameTextButton.isEnabled = u.toString().isNotEmpty() setSaveButtonState() } @@ -192,7 +197,8 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { when { p.toString().isEmpty() -> { passwordChanged = true - clearPasswordTextButton.isEnabled = false + revealPasswordButton.isVisible = false + clearPasswordTextButton.isVisible = false setPasswordError() } p.toString() == oldLogin.password -> { @@ -200,14 +206,16 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { validPassword = true inputLayoutPassword.error = null inputLayoutPassword.errorIconDrawable = null - clearPasswordTextButton.isEnabled = true + revealPasswordButton.isVisible = true + clearPasswordTextButton.isVisible = true } else -> { passwordChanged = true validPassword = true inputLayoutPassword.error = null inputLayoutPassword.errorIconDrawable = null - clearPasswordTextButton.isEnabled = true + revealPasswordButton.isVisible = true + clearPasswordTextButton.isVisible = true } } setSaveButtonState() @@ -231,13 +239,21 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { inputLayoutUsername?.let { usernameChanged = true validUsername = false - it.setErrorIconDrawable(R.drawable.mozac_ic_warning) it.error = context?.getString(R.string.saved_login_duplicate) + it.setErrorIconDrawable(R.drawable.mozac_ic_warning_with_bottom_padding) + it.setErrorIconTintList( + ColorStateList.valueOf( + ContextCompat.getColor(requireContext(), R.color.design_error) + ) + ) + clearUsernameTextButton.isVisible = false } } else { usernameChanged = true validUsername = true inputLayoutUsername.error = null + inputLayoutUsername.errorIconDrawable = null + clearUsernameTextButton.isVisible = true } } @@ -245,7 +261,12 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { inputLayoutPassword?.let { layout -> validPassword = false layout.error = context?.getString(R.string.saved_login_password_required) - layout.setErrorIconDrawable(R.drawable.mozac_ic_warning) + layout.setErrorIconDrawable(R.drawable.mozac_ic_warning_with_bottom_padding) + layout.setErrorIconTintList( + ColorStateList.valueOf( + ContextCompat.getColor(requireContext(), R.color.design_error) + ) + ) } } diff --git a/app/src/main/res/drawable/mozac_ic_warning_with_bottom_padding.xml b/app/src/main/res/drawable/mozac_ic_warning_with_bottom_padding.xml new file mode 100644 index 000000000..408f4c077 --- /dev/null +++ b/app/src/main/res/drawable/mozac_ic_warning_with_bottom_padding.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file From 71b51146cbcfa6f7a6b792ee1296153da5b73efe Mon Sep 17 00:00:00 2001 From: Grisha Kruglov Date: Mon, 27 Jul 2020 19:46:36 -0700 Subject: [PATCH 14/36] Update breaking changes in the FxA/Sync integration --- .../java/org/mozilla/fenix/HomeActivity.kt | 4 +- .../fenix/components/AccountAbnormalities.kt | 71 +++++++++---------- .../fenix/components/BackgroundServices.kt | 45 ++++++------ .../mozilla/fenix/components/metrics/Event.kt | 3 +- .../components/metrics/GleanMetricsService.kt | 2 +- .../metrics/LeanplumMetricsService.kt | 2 +- .../OnboardingAutomaticSignInViewHolder.kt | 13 ++-- .../library/bookmarks/BookmarkController.kt | 2 +- .../fenix/library/history/HistoryFragment.kt | 2 +- .../mozilla/fenix/push/PushFxaIntegration.kt | 2 +- .../account/AccountSettingsFragment.kt | 61 ++++++++-------- .../fenix/settings/account/SignOutFragment.kt | 2 +- .../settings/account/TurnOnSyncFragment.kt | 25 +++---- .../org/mozilla/fenix/share/ShareViewModel.kt | 3 +- .../components/AccountAbnormalitiesTest.kt | 39 +++------- .../components/BackgroundServicesTest.kt | 7 +- ...OnboardingAutomaticSignInViewHolderTest.kt | 37 +++++++--- 17 files changed, 153 insertions(+), 167 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index d18c56462..7402463dd 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -261,10 +261,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { components.backgroundServices.accountManagerAvailableQueue.runIfReadyOrQueue { lifecycleScope.launch { // Make sure accountManager is initialized. - components.backgroundServices.accountManager.initAsync().await() + components.backgroundServices.accountManager.start() // If we're authenticated, kick-off a sync and a device state refresh. components.backgroundServices.accountManager.authenticatedAccount()?.let { - components.backgroundServices.accountManager.syncNowAsync( + components.backgroundServices.accountManager.syncNow( SyncReason.Startup, debounce = true ) diff --git a/app/src/main/java/org/mozilla/fenix/components/AccountAbnormalities.kt b/app/src/main/java/org/mozilla/fenix/components/AccountAbnormalities.kt index 9f1a137e8..92fb160bb 100644 --- a/app/src/main/java/org/mozilla/fenix/components/AccountAbnormalities.kt +++ b/app/src/main/java/org/mozilla/fenix/components/AccountAbnormalities.kt @@ -5,13 +5,11 @@ package org.mozilla.fenix.components import android.content.Context +import android.content.SharedPreferences import android.os.StrictMode import androidx.annotation.GuardedBy import androidx.annotation.VisibleForTesting -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async import mozilla.components.concept.sync.AccountObserver import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.OAuthAccount @@ -77,9 +75,18 @@ class AccountAbnormalities( private val logger = Logger("AccountAbnormalities") - private val prefs = StrictMode.allowThreadDiskReads().resetPoliciesAfter { - context.getSharedPreferences(PREF_FXA_ABNORMALITIES, Context.MODE_PRIVATE) - } + private val prefs: SharedPreferences + private val hadAccountPrior: Boolean + + init { + val prefPair = StrictMode.allowThreadDiskReads().resetPoliciesAfter { + val p = context.getSharedPreferences(PREF_FXA_ABNORMALITIES, Context.MODE_PRIVATE) + val a = p.getBoolean(KEY_HAS_ACCOUNT, false) + Pair(p, a) + } + prefs = prefPair.first + hadAccountPrior = prefPair.second +} /** * Once [accountManager] is initialized, queries it to detect abnormal account states. @@ -89,37 +96,28 @@ class AccountAbnormalities( * @param initResult A deferred result of initializing [accountManager]. * @return A [Unit] deferred, resolved once [initResult] is resolved and state is processed for abnormalities. */ - fun accountManagerInitializedAsync( - accountManager: FxaAccountManager, - initResult: Deferred - ): Deferred { + fun accountManagerStarted( + accountManager: FxaAccountManager + ) { + check(!accountManagerConfigured) { "accountManagerStarted called twice" } accountManagerConfigured = true - return CoroutineScope(coroutineContext).async { - // Wait for the account manager to finish initializing. If it's queried before the - // "init" deferred returns, we'll get inaccurate results. - initResult.await() - - // Account manager finished initialization, we can now query it for the account state - // and see if it doesn't match our expectations. - // Behaviour considered abnormal: - // - we had an account before, and it's no longer present during startup - - // We use a flag in prefs to keep track of the fact that we have an authenticated - // account. This works because our account state is persisted in the application's - // directory, same as SharedPreferences. If user clears application data, both the - // fxa state and our flag will be removed. - val hadAccountBefore = prefs.getBoolean(KEY_HAS_ACCOUNT, false) - val hasAccountNow = accountManager.authenticatedAccount() != null - if (hadAccountBefore && !hasAccountNow) { - prefs.edit().putBoolean(KEY_HAS_ACCOUNT, false).apply() - - logger.warn("Missing expected account on startup") - - crashReporter.submitCaughtException( - AbnormalFxaEvent.MissingExpectedAccountAfterStartup() - ) - } + // Behaviour considered abnormal: + // - we had an account before, and it's no longer present during startup + + // We use a flag in prefs to keep track of the fact that we have an authenticated + // account. This works because our account state is persisted in the application's + // directory, same as SharedPreferences. If user clears application data, both the + // fxa state and our flag will be removed. + val hasAccountNow = accountManager.authenticatedAccount() != null + if (hadAccountPrior && !hasAccountNow) { + prefs.edit().putBoolean(KEY_HAS_ACCOUNT, false).apply() + + logger.warn("Missing expected account on startup") + + crashReporter.submitCaughtException( + AbnormalFxaEvent.MissingExpectedAccountAfterStartup() + ) } } @@ -152,8 +150,7 @@ class AccountAbnormalities( } override fun onAuthenticated(account: OAuthAccount, authType: AuthType) { - check(accountManagerConfigured) { "onAuthenticated before account manager was configured" } - + // Not checking state of accountManagerConfigured because we'll race against account manager's start. onAuthenticatedCalled = true // We don't check if KEY_HAS_ACCOUNT was already true: we will see onAuthenticated on every diff --git a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt index 44f4131fe..c2e92057e 100644 --- a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt +++ b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt @@ -8,19 +8,22 @@ import android.content.Context import android.os.Build import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting.PRIVATE +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch import mozilla.components.browser.storage.sync.PlacesBookmarksStorage import mozilla.components.browser.storage.sync.PlacesHistoryStorage import mozilla.components.browser.storage.sync.RemoteTabsStorage import mozilla.components.concept.sync.AccountObserver import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.DeviceCapability +import mozilla.components.concept.sync.DeviceConfig import mozilla.components.concept.sync.DeviceType import mozilla.components.concept.sync.OAuthAccount import mozilla.components.feature.accounts.push.FxaPushSupportFeature import mozilla.components.feature.accounts.push.SendTabFeature import mozilla.components.feature.syncedtabs.storage.SyncedTabsStorage import mozilla.components.lib.crash.CrashReporter -import mozilla.components.service.fxa.DeviceConfig +import mozilla.components.service.fxa.PeriodicSyncConfig import mozilla.components.service.fxa.ServerConfig import mozilla.components.service.fxa.SyncConfig import mozilla.components.service.fxa.SyncEngine @@ -86,7 +89,7 @@ class BackgroundServices( @VisibleForTesting val supportedEngines = setOf(SyncEngine.History, SyncEngine.Bookmarks, SyncEngine.Passwords, SyncEngine.Tabs) - private val syncConfig = SyncConfig(supportedEngines, syncPeriodInMinutes = 240L) // four hours + private val syncConfig = SyncConfig(supportedEngines, PeriodicSyncConfig(periodMinutes = 240)) // four hours init { /* Make the "history", "bookmark", "passwords", and "tabs" stores accessible to workers @@ -156,10 +159,10 @@ class BackgroundServices( SyncedTabsIntegration(context, accountManager).launch() - accountAbnormalities.accountManagerInitializedAsync( - accountManager, - accountManager.initAsync() - ) + MainScope().launch { + accountManager.start() + accountAbnormalities.accountManagerStarted(accountManager) + } }.also { accountManagerAvailableQueue.ready() } @@ -180,31 +183,33 @@ internal class TelemetryAccountObserver( override fun onAuthenticated(account: OAuthAccount, authType: AuthType) { when (authType) { // User signed-in into an existing FxA account. - AuthType.Signin -> - metricController.track(Event.SyncAuthSignIn) + AuthType.Signin -> Event.SyncAuthSignIn // User created a new FxA account. - AuthType.Signup -> - metricController.track(Event.SyncAuthSignUp) + AuthType.Signup -> Event.SyncAuthSignUp // User paired to an existing account via QR code scanning. - AuthType.Pairing -> - metricController.track(Event.SyncAuthPaired) + AuthType.Pairing -> Event.SyncAuthPaired + + // User signed-in into an FxA account shared from another locally installed app using the reuse flow. + AuthType.MigratedReuse -> Event.SyncAuthFromSharedReuse - // User signed-in into an FxA account shared from another locally installed app - // (e.g. Fennec). - AuthType.Shared -> - metricController.track(Event.SyncAuthFromShared) + // User signed-in into an FxA account shared from another locally installed app using the copy flow. + AuthType.MigratedCopy -> Event.SyncAuthFromSharedCopy // Account Manager recovered a broken FxA auth state, without direct user involvement. - AuthType.Recovered -> - metricController.track(Event.SyncAuthRecovered) + AuthType.Recovered -> Event.SyncAuthRecovered // User signed-in into an FxA account via unknown means. // Exact mechanism identified by the 'action' param. - is AuthType.OtherExternal -> - metricController.track(Event.SyncAuthOtherExternal) + is AuthType.OtherExternal -> Event.SyncAuthOtherExternal + + // Account restored from a hydrated state on disk (e.g. during startup). + AuthType.Existing -> null + }?.let { + metricController.track(it) } + // Used by Leanplum as a context variable. settings.fxaSignedIn = true } diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt index 1a3be9a71..c01c4998b 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt @@ -67,7 +67,8 @@ sealed class Event { object SyncAuthPaired : Event() object SyncAuthRecovered : Event() object SyncAuthOtherExternal : Event() - object SyncAuthFromShared : Event() + object SyncAuthFromSharedReuse : Event() + object SyncAuthFromSharedCopy : Event() object SyncAccountOpened : Event() object SyncAccountClosed : Event() object SyncAccountSyncNow : Event() diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt index ff71896e1..25a289a55 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt @@ -269,7 +269,7 @@ private val Event.wrapper: EventWrapper<*>? is Event.SyncAuthOtherExternal -> EventWrapper( { SyncAuth.otherExternal.record(it) } ) - is Event.SyncAuthFromShared -> EventWrapper( + is Event.SyncAuthFromSharedReuse, Event.SyncAuthFromSharedCopy -> EventWrapper( { SyncAuth.autoLogin.record(it) } ) is Event.SyncAuthRecovered -> EventWrapper( diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt index acd4dae51..d3dc2b392 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt @@ -42,7 +42,7 @@ private val Event.name: String? is Event.CollectionTabRestored -> "E_Collection_Tab_Opened" is Event.SyncAuthSignUp -> "E_FxA_New_Signup" is Event.SyncAuthSignIn, Event.SyncAuthPaired, Event.SyncAuthOtherExternal -> "E_Sign_In_FxA" - is Event.SyncAuthFromShared -> "E_Sign_In_FxA_Fennec_to_Fenix" + is Event.SyncAuthFromSharedCopy, Event.SyncAuthFromSharedReuse -> "E_Sign_In_FxA_Fennec_to_Fenix" is Event.SyncAuthSignOut -> "E_Sign_Out_FxA" is Event.ClearedPrivateData -> "E_Cleared_Private_Data" is Event.DismissedOnboarding -> "E_Dismissed_Onboarding" diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt index 338b514b9..7bbad2c75 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt @@ -14,7 +14,7 @@ import kotlinx.android.synthetic.main.onboarding_automatic_signin.view.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch -import mozilla.components.service.fxa.manager.SignInWithShareableAccountResult +import mozilla.components.service.fxa.manager.MigrationResult import mozilla.components.service.fxa.sharing.ShareableAccount import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelativeWithIntrinsicBounds import org.mozilla.fenix.R @@ -56,8 +56,12 @@ class OnboardingAutomaticSignInViewHolder( button.isEnabled = false val accountManager = context.components.backgroundServices.accountManager - when (accountManager.signInWithShareableAccountAsync(shareableAccount).await()) { - SignInWithShareableAccountResult.Failure -> { + when (accountManager.migrateFromAccount(shareableAccount)) { + MigrationResult.WillRetry, + MigrationResult.Success -> { + // We consider both of these as a 'success'. + } + MigrationResult.Failure -> { // Failed to sign-in (e.g. bad credentials). Allow to try again. button.text = context.getString(R.string.onboarding_firefox_account_auto_signin_confirm) button.isEnabled = true @@ -69,9 +73,6 @@ class OnboardingAutomaticSignInViewHolder( context.getString(R.string.onboarding_firefox_account_automatic_signin_failed) ).show() } - SignInWithShareableAccountResult.WillRetry, SignInWithShareableAccountResult.Success -> { - // We consider both of these as a 'success'. - } } } diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt index 5d203028f..842d1878c 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt @@ -143,7 +143,7 @@ class DefaultBookmarkController( scope.launch { store.dispatch(BookmarkFragmentAction.StartSync) invokePendingDeletion() - activity.components.backgroundServices.accountManager.syncNowAsync(SyncReason.User).await() + activity.components.backgroundServices.accountManager.syncNow(SyncReason.User) // The current bookmark node we are viewing may be made invalid after syncing so we // check if the current node is valid and if it isn't we find the nearest valid ancestor // and open it diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt index b34f22bb4..f4714615a 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt @@ -344,7 +344,7 @@ class HistoryFragment : LibraryPageFragment(), UserInteractionHandl private suspend fun syncHistory() { val accountManager = requireComponents.backgroundServices.accountManager - accountManager.syncNowAsync(SyncReason.User).await() + accountManager.syncNow(SyncReason.User) viewModel.invalidate() } } diff --git a/app/src/main/java/org/mozilla/fenix/push/PushFxaIntegration.kt b/app/src/main/java/org/mozilla/fenix/push/PushFxaIntegration.kt index 061d06ac6..9412435f0 100644 --- a/app/src/main/java/org/mozilla/fenix/push/PushFxaIntegration.kt +++ b/app/src/main/java/org/mozilla/fenix/push/PushFxaIntegration.kt @@ -122,7 +122,7 @@ internal class OneTimeMessageDeliveryObserver( authType: AuthType ) { lazyAccount.value.withConstellation { - processRawEventAsync(String(message)) + MainScope().launch { processRawEvent(String(message)) } } MainScope().launch { diff --git a/app/src/main/java/org/mozilla/fenix/settings/account/AccountSettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/account/AccountSettingsFragment.kt index a9f56e8b6..8ccc5ff87 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/account/AccountSettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/account/AccountSettingsFragment.kt @@ -169,25 +169,32 @@ class AccountSettingsFragment : PreferenceFragmentCompat() { updateSyncEngineStates() setDisabledWhileSyncing(accountManager.isSyncActive()) - requirePreference(R.string.pref_key_sync_history).apply { - setOnPreferenceChangeListener { _, newValue -> - SyncEnginesStorage(context).setStatus(SyncEngine.History, newValue as Boolean) - @Suppress("DeferredResultUnused") - context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange) - true + fun updateSyncEngineState(context: Context, engine: SyncEngine, newState: Boolean) { + SyncEnginesStorage(context).setStatus(engine, newState) + viewLifecycleOwner.lifecycleScope.launch { + context.components.backgroundServices.accountManager.syncNow(SyncReason.EngineChange) } } - requirePreference(R.string.pref_key_sync_bookmarks).apply { - setOnPreferenceChangeListener { _, newValue -> - SyncEnginesStorage(context).setStatus(SyncEngine.Bookmarks, newValue as Boolean) - @Suppress("DeferredResultUnused") - context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange) - true + fun SyncEngine.prefId(): Int = when (this) { + SyncEngine.History -> R.string.pref_key_sync_history + SyncEngine.Bookmarks -> R.string.pref_key_sync_bookmarks + SyncEngine.Passwords -> R.string.pref_key_sync_logins + SyncEngine.Tabs -> R.string.pref_key_sync_tabs + else -> throw IllegalStateException("Accessing internal sync engines") + } + + listOf(SyncEngine.History, SyncEngine.Bookmarks, SyncEngine.Tabs).forEach { + requirePreference(it.prefId()).apply { + setOnPreferenceChangeListener { _, newValue -> + updateSyncEngineState(context, it, newValue as Boolean) + true + } } } - requirePreference(R.string.pref_key_sync_logins).apply { + // 'Passwords' listener is special, since we also display a pin protection warning. + requirePreference(SyncEngine.Passwords.prefId()).apply { setOnPreferenceChangeListener { _, newValue -> val manager = activity?.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager @@ -195,9 +202,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat() { newValue == false || !context.settings().shouldShowSecurityPinWarningSync ) { - SyncEnginesStorage(context).setStatus(SyncEngine.Passwords, newValue as Boolean) - @Suppress("DeferredResultUnused") - context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange) + updateSyncEngineState(context, SyncEngine.Passwords, newValue as Boolean) } else { showPinDialogWarning(newValue as Boolean) } @@ -205,15 +210,6 @@ class AccountSettingsFragment : PreferenceFragmentCompat() { } } - requirePreference(R.string.pref_key_sync_tabs).apply { - setOnPreferenceChangeListener { _, newValue -> - SyncEnginesStorage(context).setStatus(SyncEngine.Tabs, newValue as Boolean) - @Suppress("DeferredResultUnused") - context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange) - true - } - } - deviceConstellation?.registerDeviceObserver( deviceConstellationObserver, owner = this, @@ -237,8 +233,9 @@ class AccountSettingsFragment : PreferenceFragmentCompat() { setNegativeButton(getString(R.string.logins_warning_dialog_later)) { _: DialogInterface, _ -> SyncEnginesStorage(context).setStatus(SyncEngine.Passwords, newValue) - @Suppress("DeferredResultUnused") - context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange) + viewLifecycleOwner.lifecycleScope.launch { + context.components.backgroundServices.accountManager.syncNow(SyncReason.EngineChange) + } } setPositiveButton(getString(R.string.logins_warning_dialog_set_up_now)) { it: DialogInterface, _ -> @@ -278,13 +275,12 @@ class AccountSettingsFragment : PreferenceFragmentCompat() { viewLifecycleOwner.lifecycleScope.launch { requireComponents.analytics.metrics.track(Event.SyncAccountSyncNow) // Trigger a sync. - requireComponents.backgroundServices.accountManager.syncNowAsync(SyncReason.User) - .await() + requireComponents.backgroundServices.accountManager.syncNow(SyncReason.User) // Poll for device events & update devices. accountManager.authenticatedAccount() ?.deviceConstellation()?.run { - refreshDevicesAsync().await() - pollForCommandsAsync().await() + refreshDevices() + pollForCommands() } } } @@ -298,8 +294,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat() { context?.let { accountManager.authenticatedAccount() ?.deviceConstellation() - ?.setDeviceNameAsync(newValue, it) - ?.await() + ?.setDeviceName(newValue, it) } } return true diff --git a/app/src/main/java/org/mozilla/fenix/settings/account/SignOutFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/account/SignOutFragment.kt index f2c3ef861..c972737de 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/account/SignOutFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/account/SignOutFragment.kt @@ -64,7 +64,7 @@ class SignOutFragment : BottomSheetDialogFragment() { viewLifecycleOwner.lifecycleScope.launch { requireComponents .backgroundServices.accountAbnormalities.userRequestedLogout() - accountManager.logoutAsync().await() + accountManager.logout() }.invokeOnCompletion { if (!findNavController().popBackStack(R.id.settingsFragment, false)) { dismiss() diff --git a/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt index 99fe35db9..262d19b1d 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt @@ -70,6 +70,7 @@ class TurnOnSyncFragment : Fragment(), AccountObserver { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + requireComponents.backgroundServices.accountManager.register(this, owner = this) requireComponents.analytics.metrics.track(Event.SyncAuthOpened) // App can be installed on devices with no camera modules. Like Android TV boxes. @@ -139,23 +140,13 @@ class TurnOnSyncFragment : Fragment(), AccountObserver { // Since the snackbar can be presented in BrowserFragment or in SettingsFragment we must // base our display method on the padSnackbar argument - if (args.padSnackbar) { - FenixSnackbar.make( - view = requireView(), - duration = snackbarLength, - isDisplayedWithBrowserToolbar = true - ) - .setText(snackbarText) - .show() - } else { - FenixSnackbar.make( - view = requireView(), - duration = snackbarLength, - isDisplayedWithBrowserToolbar = false - ) - .setText(snackbarText) - .show() - } + FenixSnackbar.make( + view = requireView(), + duration = snackbarLength, + isDisplayedWithBrowserToolbar = args.padSnackbar + ) + .setText(snackbarText) + .show() } private fun navigateToPairWithEmail() { diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareViewModel.kt b/app/src/main/java/org/mozilla/fenix/share/ShareViewModel.kt index 65d25caed..4fde40fce 100644 --- a/app/src/main/java/org/mozilla/fenix/share/ShareViewModel.kt +++ b/app/src/main/java/org/mozilla/fenix/share/ShareViewModel.kt @@ -54,8 +54,7 @@ class ShareViewModel(application: Application) : AndroidViewModel(application) { viewModelScope.launch(ioDispatcher) { fxaAccountManager.authenticatedAccount() ?.deviceConstellation() - ?.refreshDevicesAsync() - ?.await() + ?.refreshDevices() val devicesShareOptions = buildDeviceList(fxaAccountManager, network) devicesListLiveData.postValue(devicesShareOptions) diff --git a/app/src/test/java/org/mozilla/fenix/components/AccountAbnormalitiesTest.kt b/app/src/test/java/org/mozilla/fenix/components/AccountAbnormalitiesTest.kt index 21c0b404c..22a8df8ed 100644 --- a/app/src/test/java/org/mozilla/fenix/components/AccountAbnormalitiesTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/AccountAbnormalitiesTest.kt @@ -9,7 +9,6 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verify import org.mozilla.fenix.helpers.FenixRobolectricTestRunner -import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.runBlocking import mozilla.components.lib.crash.CrashReporter import mozilla.components.service.fxa.manager.FxaAccountManager @@ -35,12 +34,8 @@ class AccountAbnormalitiesTest { assertEquals("userRequestedLogout before account manager was configured", e.message) } - try { - accountAbnormalities.onAuthenticated(mockk(), mockk()) - fail() - } catch (e: IllegalStateException) { - assertEquals("onAuthenticated before account manager was configured", e.message) - } + // This doesn't throw, see method for details. + accountAbnormalities.onAuthenticated(mockk(), mockk()) try { accountAbnormalities.onLoggedOut() @@ -58,10 +53,7 @@ class AccountAbnormalitiesTest { val accountManager: FxaAccountManager = mockk(relaxed = true) val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) - accountAbnormalities.accountManagerInitializedAsync( - accountManager, - CompletableDeferred(Unit).also { it.complete(Unit) } - ).await() + accountAbnormalities.accountManagerStarted(accountManager) // Logout action must be preceded by auth. accountAbnormalities.userRequestedLogout() @@ -74,10 +66,7 @@ class AccountAbnormalitiesTest { val accountManager: FxaAccountManager = mockk(relaxed = true) val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) - accountAbnormalities.accountManagerInitializedAsync( - accountManager, - CompletableDeferred(Unit).also { it.complete(Unit) } - ).await() + accountAbnormalities.accountManagerStarted(accountManager) accountAbnormalities.onAuthenticated(mockk(), mockk()) // So far, so good. A regular logout request while being authenticated. @@ -95,10 +84,7 @@ class AccountAbnormalitiesTest { val accountManager: FxaAccountManager = mockk(relaxed = true) val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) - accountAbnormalities.accountManagerInitializedAsync( - accountManager, - CompletableDeferred(Unit).also { it.complete(Unit) } - ).await() + accountAbnormalities.accountManagerStarted(accountManager) // User didn't request this logout. accountAbnormalities.onLoggedOut() @@ -111,10 +97,7 @@ class AccountAbnormalitiesTest { val accountManager: FxaAccountManager = mockk(relaxed = true) val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) - accountAbnormalities.accountManagerInitializedAsync( - accountManager, - CompletableDeferred(Unit).also { it.complete(Unit) } - ).await() + accountAbnormalities.accountManagerStarted(accountManager) accountAbnormalities.onAuthenticated(mockk(), mockk()) verify { crashReporter wasNot Called } @@ -124,10 +107,7 @@ class AccountAbnormalitiesTest { val accountAbnormalities2 = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) // mock accountManager doesn't have an account, but we expect it to have one since we // were authenticated before our "restart". - accountAbnormalities2.accountManagerInitializedAsync( - accountManager, - CompletableDeferred(Unit).also { it.complete(Unit) } - ).await() + accountAbnormalities2.accountManagerStarted(accountManager) assertCaughtException(crashReporter) } @@ -138,10 +118,7 @@ class AccountAbnormalitiesTest { val accountManager: FxaAccountManager = mockk(relaxed = true) val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) - accountAbnormalities.accountManagerInitializedAsync( - accountManager, - CompletableDeferred(Unit).also { it.complete(Unit) } - ).await() + accountAbnormalities.accountManagerStarted(accountManager) // We saw an auth event, then user requested a logout. accountAbnormalities.onAuthenticated(mockk(), mockk()) diff --git a/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt b/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt index e0e93dad0..9104b8a98 100644 --- a/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt @@ -78,10 +78,13 @@ class BackgroundServicesTest { fun `telemetry account observer tracks shared event`() { val account = mockk() - registry.notifyObservers { onAuthenticated(account, AuthType.Shared) } - verify { metrics.track(Event.SyncAuthFromShared) } + registry.notifyObservers { onAuthenticated(account, AuthType.MigratedReuse) } + verify { metrics.track(Event.SyncAuthFromSharedReuse) } verify { settings.fxaSignedIn = true } confirmVerified(metrics, settings) + + registry.notifyObservers { onAuthenticated(account, AuthType.MigratedCopy) } + verify { metrics.track(Event.SyncAuthFromSharedCopy) } } @Test diff --git a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolderTest.kt index c81dad81d..80978e3f0 100644 --- a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolderTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolderTest.kt @@ -7,16 +7,16 @@ package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding import android.view.LayoutInflater import android.view.View import io.mockk.every +import io.mockk.coEvery import io.mockk.mockk import io.mockk.mockkObject -import io.mockk.unmockkObject import io.mockk.verify +import io.mockk.unmockkObject import kotlinx.android.synthetic.main.onboarding_automatic_signin.view.* -import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runBlockingTest -import mozilla.components.service.fxa.manager.SignInWithShareableAccountResult +import mozilla.components.service.fxa.manager.MigrationResult import mozilla.components.service.fxa.sharing.ShareableAccount import mozilla.components.support.test.robolectric.testContext import org.junit.After @@ -69,13 +69,30 @@ class OnboardingAutomaticSignInViewHolderTest { } @Test - fun `sign in on click`() = runBlocking { + fun `sign in on click - MigrationResult Success`() = runBlocking { + val account = mockk { + every { email } returns "email@example.com" + } + coEvery { + backgroundServices.accountManager.migrateFromAccount(account) + } returns MigrationResult.Success + + val holder = OnboardingAutomaticSignInViewHolder(view, scope = this) + holder.bind(account) + holder.onClick(view.fxa_sign_in_button) + + assertEquals("Signing in…", view.fxa_sign_in_button.text) + assertFalse(view.fxa_sign_in_button.isEnabled) + } + + @Test + fun `sign in on click - MigrationResult WillRetry treated the same as Success`() = runBlocking { val account = mockk { every { email } returns "email@example.com" } - every { - backgroundServices.accountManager.signInWithShareableAccountAsync(account) - } returns CompletableDeferred(SignInWithShareableAccountResult.Success) + coEvery { + backgroundServices.accountManager.migrateFromAccount(account) + } returns MigrationResult.WillRetry val holder = OnboardingAutomaticSignInViewHolder(view, scope = this) holder.bind(account) @@ -90,9 +107,9 @@ class OnboardingAutomaticSignInViewHolderTest { val account = mockk { every { email } returns "email@example.com" } - every { - backgroundServices.accountManager.signInWithShareableAccountAsync(account) - } returns CompletableDeferred(SignInWithShareableAccountResult.Failure) + coEvery { + backgroundServices.accountManager.migrateFromAccount(account) + } returns MigrationResult.Failure val holder = OnboardingAutomaticSignInViewHolder(view, scope = this) holder.bind(account) From 9b01ca7d76f074872194e1f5023203e3230128d5 Mon Sep 17 00:00:00 2001 From: Grisha Kruglov Date: Wed, 23 Sep 2020 11:43:46 -0700 Subject: [PATCH 15/36] Update A-C Version --- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index a562cf645..bf36cbaa2 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 = "60.0.20200922130109" + const val VERSION = "61.0.20200923130632" } From 57755fe165b2d19650216bac1929e029f0c3a978 Mon Sep 17 00:00:00 2001 From: Grisha Kruglov Date: Wed, 23 Sep 2020 11:56:56 -0700 Subject: [PATCH 16/36] Glean docs update --- docs/metrics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/metrics.md b/docs/metrics.md index 197643258..845a399b9 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -1,7 +1,7 @@ # Metrics -This document enumerates the metrics collected by this project. +This document enumerates the metrics collected by this project using the [Glean SDK](https://mozilla.github.io/glean/book/index.html). This project may depend on other projects which also collect metrics. This means you might have to go searching through the dependency tree to get a full picture of everything collected by this project. From 0fa2509bef9ba18c98eaa59539b3c7bf37bab1fa Mon Sep 17 00:00:00 2001 From: Sawyer Blatz Date: Wed, 23 Sep 2020 14:57:52 -0700 Subject: [PATCH 17/36] For #14243: Pre-land visuals for Close Tabs CFR (#15171) --- .../org/mozilla/fenix/browser/InfoBanner.kt | 9 ++++++- .../browser/OpenInAppOnboardingObserver.kt | 1 - .../fenix/tabtray/TabTrayController.kt | 6 +++++ .../tabtray/TabTrayFragmentInteractor.kt | 9 +++++++ .../org/mozilla/fenix/tabtray/TabTrayView.kt | 24 +++++++++++++++++++ .../java/org/mozilla/fenix/utils/Settings.kt | 5 ++++ .../main/res/layout/component_tabstray.xml | 13 ++++++++-- app/src/main/res/values/preference_keys.xml | 3 +++ .../tabtray/DefaultTabTrayControllerTest.kt | 10 ++++++++ .../tabtray/TabTrayFragmentInteractorTest.kt | 6 +++++ buildSrc/src/main/java/Dependencies.kt | 2 +- 11 files changed, 83 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/browser/InfoBanner.kt b/app/src/main/java/org/mozilla/fenix/browser/InfoBanner.kt index be74646d2..d7f4187cc 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/InfoBanner.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/InfoBanner.kt @@ -13,6 +13,7 @@ import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import kotlinx.android.synthetic.main.info_banner.view.* import org.mozilla.fenix.R +import org.mozilla.fenix.ext.settings /** * Displays an Info Banner in the specified container with a message and an optional action. @@ -25,12 +26,15 @@ import org.mozilla.fenix.R * @param actionText - The text on the action to perform button * @param actionToPerform - The action to be performed on action button press */ +@SuppressWarnings("LongParameterList") class InfoBanner( private val context: Context, private val container: ViewGroup, private val message: String, private val dismissText: String, private val actionText: String? = null, + private val dismissByHiding: Boolean = false, + private val dismissAction: (() -> Unit)? = null, private val actionToPerform: (() -> Unit)? = null ) { @SuppressLint("InflateParams") @@ -54,12 +58,15 @@ class InfoBanner( params.width = MATCH_PARENT bannerLayout.dismiss.setOnClickListener { - dismiss() + dismissAction?.invoke() + if (dismissByHiding) { bannerLayout.visibility = GONE } else { dismiss() } } bannerLayout.action.setOnClickListener { actionToPerform?.invoke() } + + context.settings().lastCfrShownTimeInMillis = System.currentTimeMillis() } internal fun dismiss() { diff --git a/app/src/main/java/org/mozilla/fenix/browser/OpenInAppOnboardingObserver.kt b/app/src/main/java/org/mozilla/fenix/browser/OpenInAppOnboardingObserver.kt index 43be8b169..63b8c2635 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/OpenInAppOnboardingObserver.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/OpenInAppOnboardingObserver.kt @@ -60,7 +60,6 @@ class OpenInAppOnboardingObserver( infoBanner?.showBanner() sessionDomainForDisplayedBanner = session.url.tryGetHostFromUrl() - settings.lastCfrShownTimeInMillis = System.currentTimeMillis() settings.shouldShowOpenInAppBanner = false } } diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt index 431bfe898..41c6ddf40 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt @@ -43,6 +43,7 @@ interface TabTrayController { fun handleOpenTab(tab: Tab) fun handleEnterMultiselect() fun handleRecentlyClosedClicked() + fun handleSetUpAutoCloseTabsClicked() } /** @@ -184,4 +185,9 @@ class DefaultTabTrayController( val directions = TabTrayDialogFragmentDirections.actionGlobalRecentlyClosed() navController.navigate(directions) } + + override fun handleSetUpAutoCloseTabsClicked() { + val directions = TabTrayDialogFragmentDirections.actionGlobalCloseTabSettingsFragment() + navController.navigate(directions) + } } diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractor.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractor.kt index 3cf89a386..556023572 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractor.kt @@ -54,6 +54,11 @@ interface TabTrayInteractor { */ fun onModeRequested(): TabTrayDialogFragmentState.Mode + /** + * Called when user clicks on the "set it up" prompt for automatically closing tabs + */ + fun onSetUpAutoCloseTabsClicked() + /** * Called when a tab should be opened in the browser. */ @@ -140,4 +145,8 @@ class TabTrayFragmentInteractor(private val controller: TabTrayController) : Tab override fun onEnterMultiselect() { controller.handleEnterMultiselect() } + + override fun onSetUpAutoCloseTabsClicked() { + controller.handleSetUpAutoCloseTabsClicked() + } } diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt index 948fb9e88..2d24cc0ef 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt @@ -43,6 +43,7 @@ import mozilla.components.browser.tabstray.TabViewHolder import mozilla.components.feature.syncedtabs.SyncedTabsFeature import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import org.mozilla.fenix.R +import org.mozilla.fenix.browser.InfoBanner import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.toolbar.TabCounter.Companion.INFINITE_CHAR_PADDING_BOTTOM import org.mozilla.fenix.components.toolbar.TabCounter.Companion.MAX_VISIBLE_TABS @@ -239,6 +240,28 @@ class TabTrayView( } adjustNewTabButtonsForNormalMode() + + if ( + view.context.settings().shouldShowAutoCloseTabsBanner && + view.context.settings().canShowCfr && + tabs.size >= TAB_COUNT_SHOW_CFR + ) { + InfoBanner( + context = view.context, + message = view.context.getString(R.string.tab_tray_close_tabs_banner_message), + dismissText = view.context.getString(R.string.tab_tray_close_tabs_banner_negative_button_text), + actionText = view.context.getString(R.string.tab_tray_close_tabs_banner_positive_button_text), + container = view.infoBanner, + dismissByHiding = true, + dismissAction = { view.context.settings().shouldShowAutoCloseTabsBanner = false } + ) { + interactor.onSetUpAutoCloseTabsClicked() + view.context.settings().shouldShowAutoCloseTabsBanner = false + }.apply { + view.infoBanner.visibility = View.VISIBLE + showBanner() + } + } } private fun handleTabClicked(tab: SyncTab) { @@ -578,6 +601,7 @@ class TabTrayView( } companion object { + private const val TAB_COUNT_SHOW_CFR = 6 private const val DEFAULT_TAB_ID = 0 private const val PRIVATE_TAB_ID = 1 private const val EXPAND_AT_SIZE = 3 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 ff4bed759..a0bd0ad76 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -689,6 +689,11 @@ class Settings(private val appContext: Context) : PreferencesHolder { val shouldShowOpenInAppCfr: Boolean get() = canShowCfr && shouldShowOpenInAppBanner + var shouldShowAutoCloseTabsBanner by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_should_show_auto_close_tabs_banner), + default = true + ) + @VisibleForTesting(otherwise = PRIVATE) internal val trackingProtectionOnboardingCount = counterPreference( appContext.getPreferenceKey(R.string.pref_key_tracking_protection_onboarding), diff --git a/app/src/main/res/layout/component_tabstray.xml b/app/src/main/res/layout/component_tabstray.xml index e785b4dfd..0005ecb5a 100644 --- a/app/src/main/res/layout/component_tabstray.xml +++ b/app/src/main/res/layout/component_tabstray.xml @@ -24,6 +24,15 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintWidth_percent="0.1" /> + + + + app:layout_constraintTop_toBottomOf="@id/infoBanner" /> + app:layout_constraintTop_toBottomOf="@+id/infoBanner" /> pref_key_should_show_open_in_app_banner + + pref_key_should_show_auto_close_tabs_banner + pref_key_migrating_from_fenix_nightly_tip pref_key_migrating_from_firefox_nightly_tip pref_key_migrating_from_fenix_tip diff --git a/app/src/test/java/org/mozilla/fenix/tabtray/DefaultTabTrayControllerTest.kt b/app/src/test/java/org/mozilla/fenix/tabtray/DefaultTabTrayControllerTest.kt index 0e478c3ba..c484c82a2 100644 --- a/app/src/test/java/org/mozilla/fenix/tabtray/DefaultTabTrayControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/tabtray/DefaultTabTrayControllerTest.kt @@ -248,4 +248,14 @@ class DefaultTabTrayControllerTest { showChooseCollectionDialog(listOf(session)) } } + + @Test + fun handleSetUpAutoCloseTabsClicked() { + controller.handleSetUpAutoCloseTabsClicked() + val directions = TabTrayDialogFragmentDirections.actionGlobalCloseTabSettingsFragment() + + verify { + navController.navigate(directions) + } + } } diff --git a/app/src/test/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractorTest.kt index 7fbb8306c..1e1f027e1 100644 --- a/app/src/test/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractorTest.kt @@ -106,4 +106,10 @@ class TabTrayFragmentInteractorTest { interactor.onEnterMultiselect() verify { controller.handleEnterMultiselect() } } + + @Test + fun onSetUpAutoCloseTabsClicked() { + interactor.onSetUpAutoCloseTabsClicked() + verify { controller.handleSetUpAutoCloseTabsClicked() } + } } diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index ff7344e09..6daeba32e 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -15,7 +15,7 @@ object Versions { const val androidx_appcompat = "1.2.0-rc01" const val androidx_biometric = "1.1.0-alpha01" const val androidx_coordinator_layout = "1.1.0-rc01" - const val androidx_constraint_layout = "2.0.0-beta6" + const val androidx_constraint_layout = "2.0.0" const val androidx_preference = "1.1.0" const val androidx_legacy = "1.0.0" const val androidx_annotation = "1.1.0" From 4fad099b232e72199808c408fb1d06963e3cd532 Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Thu, 24 Sep 2020 00:07:39 +0000 Subject: [PATCH 18/36] Import l10n. --- app/src/main/res/values-ar/strings.xml | 75 +++++++++++++++++++++- app/src/main/res/values-be/strings.xml | 10 +++ app/src/main/res/values-de/strings.xml | 7 ++ app/src/main/res/values-dsb/strings.xml | 7 ++ app/src/main/res/values-en-rCA/strings.xml | 7 ++ app/src/main/res/values-es-rAR/strings.xml | 7 ++ app/src/main/res/values-es-rCL/strings.xml | 9 +++ app/src/main/res/values-fi/strings.xml | 7 ++ app/src/main/res/values-hsb/strings.xml | 7 ++ app/src/main/res/values-iw/strings.xml | 12 +++- app/src/main/res/values-ka/strings.xml | 10 ++- app/src/main/res/values-ko/strings.xml | 8 +++ app/src/main/res/values-nb-rNO/strings.xml | 7 ++ app/src/main/res/values-sl/strings.xml | 6 ++ app/src/main/res/values-sv-rSE/strings.xml | 7 ++ app/src/main/res/values-tr/strings.xml | 7 ++ app/src/main/res/values-uk/strings.xml | 7 ++ l10n.toml | 2 + 18 files changed, 195 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 20f14d4c3..f38df865a 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -47,6 +47,9 @@ دخلت وضع التحديد المتعدد، حدّد الألسنة لحفظها في تجميعة + + محدد + ‏%1$s من Mozilla. @@ -69,6 +72,11 @@ لا، شكرًا + + + افتح Firefox بسرعة أكبر. أضف أداة Firefox إلى الشاشة الرئيسية. + + أضف الأداة ليس الآن @@ -138,6 +146,8 @@ ثبّت الألسنة المُزامنة + + أعِد المزامنة ابحث في الصفحة @@ -155,10 +165,10 @@ افتح في %1$s - تدعمها %1$s + تدعمه %1$s - تدعمها %1$s + تدعمه %1$s منظور القارئ @@ -261,6 +271,8 @@ افتح الروابط في ألسنة خاصة اسمح بلقطات الشاشة في التصفّح الخاص + + إن سمحت فستظهر الألسنة الخاصة حين فتح أكثر من تطبيق أضِف اختصارًا للتصفح الخاص @@ -317,6 +329,8 @@ البحث في تأريخ التصفح البحث في العلامات + + ابحث في الألسنة المُزامنة إعدادات الحساب @@ -360,6 +374,11 @@ تاريخ آخر مزامنة: أبدًا + + ‏%1$s على %2$s %3$s + الألسنة المستلمة @@ -450,6 +469,11 @@ مرّر لإخفاء شريط الأدوات + + مرّر شريط الأدوات إلى اليمين واليسار للتبديل بين الألسنة + + مرّر شريط الأدوات إلى أعلى لفتح الألسنة + الجلسات @@ -690,6 +714,10 @@ أضِف مجلدًا اختر مجلدًا + + يجب أن يكون للعلامة عنوان + + مسار غير صحيح ما من علامات هنا فهمت + + تعذرت المشاركة مع هذا التطبيق أرسِل إلى جهاز @@ -839,8 +869,12 @@ جلسة تصفح خاصة حذف الألسنة الخاصة + + أغلِق الألسنة الخاصة افتح + + تدعمه حُذفت التجميعة @@ -944,9 +978,25 @@ صار Firefox Preview الآن Firefox Nightly + + تُحدّث النسخة الليلية من Firefox في كل ليلة وتُضاف إليها مزايا تجريبية جديدة. +ولكن قد يكون استقرارها أقل من العادية. نزّل متصفّح بيتا لتجربة أكثر استقرارًا. نزّل نسخة Firefox لأندرويد التجريبية + + انتقلت النسخة الليلية من Firefox من هنا + + لن يتلقى هذا التطبيق تحديثات الأمان بعد الآن. يُنصح بالتوقف عن استخدام هذا التطبيق والانتقال إلى النسخة الليلية الجديدة. +لنقل علاماتك وجلسات الولوج والتأريخ إلى تطبيق آخر، افتح حساب Firefox. + + انتقل إلى النسخة الليلية الجديدة + + + انتقلت النسخة الليلية من Firefox من هنا + + لن يتلقى هذا التطبيق تحديثات الأمان بعد الآن. ننصح بتنزيل النسخة الليلية الجديدة والتوقف عن استخدام هذا التطبيق. +لنقل علاماتك وجلسات الولوج والتأريخ إلى تطبيق آخر، افتح حساب Firefox. نزّل النسخة الليلية الجديدة @@ -961,6 +1011,21 @@ تعرف على %s اعرف ما الجديد + + ألديك أسئلة عن متصفّح %s الذي أعدنا تصميمه؟ أتريد معرفة ما تغيّر؟ + + ستجد هنا إجابات أسئلتك + + اشرع الآن بمزامنة العلامات وكلمات السر وغيرها الكثير عبر حساب Firefox. + + اطّلع على المزيد + + ولجت ببريد %s على متصفّح Firefox آخر على هذا الجهاز. أتريد الولوج بنفس الحساب؟ + + نعم، سألج بنفس الحساب تَلج الآن… @@ -975,6 +1040,10 @@ صارم (مستحسن) صارم + + اختر جهةً تصفّح بخصوصية @@ -1312,7 +1381,7 @@ حسنًا، فهمت - استغلّ %s إلى أقصى حد. diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index b568c4ef2..64875a114 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -282,6 +282,8 @@ Тэма Хатняя старонка + + Жэсты Уладкаванне @@ -317,8 +319,12 @@ Пошук у гісторыі аглядання Пошук у закладках + + Шукаць у сінхранізаваных картках Налады ўліковага запісу + + Аўтазапаўненне URL-адрасоў Адкрываць спасылкі ў праграмах @@ -450,6 +456,10 @@ Тэма прылады + + + Пацягніце, каб абнавіць + Сеансы diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index fe3f77cf3..281f5bf5c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1197,6 +1197,8 @@ Melden Sie sich mit Ihrer Kamera an Stattdessen E-Mail-Adresse verwenden + + Erstellen Sie eines, um Firefox zwischen Geräten zu synchronisieren.]]> Firefox beendet die Synchronisation mit Ihrem Konto, löscht aber keine Surf-Daten auf diesem Gerät. @@ -1304,6 +1306,11 @@ The first parameter is the app name --> %s | OSS-Bibliotheken + + Weiterleitungs-Tracker + + Löscht Cookies, die durch Weiterleitungen zu bekannten Tracking-Websites gesetzt wurden. + Hilfe diff --git a/app/src/main/res/values-dsb/strings.xml b/app/src/main/res/values-dsb/strings.xml index 35607c9eb..db76686a3 100644 --- a/app/src/main/res/values-dsb/strings.xml +++ b/app/src/main/res/values-dsb/strings.xml @@ -1164,6 +1164,8 @@ Pśizjawśo se ze swójeju kameru E-mail město togo wužywaś + + Załožćo take, aby Firefox mjazy rědami synchronizěrował.]]> Firefox pśestanjo z wašym kontom synchronizěrowaś, ale njewulašujo pśeglědowańske daty na toś tom rěźe. @@ -1274,6 +1276,11 @@ The first parameter is the app name --> %s | OSS-biblioteki + + Dalejpósrědnjańske pśeslědowaki + + Wulašujo cookije, kótarež su se stajili pśez dalejpósrědnjenja k znatym slědujucym websedłam. + Pomoc diff --git a/app/src/main/res/values-en-rCA/strings.xml b/app/src/main/res/values-en-rCA/strings.xml index a99ae80e5..b790bc56d 100644 --- a/app/src/main/res/values-en-rCA/strings.xml +++ b/app/src/main/res/values-en-rCA/strings.xml @@ -1155,6 +1155,8 @@ Sign in with your camera Use email instead + + Create one to sync Firefox between devices.]]> Firefox will stop syncing with your account, but won’t delete any of your browsing data on this device. @@ -1262,6 +1264,11 @@ The first parameter is the app name --> %s | OSS Libraries + + Redirect Trackers + + Clears cookies set by redirects to known tracking websites. + Support diff --git a/app/src/main/res/values-es-rAR/strings.xml b/app/src/main/res/values-es-rAR/strings.xml index 77b122049..455e69cda 100644 --- a/app/src/main/res/values-es-rAR/strings.xml +++ b/app/src/main/res/values-es-rAR/strings.xml @@ -1182,6 +1182,8 @@ Inicia sesión con tu cámara Use el correo electrónico en su lugar + + Creá una para sincronizar Firefox entre dispositivos.]]> Firefox va a dejar de sincronizar con tu cuenta pero no va a eliminar ningún dato de navegación en este dispositivo. @@ -1290,6 +1292,11 @@ The first parameter is the app name --> %s | Bibliotecas OSS + + Redirigir rastreadores + + Borra las cookies establecidas por redireccionamientos a sitios web de rastreo conocidos. + Ayuda diff --git a/app/src/main/res/values-es-rCL/strings.xml b/app/src/main/res/values-es-rCL/strings.xml index 4f840179b..13e446e29 100644 --- a/app/src/main/res/values-es-rCL/strings.xml +++ b/app/src/main/res/values-es-rCL/strings.xml @@ -144,6 +144,8 @@ Instalar Pestañas sincronizadas + + Resincronizar Buscar en la página @@ -1159,6 +1161,8 @@ Conéctate con tu cámara O usa tu correo + + Crea una para sincronizar Firefox entre dispositivos.]]> Firefox dejará de sincronizarse con tu cuenta, pero no se borrarán los datos de navegación del dispositivo. @@ -1266,6 +1270,11 @@ The first parameter is the app name --> %s | Bibliotecas OSS + + Rastreadores de redirección + + Limpia las cookies creadas por redirecciones a sitios web de seguimiento conocidos. + Ayuda diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index efcefa9fd..6fc119dbe 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -1178,6 +1178,8 @@ Kirjaudu sisään kamerallasi Käytä sähköpostia + + Luo tili synkronoidaksesi Firefox laitteiden välillä.]]> Firefox lopettaa tilisi synkronoinnin, mutta ei poista mitään selaustietoja tältä laitteelta. @@ -1286,6 +1288,11 @@ The first parameter is the app name --> %s | Avoimen lähdekoodin kirjastot + + Uudelleenohjausseuraimet + + Tyhjentää evästeet, jotka on asetettu tunnetuille seurantasivustoille johtavilla uudelleenohjauksilla. + Tuki diff --git a/app/src/main/res/values-hsb/strings.xml b/app/src/main/res/values-hsb/strings.xml index 6ba544559..736e8218f 100644 --- a/app/src/main/res/values-hsb/strings.xml +++ b/app/src/main/res/values-hsb/strings.xml @@ -1166,6 +1166,8 @@ Přizjewće so ze swojej kameru E-mejl město toho wužiwać + + Załožće tajke, zo byšće Firefox mjez gratami synchronizował.]]> Firefox přestanje z wašim kontom synchronizować, ale njezhaša přehladowanske daty na tutym graće. @@ -1273,6 +1275,11 @@ The first parameter is the app name --> %s | OSS-biblioteki + + Sposrědkowanske přesćěhowaki + + Zhaša placki, kotrež su so přez dalesposrědkowanja k znatym slědowacym websydłam stajili. + Pomoc diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 614772aec..7af1ad7a9 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -1157,6 +1157,8 @@ התחברות באמצעות המצלמה שלך שימוש בדוא״ל במקום + + באפשרותך ליצור אחד כדי לסנכרן את Firefox בין מכשירים.]]> ‏Firefox יפסיק להסתנכרן עם החשבון שלך, אבל לא ימחק את נתוני הגלישה שלך ממכשיר זה. @@ -1230,7 +1232,7 @@ עוגיות מעקב חוצות אתרים - חסימת עוגיות שרשתות פרסומות וחברות ניתוח תעבורה משתמשות בהן כדי לאסוף פרופיל על נתוני הגלישה שלך על פני מגוון רחב של אתרים. + חוסם עוגיות שרשתות פרסומות וחברות ניתוח תעבורה משתמשות בהן כדי לאסוף פרופיל על נתוני הגלישה שלך על פני מגוון רחב של אתרים. כורי מטבעות דיגיטליים @@ -1265,6 +1267,11 @@ The first parameter is the app name --> ‏%s | ספריות OSS + + רכיבי מעקב של הפניות + + מנקה עוגיות המוגדרות על־ידי הפניות לאתרי מעקב ידועים. + תמיכה @@ -1279,6 +1286,9 @@ ספריות בהן אנו משתמשים + + תפריט ניפוי שגיאות: נותרו %1$d לחיצות כדי להפעיל תפריט ניפוי שגיאות מופעל diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml index b3fa812a0..d74dc6441 100644 --- a/app/src/main/res/values-ka/strings.xml +++ b/app/src/main/res/values-ka/strings.xml @@ -48,7 +48,7 @@ მონიშნულია - %1$s შექმნა Mozilla-მ. + %1$s შემქმნელი Mozilla. @@ -143,6 +143,8 @@ დაყენება დასინქრონებული ჩანართები + + კვლავ დასინქრონება პოვნა გვერდზე @@ -325,6 +327,8 @@ ძიება დათვალიერების ისტორიაში სანიშნების ძიება + + დასინქრონებული ჩანართების მოძიება ანგარიშის პარამეტრები @@ -1234,7 +1238,7 @@ საიტთაშორისი მეთვალყურე ფუნთუშები - ზღუდავს ფუნთუშებს, რომლებსაც სარეკლამო და ანალიტიკური კომპანიები იყენებენ თქვენზე მონაცემების ერთიანად აღრიცხვისთვის ბევრ საიტზე. + ზღუდავს ფუნთუშებს, რომლებსაც სარეკლამო და ანალიტიკური კომპანიები იყენებენ თქვენზე მონაცემების ერთიანად შეგროვებისთვის, სხვადასხვა საიტიდან. კრიპტოგამომმუშავებლები @@ -1405,7 +1409,7 @@ დაიცავით თქვენი ანგარიშები - დააყენეთ მოწყობილობის ჩასაკეტად მოსახაზი, PIN-კოდი ან პაროლი, თქვენი შენახული ანგარიშების მონაცემებთან, უცხო პირების წვდომის აღსაკვეთათ. + დააყენეთ მოწყობილობის ჩასაკეტად მოსახაზი, PIN-კოდი ან პაროლი, თქვენი შენახული ანგარიშების მონაცემებთან, უცხო პირების წვდომის აღსაკვეთად. მოგვიანებით diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 6e56670b2..800370504 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -1210,6 +1210,8 @@ 카메라로 로그인 대신 이메일 사용 + + 하나를 만드세요.]]> Firefox가 계정과의 동기화를 중단하지만 이 기기의 사용자 탐색 데이터는 삭제하지 않습니다. @@ -1319,6 +1321,12 @@ The first parameter is the app name --> %s | OSS 라이브러리 + + 트래커 리디렉션 + + + 알려진 추적 웹 사이트로 리디렉션하여 설정된 쿠키를 지웁니다. + 지원 diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 3b2849fb1..2f3b0562e 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -1178,6 +1178,8 @@ Logg inn med kameraet ditt Bruk e-post i stedet + + Opprett en for å synkronisere Firefox mellom enheter.]]> Firefox vil stoppe synkroniseringen med kontoen din, men vil ikke slette noen av dine nettleserdata på denne enheten. @@ -1290,6 +1292,11 @@ The first parameter is the app name --> %s | OSS-bibliotek + + Omdirigeringssporere + + Fjerner infokapsler satt av omdirigeringer til kjente sporingsnettsteder. + Brukerstøtte diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 3bc0d4afe..c0bdabd12 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -146,6 +146,8 @@ Namesti Sinhronizirani zavihki + + Znova sinhroniziraj Najdi na strani @@ -268,6 +270,8 @@ Odpri povezave v zasebnem zavihku Dovoli zajemanje posnetkov zaslona v zasebnem brskanju + + Če je dovoljeno, bodo zasebni zavihki vidni tudi, ko je odprtih več aplikacij Dodaj bližnjico zasebnega brskanja @@ -328,6 +332,8 @@ Nastavitve računa + + Samodokončaj spletne naslove Odpiraj povezave v aplikacijah diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 66f477961..eb8a52c0f 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -1181,6 +1181,8 @@ Logga in med din kamera Använd e-post istället + + Skapa ett för att synkronisera Firefox mellan enheter.]]> Firefox kommer sluta att synka med ditt konto, men kommer inte att radera din surfdata på den här enheten. @@ -1289,6 +1291,11 @@ The first parameter is the app name --> %s | OSS bibliotek + + Omdirigera spårare + + Rensar kakor som ställts in av omdirigeringar till kända spårningswebbplatser. + Support diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 5e4c8fedc..60744eba5 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1162,6 +1162,8 @@ Kameranızla giriş yapın E-posta ile giriş yap + + hesap açın.]]> Firefox artık hesabınızla eşitlenmeyecek ama bu cihazdaki gezinti geçmişiniz silinmeyecek. @@ -1270,6 +1272,11 @@ The first parameter is the app name --> %s | OSS Kitaplıkları + + Yönlendirme takipçileri + + Bilinen takip sitelerine yapılan yönlendirmelere ait çerezleri temizler. + Destek diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index d354a87a5..1ab6cd697 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1176,6 +1176,8 @@ Використати е-пошту + + Створіть його для синхронізації Firefox між пристроями.]]> Firefox припинить синхронізацію з вашим обліковим записом, але не вилучить жодних даних перегляду на цьому пристрої. @@ -1284,6 +1286,11 @@ The first parameter is the app name --> %s | Вільні бібліотеки + + Елементи стеження переспрямуванням + + Очищує куки, встановлені переспрямовувачами на відомі вебсайти для стеження. + Підтримка diff --git a/l10n.toml b/l10n.toml index 643f4f0d1..cb946c589 100644 --- a/l10n.toml +++ b/l10n.toml @@ -75,6 +75,7 @@ locales = [ "rm", "ro", "ru", + "sat-Olck", "sk", "sl", "sq", @@ -83,6 +84,7 @@ locales = [ "sv-SE", "ta", "te", + "tg", "th", "tr", "trs", From f1b0827a0b1d4e945d69a44830016cdceb79d2ab Mon Sep 17 00:00:00 2001 From: Grisha Kruglov Date: Wed, 23 Sep 2020 16:34:15 -0700 Subject: [PATCH 19/36] For #15296: Allow excluding bookmark subtrees when editing parent folder I'm really not a fan of how title overwriting and structure processing are mangled together, but will leave clearing that up for another day. --- .../mozilla/fenix/library/bookmarks/Utils.kt | 14 ++ .../SelectBookmarkFolderAdapter.kt | 24 +-- .../fenix/library/bookmarks/UtilsKtTest.kt | 147 ++++++++++++++++++ 3 files changed, 165 insertions(+), 20 deletions(-) create mode 100644 app/src/test/java/org/mozilla/fenix/library/bookmarks/UtilsKtTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/Utils.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/Utils.kt index 354b80bac..44a1c00e9 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/Utils.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/Utils.kt @@ -6,6 +6,7 @@ package org.mozilla.fenix.library.bookmarks import android.content.Context import mozilla.components.concept.storage.BookmarkNode +import mozilla.components.concept.storage.BookmarkNodeType import org.mozilla.fenix.R fun rootTitles(context: Context, withMobileRoot: Boolean): Map = if (withMobileRoot) { @@ -35,3 +36,16 @@ fun friendlyRootTitle( rootTitles.containsKey(node.title) -> rootTitles[node.title] else -> node.title } + +data class BookmarkNodeWithDepth(val depth: Int, val node: BookmarkNode, val parent: String?) + +fun BookmarkNode.flatNodeList(excludeSubtreeRoot: String?, depth: Int = 0): List { + if (this.type != BookmarkNodeType.FOLDER || this.guid == excludeSubtreeRoot) { + return emptyList() + } + val newList = listOf(BookmarkNodeWithDepth(depth, this, this.parentGuid)) + return newList + children + ?.filter { it.type == BookmarkNodeType.FOLDER } + ?.flatMap { it.flatNodeList(excludeSubtreeRoot = excludeSubtreeRoot, depth = depth + 1) } + .orEmpty() +} diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/selectfolder/SelectBookmarkFolderAdapter.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/selectfolder/SelectBookmarkFolderAdapter.kt index 7eb347bd3..938426bed 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/selectfolder/SelectBookmarkFolderAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/selectfolder/SelectBookmarkFolderAdapter.kt @@ -14,29 +14,23 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import kotlinx.android.extensions.LayoutContainer import mozilla.components.concept.storage.BookmarkNode -import mozilla.components.concept.storage.BookmarkNodeType import org.mozilla.fenix.R import org.mozilla.fenix.library.LibrarySiteItemView +import org.mozilla.fenix.library.bookmarks.BookmarkNodeWithDepth import org.mozilla.fenix.library.bookmarks.BookmarksSharedViewModel +import org.mozilla.fenix.library.bookmarks.flatNodeList import org.mozilla.fenix.library.bookmarks.selectfolder.SelectBookmarkFolderAdapter.BookmarkFolderViewHolder -import org.mozilla.fenix.library.bookmarks.selectfolder.SelectBookmarkFolderAdapter.BookmarkNodeWithDepth class SelectBookmarkFolderAdapter(private val sharedViewModel: BookmarksSharedViewModel) : ListAdapter(DiffCallback) { fun updateData(tree: BookmarkNode?, hideFolderGuid: String?) { val updatedData = tree - ?.convertToFolderDepthTree() + ?.flatNodeList(hideFolderGuid) ?.drop(1) .orEmpty() - val filteredData = if (hideFolderGuid != null && updatedData.isNotEmpty()) { - updatedData.filter { it.node.guid != hideFolderGuid } - } else { - updatedData - } - - submitList(filteredData) + submitList(updatedData) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookmarkFolderViewHolder { @@ -101,16 +95,6 @@ class SelectBookmarkFolderAdapter(private val sharedViewModel: BookmarksSharedVi } } - data class BookmarkNodeWithDepth(val depth: Int, val node: BookmarkNode, val parent: String?) - - private fun BookmarkNode.convertToFolderDepthTree(depth: Int = 0): List { - val newList = listOf(BookmarkNodeWithDepth(depth, this, this.parentGuid)) - return newList + children - ?.filter { it.type == BookmarkNodeType.FOLDER } - ?.flatMap { it.convertToFolderDepthTree(depth = depth + 1) } - .orEmpty() - } - private fun getSelectedItemIndex(): Int? { val selectedNode = sharedViewModel.selectedFolder val selectedNodeIndex = currentList.indexOfFirst { it.node == selectedNode } diff --git a/app/src/test/java/org/mozilla/fenix/library/bookmarks/UtilsKtTest.kt b/app/src/test/java/org/mozilla/fenix/library/bookmarks/UtilsKtTest.kt new file mode 100644 index 000000000..42f185f32 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/library/bookmarks/UtilsKtTest.kt @@ -0,0 +1,147 @@ +/* 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.library.bookmarks + +import mozilla.components.concept.storage.BookmarkNode +import mozilla.components.concept.storage.BookmarkNodeType +import mozilla.components.support.test.robolectric.testContext +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +@RunWith(FenixRobolectricTestRunner::class) +class UtilsKtTest { + @Test + fun `friendly root titles`() { + val url = BookmarkNode( + BookmarkNodeType.ITEM, + "456", + "folder", + 0, + "Mozilla", + "http://mozilla.org", + null + ) + assertEquals("Mozilla", friendlyRootTitle(testContext, url)) + + val folder = BookmarkNode( + BookmarkNodeType.FOLDER, + "456", + "folder", + 0, + "Folder", + null, + null + ) + assertEquals("Folder", friendlyRootTitle(testContext, folder)) + + val root = folder.copy(guid = "root________", title = "root") + assertEquals("Bookmarks", friendlyRootTitle(testContext, root, withMobileRoot = true)) + assertEquals("Desktop Bookmarks", friendlyRootTitle(testContext, root, withMobileRoot = false)) + + val mobileRoot = folder.copy(guid = "mobile______", title = "mobile") + assertEquals("Bookmarks", friendlyRootTitle(testContext, mobileRoot, withMobileRoot = true)) + assertEquals("mobile", friendlyRootTitle(testContext, mobileRoot, withMobileRoot = false)) + + val menuRoot = folder.copy(guid = "menu________", title = "menu") + assertEquals("Bookmarks Menu", friendlyRootTitle(testContext, menuRoot, withMobileRoot = true)) + assertEquals("Bookmarks Menu", friendlyRootTitle(testContext, menuRoot, withMobileRoot = false)) + + val toolbarRoot = folder.copy(guid = "toolbar_____", title = "toolbar") + assertEquals("Bookmarks Toolbar", friendlyRootTitle(testContext, toolbarRoot, withMobileRoot = true)) + assertEquals("Bookmarks Toolbar", friendlyRootTitle(testContext, toolbarRoot, withMobileRoot = false)) + + val unfiledRoot = folder.copy(guid = "unfiled_____", title = "unfiled") + assertEquals("Other Bookmarks", friendlyRootTitle(testContext, unfiledRoot, withMobileRoot = true)) + assertEquals("Other Bookmarks", friendlyRootTitle(testContext, unfiledRoot, withMobileRoot = false)) + + val almostRoot = folder.copy(guid = "notRoot________", title = "root") + assertEquals("root", friendlyRootTitle(testContext, almostRoot, withMobileRoot = true)) + assertEquals("root", friendlyRootTitle(testContext, almostRoot, withMobileRoot = false)) + } + + @Test + fun `flatNodeList various cases`() { + val url = BookmarkNode( + BookmarkNodeType.ITEM, + "456", + "folder", + 0, + "Mozilla", + "http://mozilla.org", + null + ) + val url2 = BookmarkNode( + BookmarkNodeType.ITEM, + "8674", + "folder2", + 0, + "Mozilla", + "http://mozilla.org", + null + ) + assertEquals(emptyList(), url.flatNodeList(null)) + + val root = BookmarkNode( + BookmarkNodeType.FOLDER, + "root", + null, + 0, + "root", + null, + null + ) + assertEquals(listOf(BookmarkNodeWithDepth(0, root, null)), root.flatNodeList(null)) + assertEquals(emptyList(), root.flatNodeList("root")) + + val folder = BookmarkNode( + BookmarkNodeType.FOLDER, + "folder", + root.guid, + 0, + "folder", + null, + listOf(url) + ) + + val folder3 = BookmarkNode( + BookmarkNodeType.FOLDER, + "folder3", + "folder2", + 0, + "folder3", + null, + null + ) + + val folder2 = BookmarkNode( + BookmarkNodeType.FOLDER, + "folder2", + root.guid, + 0, + "folder2", + null, + listOf(folder3, url2) + ) + + val rootWithChildren = root.copy(children = listOf(folder, folder2)) + assertEquals( + listOf( + BookmarkNodeWithDepth(0, rootWithChildren, null), + BookmarkNodeWithDepth(1, folder, "root"), + BookmarkNodeWithDepth(1, folder2, "root"), + BookmarkNodeWithDepth(2, folder3, "folder2") + ), rootWithChildren.flatNodeList(null) + ) + + assertEquals( + listOf( + BookmarkNodeWithDepth(0, rootWithChildren, null), + BookmarkNodeWithDepth(1, folder, "root") + ), rootWithChildren.flatNodeList(excludeSubtreeRoot = "folder2") + ) + } +} From 70df3e8b151731262b518cf7fcbc4475e9066b68 Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Mon, 21 Sep 2020 13:32:19 -0700 Subject: [PATCH 20/36] For #15273: fix suspected syntax errors in code owners. I validated the paths used using a technique in this blog post: http://www.benjaminoakes.com/git/2018/08/10/Testing-changes-to-GitHub-CODEOWNERS/ So the syntax error(s) are likely within the names, such as this example. --- .github/CODEOWNERS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 58b4bf9b0..167052f85 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -18,10 +18,10 @@ # By default the Android Components team will be the owner for everything in # the repo. Unless a later match takes precedence. * @mozilla-mobile/ACT @mozilla-mobile/fenix -/.cron.yml /@mozilla-mobile/releng @mozilla-mobile/fenix -/.taskcluster.yml /@mozilla-mobile/releng @mozilla-mobile/fenix +/.cron.yml @mozilla-mobile/releng @mozilla-mobile/fenix +/.taskcluster.yml @mozilla-mobile/releng @mozilla-mobile/fenix /automation/ @mozilla-mobile/releng @mozilla-mobile/fenix -/taskcluster/ /@mozilla-mobile/releng @mozilla-mobile/fenix +/taskcluster/ @mozilla-mobile/releng @mozilla-mobile/fenix /.github/ @mozilla-mobile/releng @mozilla-mobile/fenix # --- PERFORMANCE START --- # From e04e20234dd8c2e834301e6917e02dc1f532122a Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Mon, 21 Sep 2020 13:33:18 -0700 Subject: [PATCH 21/36] For #15273: add warnings about codeowners; add perf team as CODEOWNERS owner. --- .github/CODEOWNERS | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 167052f85..81aa72d6d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -14,6 +14,12 @@ # to their GitHub account, for example user@example.com. # https://help.github.com/articles/about-codeowners/ +# WARNING: if there is a single syntax error in this file, CODEOWNERS +# WILL NOT WORK AT ALL. Please be careful when editing this file. +# +# You can use the technique described in this blog post to validate +# the paths you specify in .gitignore: +# http://www.benjaminoakes.com/git/2018/08/10/Testing-changes-to-GitHub-CODEOWNERS/ # By default the Android Components team will be the owner for everything in # the repo. Unless a later match takes precedence. @@ -30,6 +36,12 @@ # these changes (for now) but to be aware of them. Please let us know # if the CODEOWNERS system makes this impractical. We're available at # #perf-android-frontend on Matrix. + +# The perf team is relying on CODEOWNERS to catch regressions. If +# there is a single syntax error in the file, no rules will work. +# Therefore, we make the Perfomance team code owners of this file. +/.github/CODEOWNERS @mozilla-mobile/Performance + /app/src/*/java/org/mozilla/fenix/perf/** @mozilla-mobile/Performance *.pro @mozilla-mobile/Performance *proguard* @mozilla-mobile/Performance From 5cee8a11508cac743ab703ad514283f16e534916 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Thu, 24 Sep 2020 14:07:59 +0000 Subject: [PATCH 22/36] Update Android Components version to 61.0.20200923190103. --- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index bf36cbaa2..3eaedf542 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 = "61.0.20200923130632" + const val VERSION = "61.0.20200923190103" } From add60611b498133eb3a81b1f1bc9a634ed88c3e3 Mon Sep 17 00:00:00 2001 From: Jonathan Almeida Date: Thu, 24 Sep 2020 11:42:37 -0400 Subject: [PATCH 23/36] For #15349: Fixes SyncedTabs suggestion not clicking --- .../java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 74bad1b8c..46232e191 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 @@ -130,7 +130,7 @@ class AwesomeBarView( syncedTabsStorageSuggestionProvider = SyncedTabsStorageSuggestionProvider( components.backgroundServices.syncedTabsStorage, - components.useCases.tabsUseCases.addTab, + loadUrlUseCase, components.core.icons, DeviceIndicators( getDrawable(activity, R.drawable.ic_search_results_device_desktop), From 1adf4672489067c60ee5e369c169cecd33006ff8 Mon Sep 17 00:00:00 2001 From: Elise Richards Date: Thu, 24 Sep 2020 15:46:42 -0500 Subject: [PATCH 24/36] For #14280, #14743: Remove old search fragment (#15169) * Remove search fragment * Use new folder to search dialog * Rebase and lint * Update tests with search dialog nav directions * Rename interactor to match naming convention. Remove old controller and point everything to the dialog controller. --- .../org/mozilla/fenix/BrowserDirection.kt | 1 - .../java/org/mozilla/fenix/FeatureFlags.kt | 5 - .../java/org/mozilla/fenix/HomeActivity.kt | 5 +- .../fenix/browser/BaseBrowserFragment.kt | 2 +- .../toolbar/BrowserToolbarController.kt | 58 +-- .../org/mozilla/fenix/home/HomeFragment.kt | 13 +- .../home/intent/StartSearchIntentProcessor.kt | 2 +- .../SessionControlController.kt | 2 +- .../mozilla/fenix/search/SearchController.kt | 252 ---------- .../SearchDialogController.kt | 23 +- .../SearchDialogFragment.kt | 8 +- ...nteractor.kt => SearchDialogInteractor.kt} | 4 +- .../mozilla/fenix/search/SearchFragment.kt | 462 ------------------ app/src/main/res/navigation/nav_graph.xml | 30 +- .../DefaultBrowserToolbarControllerTest.kt | 16 +- .../DefaultSessionControlControllerTest.kt | 2 +- .../intent/StartSearchIntentProcessorTest.kt | 2 +- .../search/DefaultSearchControllerTest.kt | 346 ------------- .../SearchDialogControllerTest.kt | 14 +- ...rTest.kt => SearchDialogInteractorTest.kt} | 10 +- .../account/DefaultSyncControllerTest.kt | 4 +- 21 files changed, 68 insertions(+), 1193 deletions(-) delete mode 100644 app/src/main/java/org/mozilla/fenix/search/SearchController.kt rename app/src/main/java/org/mozilla/fenix/{searchdialog => search}/SearchDialogController.kt (92%) rename app/src/main/java/org/mozilla/fenix/{searchdialog => search}/SearchDialogFragment.kt (98%) rename app/src/main/java/org/mozilla/fenix/search/{SearchInteractor.kt => SearchDialogInteractor.kt} (95%) delete mode 100644 app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt delete mode 100644 app/src/test/java/org/mozilla/fenix/search/DefaultSearchControllerTest.kt rename app/src/test/java/org/mozilla/fenix/{searchdialog => search}/SearchDialogControllerTest.kt (95%) rename app/src/test/java/org/mozilla/fenix/search/{SearchInteractorTest.kt => SearchDialogInteractorTest.kt} (91%) diff --git a/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt b/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt index 84da2f843..e14c6c048 100644 --- a/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt +++ b/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt @@ -16,7 +16,6 @@ import androidx.annotation.IdRes enum class BrowserDirection(@IdRes val fragmentId: Int) { FromGlobal(0), FromHome(R.id.homeFragment), - FromSearch(R.id.searchFragment), FromSearchDialog(R.id.searchDialogFragment), FromSettings(R.id.settingsFragment), FromSyncedTabs(R.id.syncedTabsFragment), diff --git a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt index 01fd6d64c..b68d5f35f 100644 --- a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt +++ b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt @@ -21,11 +21,6 @@ object FeatureFlags { */ val syncedTabsInTabsTray = Config.channel.isNightlyOrDebug - /** - * Enables the new search experience - */ - const val newSearchExperience = true - /** * Enables showing the top frequently visited sites */ diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 7402463dd..7523c5ab4 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -86,8 +86,7 @@ import org.mozilla.fenix.library.history.HistoryFragmentDirections import org.mozilla.fenix.library.recentlyclosed.RecentlyClosedFragmentDirections import org.mozilla.fenix.perf.Performance import org.mozilla.fenix.perf.StartupTimeline -import org.mozilla.fenix.search.SearchFragmentDirections -import org.mozilla.fenix.searchdialog.SearchDialogFragmentDirections +import org.mozilla.fenix.search.SearchDialogFragmentDirections import org.mozilla.fenix.session.PrivateNotificationService import org.mozilla.fenix.settings.SettingsFragmentDirections import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections @@ -670,8 +669,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { NavGraphDirections.actionGlobalBrowser(customTabSessionId) BrowserDirection.FromHome -> HomeFragmentDirections.actionGlobalBrowser(customTabSessionId) - BrowserDirection.FromSearch -> - SearchFragmentDirections.actionGlobalBrowser(customTabSessionId) BrowserDirection.FromSearchDialog -> SearchDialogFragmentDirections.actionGlobalBrowser(customTabSessionId) BrowserDirection.FromSettings -> 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 b8921157d..7b224d6c7 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -848,7 +848,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session @CallSuper override fun onPause() { super.onPause() - if (findNavController().currentDestination?.id != R.id.searchFragment) { + if (findNavController().currentDestination?.id != R.id.searchDialogFragment) { view?.hideKeyboard() } } 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 56d2c3711..fbc5eddb1 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 @@ -9,7 +9,6 @@ import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager import mozilla.components.concept.engine.EngineView import mozilla.components.support.ktx.kotlin.isUrl -import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.browser.BrowserAnimator @@ -46,7 +45,6 @@ class DefaultBrowserToolbarController( private val engineView: EngineView, private val browserAnimator: BrowserAnimator, private val customTabSession: Session?, - private val useNewSearchExperience: Boolean = FeatureFlags.newSearchExperience, private val onTabCounterClicked: () -> Unit, private val onCloseTab: (Session) -> Unit ) : BrowserToolbarController { @@ -55,27 +53,14 @@ class DefaultBrowserToolbarController( get() = customTabSession ?: sessionManager.selectedSession override fun handleToolbarPaste(text: String) { - if (useNewSearchExperience) { - navController.nav( - R.id.browserFragment, - BrowserFragmentDirections.actionGlobalSearchDialog( - sessionId = currentSession?.id, - pastedText = text - ), - getToolbarNavOptions(activity) - ) - } else { - browserAnimator.captureEngineViewAndDrawStatically { - navController.nav( - R.id.browserFragment, - BrowserFragmentDirections.actionBrowserFragmentToSearchFragment( - sessionId = currentSession?.id, - pastedText = text - ), - getToolbarNavOptions(activity) - ) - } - } + navController.nav( + R.id.browserFragment, + BrowserFragmentDirections.actionGlobalSearchDialog( + sessionId = currentSession?.id, + pastedText = text + ), + getToolbarNavOptions(activity) + ) } override fun handleToolbarPasteAndGo(text: String) { @@ -94,26 +79,13 @@ class DefaultBrowserToolbarController( override fun handleToolbarClick() { metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)) - - if (useNewSearchExperience) { - navController.nav( - R.id.browserFragment, - BrowserFragmentDirections.actionGlobalSearchDialog( - currentSession?.id - ), - getToolbarNavOptions(activity) - ) - } else { - browserAnimator.captureEngineViewAndDrawStatically { - navController.nav( - R.id.browserFragment, - BrowserFragmentDirections.actionBrowserFragmentToSearchFragment( - currentSession?.id - ), - getToolbarNavOptions(activity) - ) - } - } + navController.nav( + R.id.browserFragment, + BrowserFragmentDirections.actionGlobalSearchDialog( + currentSession?.id + ), + getToolbarNavOptions(activity) + ) } override fun handleTabCounterClick() { 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 12a926898..708e5e19b 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -72,7 +72,6 @@ import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.support.ktx.android.content.res.resolveAttribute import org.mozilla.fenix.BrowserDirection -import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.browser.BrowserAnimator.Companion.getToolbarNavOptions @@ -431,8 +430,7 @@ class HomeFragment : Fragment() { // We call this onLayout so that the bottom bar width is correctly set for us to center // the CFR in. view.toolbar_wrapper.doOnLayout { - val willNavigateToSearch = - !bundleArgs.getBoolean(FOCUS_ON_ADDRESS_BAR) && FeatureFlags.newSearchExperience + val willNavigateToSearch = !bundleArgs.getBoolean(FOCUS_ON_ADDRESS_BAR) if (!browsingModeManager.mode.isPrivate && !willNavigateToSearch) { SearchWidgetCFR( context = view.context, @@ -464,7 +462,7 @@ class HomeFragment : Fragment() { updateTabCounter(requireComponents.core.store.state) - if (bundleArgs.getBoolean(FOCUS_ON_ADDRESS_BAR) && FeatureFlags.newSearchExperience) { + if (bundleArgs.getBoolean(FOCUS_ON_ADDRESS_BAR)) { navigateToSearch() } } @@ -739,15 +737,10 @@ class HomeFragment : Fragment() { } private fun navigateToSearch() { - val directions = if (FeatureFlags.newSearchExperience) { + val directions = HomeFragmentDirections.actionGlobalSearchDialog( sessionId = null ) - } else { - HomeFragmentDirections.actionGlobalSearch( - sessionId = null - ) - } nav(R.id.homeFragment, directions, getToolbarNavOptions(requireContext())) } diff --git a/app/src/main/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessor.kt b/app/src/main/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessor.kt index 6b5be6a51..e6f079c8b 100644 --- a/app/src/main/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessor.kt +++ b/app/src/main/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessor.kt @@ -46,7 +46,7 @@ class StartSearchIntentProcessor( out.removeExtra(HomeActivity.OPEN_TO_SEARCH) val directions = source?.let { - NavGraphDirections.actionGlobalSearch( + NavGraphDirections.actionGlobalSearchDialog( sessionId = null, searchAccessPoint = it ) diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt index 3ad615f26..c2a35df94 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt @@ -428,7 +428,7 @@ class DefaultSessionControlController( } override fun handlePaste(clipboardText: String) { - val directions = HomeFragmentDirections.actionGlobalSearch( + val directions = HomeFragmentDirections.actionGlobalSearchDialog( sessionId = null, pastedText = clipboardText ) diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchController.kt b/app/src/main/java/org/mozilla/fenix/search/SearchController.kt deleted file mode 100644 index 058bf715b..000000000 --- a/app/src/main/java/org/mozilla/fenix/search/SearchController.kt +++ /dev/null @@ -1,252 +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.search - -import android.content.DialogInterface -import android.content.Intent -import android.net.Uri -import android.os.Build -import android.text.SpannableString -import androidx.annotation.VisibleForTesting -import androidx.appcompat.app.AlertDialog -import androidx.navigation.NavController -import mozilla.components.browser.search.SearchEngine -import mozilla.components.browser.session.Session -import mozilla.components.browser.session.SessionManager -import mozilla.components.support.ktx.kotlin.isUrl -import org.mozilla.fenix.BrowserDirection -import org.mozilla.fenix.HomeActivity -import org.mozilla.fenix.R -import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint.ACTION -import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint.NONE -import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint.SUGGESTION -import org.mozilla.fenix.components.metrics.MetricController -import org.mozilla.fenix.components.metrics.MetricsUtils -import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore -import org.mozilla.fenix.crashes.CrashListActivity -import org.mozilla.fenix.ext.navigateSafe -import org.mozilla.fenix.settings.SupportUtils -import org.mozilla.fenix.settings.SupportUtils.MozillaPage.MANIFESTO -import org.mozilla.fenix.utils.Settings - -/** - * An interface that handles the view manipulation of the Search, triggered by the Interactor - */ -@Suppress("TooManyFunctions") -interface SearchController { - fun handleUrlCommitted(url: String) - fun handleEditingCancelled() - fun handleTextChanged(text: String) - fun handleUrlTapped(url: String) - fun handleSearchTermsTapped(searchTerms: String) - fun handleSearchShortcutEngineSelected(searchEngine: SearchEngine) - fun handleClickSearchEngineSettings() - fun handleExistingSessionSelected(session: Session) - fun handleExistingSessionSelected(tabId: String) - fun handleSearchShortcutsButtonClicked() - fun handleCameraPermissionsNeeded() -} - -@Suppress("TooManyFunctions", "LongParameterList") -class DefaultSearchController( - private val activity: HomeActivity, - private val sessionManager: SessionManager, - private val store: SearchFragmentStore, - private val navController: NavController, - private val settings: Settings, - private val metrics: MetricController, - private val clearToolbarFocus: () -> Unit -) : SearchController { - - override fun handleUrlCommitted(url: String) { - when (url) { - "about:crashes" -> { - // The list of past crashes can be accessed via "settings > about", but desktop and - // fennec users may be used to navigating to "about:crashes". So we intercept this here - // and open the crash list activity instead. - activity.startActivity(Intent(activity, CrashListActivity::class.java)) - } - "about:addons" -> { - val directions = SearchFragmentDirections.actionGlobalAddonsManagementFragment() - navController.navigateSafe(R.id.searchFragment, directions) - } - "moz://a" -> openSearchOrUrl(SupportUtils.getMozillaPageUrl(MANIFESTO)) - else -> if (url.isNotBlank()) { - openSearchOrUrl(url) - } - } - } - - private fun openSearchOrUrl(url: String) { - activity.openToBrowserAndLoad( - searchTermOrURL = url, - newTab = store.state.tabId == null, - from = BrowserDirection.FromSearch, - engine = store.state.searchEngineSource.searchEngine - ) - - val event = if (url.isUrl()) { - Event.EnteredUrl(false) - } else { - settings.incrementActiveSearchCount() - - val searchAccessPoint = when (store.state.searchAccessPoint) { - NONE -> ACTION - else -> store.state.searchAccessPoint - } - - searchAccessPoint?.let { sap -> - MetricsUtils.createSearchEvent( - store.state.searchEngineSource.searchEngine, - activity, - sap - ) - } - } - - event?.let { metrics.track(it) } - } - - override fun handleEditingCancelled() { - clearToolbarFocus() - } - - override fun handleTextChanged(text: String) { - // Display the search shortcuts on each entry of the search fragment (see #5308) - val textMatchesCurrentUrl = store.state.url == text - val textMatchesCurrentSearch = store.state.searchTerms == text - - store.dispatch(SearchFragmentAction.UpdateQuery(text)) - store.dispatch( - SearchFragmentAction.ShowSearchShortcutEnginePicker( - (textMatchesCurrentUrl || textMatchesCurrentSearch || text.isEmpty()) && - settings.shouldShowSearchShortcuts - ) - ) - store.dispatch( - SearchFragmentAction.AllowSearchSuggestionsInPrivateModePrompt( - text.isNotEmpty() && - activity.browsingModeManager.mode.isPrivate && - !settings.shouldShowSearchSuggestionsInPrivate && - !settings.showSearchSuggestionsInPrivateOnboardingFinished - ) - ) - } - - override fun handleUrlTapped(url: String) { - activity.openToBrowserAndLoad( - searchTermOrURL = url, - newTab = store.state.tabId == null, - from = BrowserDirection.FromSearch - ) - - metrics.track(Event.EnteredUrl(false)) - } - - override fun handleSearchTermsTapped(searchTerms: String) { - settings.incrementActiveSearchCount() - - activity.openToBrowserAndLoad( - searchTermOrURL = searchTerms, - newTab = store.state.tabId == null, - from = BrowserDirection.FromSearch, - engine = store.state.searchEngineSource.searchEngine, - forceSearch = true - ) - - val searchAccessPoint = when (store.state.searchAccessPoint) { - NONE -> SUGGESTION - else -> store.state.searchAccessPoint - } - - val event = searchAccessPoint?.let { sap -> - MetricsUtils.createSearchEvent( - store.state.searchEngineSource.searchEngine, - activity, - sap - ) - } - event?.let { metrics.track(it) } - } - - override fun handleSearchShortcutEngineSelected(searchEngine: SearchEngine) { - store.dispatch(SearchFragmentAction.SearchShortcutEngineSelected(searchEngine)) - val isCustom = - CustomSearchEngineStore.isCustomSearchEngine(activity, searchEngine.identifier) - metrics.track(Event.SearchShortcutSelected(searchEngine, isCustom)) - } - - override fun handleSearchShortcutsButtonClicked() { - val isOpen = store.state.showSearchShortcuts - store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(!isOpen)) - } - - override fun handleClickSearchEngineSettings() { - val directions = SearchFragmentDirections.actionGlobalSearchEngineFragment() - navController.navigateSafe(R.id.searchFragment, directions) - } - - override fun handleExistingSessionSelected(session: Session) { - sessionManager.select(session) - activity.openToBrowser( - from = BrowserDirection.FromSearch - ) - } - - override fun handleExistingSessionSelected(tabId: String) { - val session = sessionManager.findSessionById(tabId) - if (session != null) { - handleExistingSessionSelected(session) - } - } - - /** - * Creates and shows an [AlertDialog] when camera permissions are needed. - * - * In versions above M, [AlertDialog.BUTTON_POSITIVE] takes the user to the app settings. This - * intent only exists in M and above. Below M, [AlertDialog.BUTTON_POSITIVE] routes to a SUMO - * help page to find the app settings. - * - * [AlertDialog.BUTTON_NEGATIVE] dismisses the dialog. - */ - override fun handleCameraPermissionsNeeded() { - val dialog = buildDialog() - dialog.show() - } - - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - fun buildDialog(): AlertDialog.Builder { - return AlertDialog.Builder(activity).apply { - val spannableText = SpannableString( - activity.resources.getString(R.string.camera_permissions_needed_message) - ) - setMessage(spannableText) - setNegativeButton(R.string.camera_permissions_needed_negative_button_text) { - dialog: DialogInterface, _ -> - dialog.cancel() - } - setPositiveButton(R.string.camera_permissions_needed_positive_button_text) { - dialog: DialogInterface, _ -> - val intent: Intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS) - } else { - SupportUtils.createCustomTabIntent( - activity, - SupportUtils.getSumoURLForTopic( - activity, - SupportUtils.SumoTopic.QR_CAMERA_ACCESS - ) - ) - } - val uri = Uri.fromParts("package", activity.packageName, null) - intent.data = uri - dialog.cancel() - activity.startActivity(intent) - } - create() - } - } -} diff --git a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogController.kt b/app/src/main/java/org/mozilla/fenix/search/SearchDialogController.kt similarity index 92% rename from app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogController.kt rename to app/src/main/java/org/mozilla/fenix/search/SearchDialogController.kt index 476a7d615..742f7fab1 100644 --- a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogController.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchDialogController.kt @@ -2,7 +2,7 @@ * 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.searchdialog +package org.mozilla.fenix.search import android.content.DialogInterface import android.content.Intent @@ -25,12 +25,27 @@ import org.mozilla.fenix.components.metrics.MetricsUtils import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore import org.mozilla.fenix.crashes.CrashListActivity import org.mozilla.fenix.ext.navigateSafe -import org.mozilla.fenix.search.SearchController -import org.mozilla.fenix.search.SearchFragmentAction -import org.mozilla.fenix.search.SearchFragmentStore import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.utils.Settings +/** + * An interface that handles the view manipulation of the Search, triggered by the Interactor + */ +@Suppress("TooManyFunctions") +interface SearchController { + fun handleUrlCommitted(url: String) + fun handleEditingCancelled() + fun handleTextChanged(text: String) + fun handleUrlTapped(url: String) + fun handleSearchTermsTapped(searchTerms: String) + fun handleSearchShortcutEngineSelected(searchEngine: SearchEngine) + fun handleClickSearchEngineSettings() + fun handleExistingSessionSelected(session: Session) + fun handleExistingSessionSelected(tabId: String) + fun handleSearchShortcutsButtonClicked() + fun handleCameraPermissionsNeeded() +} + @Suppress("TooManyFunctions", "LongParameterList") class SearchDialogController( private val activity: HomeActivity, diff --git a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt similarity index 98% rename from app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt rename to app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt index ef15773d5..f290638ba 100644 --- a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt @@ -2,7 +2,7 @@ * 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.searchdialog +package org.mozilla.fenix.search import android.Manifest import android.app.Activity @@ -56,19 +56,13 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.isKeyboardVisible import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.search.SearchFragmentAction -import org.mozilla.fenix.search.SearchFragmentState -import org.mozilla.fenix.search.SearchFragmentStore -import org.mozilla.fenix.search.SearchInteractor import org.mozilla.fenix.search.awesomebar.AwesomeBarView -import org.mozilla.fenix.search.createInitialSearchFragmentState import org.mozilla.fenix.search.toolbar.ToolbarView import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.registerOnSharedPreferenceChangeListener import org.mozilla.fenix.widget.VoiceSearchActivity typealias SearchDialogFragmentStore = SearchFragmentStore -typealias SearchDialogInteractor = SearchInteractor @SuppressWarnings("LargeClass", "TooManyFunctions") class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchInteractor.kt b/app/src/main/java/org/mozilla/fenix/search/SearchDialogInteractor.kt similarity index 95% rename from app/src/main/java/org/mozilla/fenix/search/SearchInteractor.kt rename to app/src/main/java/org/mozilla/fenix/search/SearchDialogInteractor.kt index 823945453..cd35bdbce 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchDialogInteractor.kt @@ -14,8 +14,8 @@ import org.mozilla.fenix.search.toolbar.ToolbarInteractor * Provides implementations for the AwesomeBarView and ToolbarView */ @Suppress("TooManyFunctions") -class SearchInteractor( - private val searchController: SearchController +class SearchDialogInteractor( + private val searchController: SearchDialogController ) : AwesomeBarInteractor, ToolbarInteractor { override fun onUrlCommitted(url: String) { diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt deleted file mode 100644 index 7076dbf70..000000000 --- a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt +++ /dev/null @@ -1,462 +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.search - -import android.Manifest -import android.app.Activity.RESULT_OK -import android.content.Context -import android.content.DialogInterface -import android.content.Intent -import android.graphics.Typeface.BOLD -import android.graphics.Typeface.ITALIC -import android.os.Bundle -import android.speech.RecognizerIntent -import android.speech.RecognizerIntent.EXTRA_RESULTS -import android.text.style.StyleSpan -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.ViewStub -import androidx.appcompat.app.AlertDialog -import androidx.core.content.ContextCompat -import androidx.core.view.isVisible -import androidx.core.widget.NestedScrollView -import androidx.fragment.app.Fragment -import androidx.navigation.fragment.findNavController -import androidx.navigation.fragment.navArgs -import kotlinx.android.synthetic.main.fragment_search.* -import kotlinx.android.synthetic.main.fragment_search.view.* -import kotlinx.android.synthetic.main.search_suggestions_hint.view.* -import kotlinx.coroutines.ExperimentalCoroutinesApi -import mozilla.components.browser.toolbar.BrowserToolbar -import mozilla.components.concept.storage.HistoryStorage -import mozilla.components.feature.qr.QrFeature -import mozilla.components.lib.state.ext.consumeFrom -import mozilla.components.support.base.feature.UserInteractionHandler -import mozilla.components.support.base.feature.ViewBoundFeatureWrapper -import mozilla.components.support.ktx.android.content.getColorFromAttr -import mozilla.components.support.ktx.android.content.hasCamera -import mozilla.components.support.ktx.android.content.isPermissionGranted -import mozilla.components.support.ktx.android.content.res.getSpanned -import mozilla.components.support.ktx.android.view.hideKeyboard -import mozilla.components.ui.autocomplete.InlineAutocompleteEditText -import org.mozilla.fenix.BrowserDirection -import org.mozilla.fenix.HomeActivity -import org.mozilla.fenix.R -import org.mozilla.fenix.components.StoreProvider -import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore -import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.hideToolbar -import org.mozilla.fenix.ext.requireComponents -import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.search.awesomebar.AwesomeBarView -import org.mozilla.fenix.search.ext.areShortcutsAvailable -import org.mozilla.fenix.search.toolbar.ToolbarView -import org.mozilla.fenix.settings.SupportUtils -import org.mozilla.fenix.settings.registerOnSharedPreferenceChangeListener -import org.mozilla.fenix.widget.VoiceSearchActivity.Companion.SPEECH_REQUEST_CODE - -@Suppress("TooManyFunctions", "LargeClass") -class SearchFragment : Fragment(), UserInteractionHandler { - private lateinit var toolbarView: ToolbarView - private lateinit var awesomeBarView: AwesomeBarView - private val qrFeature = ViewBoundFeatureWrapper() - private lateinit var searchStore: SearchFragmentStore - private lateinit var searchInteractor: SearchInteractor - - private val speechIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH) - - @Suppress("LongMethod") - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val activity = activity as HomeActivity - val settings = activity.settings() - val args by navArgs() - - val view = inflater.inflate(R.layout.fragment_search, container, false) - - val isPrivate = activity.browsingModeManager.mode.isPrivate - - requireComponents.analytics.metrics.track(Event.InteractWithSearchURLArea) - - searchStore = StoreProvider.get(this) { - SearchFragmentStore( - createInitialSearchFragmentState( - activity, - requireComponents, - tabId = args.sessionId, - pastedText = args.pastedText, - searchAccessPoint = args.searchAccessPoint - ) - ) - } - - val searchController = DefaultSearchController( - activity = activity, - sessionManager = requireComponents.core.sessionManager, - store = searchStore, - navController = findNavController(), - settings = settings, - metrics = requireComponents.analytics.metrics, - clearToolbarFocus = ::clearToolbarFocus - ) - - searchInteractor = SearchInteractor( - searchController - ) - - awesomeBarView = AwesomeBarView( - activity, - searchInteractor, - view.findViewById(R.id.awesomeBar) - ) - setShortcutsChangedListener(CustomSearchEngineStore.PREF_FILE_SEARCH_ENGINES) - setShortcutsChangedListener(FenixSearchEngineProvider.PREF_FILE_SEARCH_ENGINES) - - view.scrollView.setOnScrollChangeListener { - _: NestedScrollView, _: Int, _: Int, _: Int, _: Int -> - view.hideKeyboard() - } - - toolbarView = ToolbarView( - requireContext(), - searchInteractor, - historyStorageProvider(), - isPrivate, - view.toolbar, - requireComponents.core.engine - ) - - toolbarView.view.addEditAction( - BrowserToolbar.Button( - ContextCompat.getDrawable(requireContext(), R.drawable.ic_microphone)!!, - requireContext().getString(R.string.voice_search_content_description), - visible = { - searchStore.state.searchEngineSource.searchEngine.identifier.contains("google") && - speechIsAvailable() && - settings.shouldShowVoiceSearch - }, - listener = ::launchVoiceSearch - ) - ) - - awesomeBarView.view.setOnEditSuggestionListener(toolbarView.view::setSearchTerms) - - val urlView = toolbarView.view - .findViewById(R.id.mozac_browser_toolbar_edit_url_view) - urlView?.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - - requireComponents.core.engine.speculativeCreateSession(isPrivate) - startPostponedEnterTransition() - return view - } - - private fun speechIsAvailable(): Boolean { - return (speechIntent.resolveActivity(requireContext().packageManager) != null) - } - - private fun setShortcutsChangedListener(preferenceFileName: String) { - requireContext().getSharedPreferences( - preferenceFileName, - Context.MODE_PRIVATE - ).registerOnSharedPreferenceChangeListener(viewLifecycleOwner) { _, _ -> - awesomeBarView.update(searchStore.state) - } - } - - private fun launchVoiceSearch() { - // Note if a user disables speech while the app is on the search fragment - // the voice button will still be available and *will* cause a crash if tapped, - // since the `visible` call is only checked on create. In order to avoid extra complexity - // around such a small edge case, we make the button have no functionality in this case. - if (!speechIsAvailable()) { return } - - requireComponents.analytics.metrics.track(Event.VoiceSearchTapped) - speechIntent.apply { - putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM) - putExtra(RecognizerIntent.EXTRA_PROMPT, requireContext().getString(R.string.voice_search_explainer)) - } - startActivityForResult(speechIntent, SPEECH_REQUEST_CODE) - } - - private fun clearToolbarFocus() { - toolbarView.view.hideKeyboard() - toolbarView.view.clearFocus() - } - - @ExperimentalCoroutinesApi - @SuppressWarnings("LongMethod") - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - search_scan_button.visibility = if (context?.hasCamera() == true) View.VISIBLE else View.GONE - - qrFeature.set( - createQrFeature(), - owner = this, - view = view - ) - - view.search_scan_button.setOnClickListener { - if (requireContext().settings().shouldShowCameraPermissionPrompt) { - requireComponents.analytics.metrics.track(Event.QRScannerOpened) - qrFeature.get()?.scan(R.id.container) - } else { - if (requireContext().isPermissionGranted(Manifest.permission.CAMERA)) { - requireComponents.analytics.metrics.track(Event.QRScannerOpened) - qrFeature.get()?.scan(R.id.container) - } else { - searchInteractor.onCameraPermissionsNeeded() - } - } - view.hideKeyboard() - search_scan_button.isChecked = false - requireContext().settings().setCameraPermissionNeededState = false - } - - view.search_engines_shortcut_button.setOnClickListener { - searchInteractor.onSearchShortcutsButtonClicked() - } - - val stubListener = ViewStub.OnInflateListener { _, inflated -> - inflated.learn_more.setOnClickListener { - (activity as HomeActivity) - .openToBrowserAndLoad( - searchTermOrURL = SupportUtils.getGenericSumoURLForTopic( - SupportUtils.SumoTopic.SEARCH_SUGGESTION - ), - newTab = searchStore.state.tabId == null, - from = BrowserDirection.FromSearch - ) - } - - inflated.allow.setOnClickListener { - inflated.visibility = View.GONE - context?.settings()?.shouldShowSearchSuggestionsInPrivate = true - context?.settings()?.showSearchSuggestionsInPrivateOnboardingFinished = true - searchStore.dispatch(SearchFragmentAction.SetShowSearchSuggestions(true)) - searchStore.dispatch(SearchFragmentAction.AllowSearchSuggestionsInPrivateModePrompt(false)) - requireComponents.analytics.metrics.track(Event.PrivateBrowsingShowSearchSuggestions) - } - - inflated.dismiss.setOnClickListener { - inflated.visibility = View.GONE - context?.settings()?.shouldShowSearchSuggestionsInPrivate = false - context?.settings()?.showSearchSuggestionsInPrivateOnboardingFinished = true - } - - inflated.text.text = - getString(R.string.search_suggestions_onboarding_text, getString(R.string.app_name)) - - inflated.title.text = - getString(R.string.search_suggestions_onboarding_title) - } - - view.search_suggestions_onboarding.setOnInflateListener((stubListener)) - - fill_link_from_clipboard.setOnClickListener { - (activity as HomeActivity) - .openToBrowserAndLoad( - searchTermOrURL = requireContext().components.clipboardHandler.url ?: "", - newTab = searchStore.state.tabId == null, - from = BrowserDirection.FromSearch - ) - } - - consumeFrom(searchStore) { - awesomeBarView.update(it) - updateSearchShortcutsIcon(it) - toolbarView.update(it) - updateSearchWithLabel(it) - updateClipboardSuggestion(it, requireContext().components.clipboardHandler.url) - updateSearchSuggestionsHintVisibility(it) - updateToolbarContentDescription(it) - } - - startPostponedEnterTransition() - } - - private fun createQrFeature(): QrFeature { - return QrFeature( - requireContext(), - fragmentManager = parentFragmentManager, - onNeedToRequestPermissions = { permissions -> - requestPermissions(permissions, REQUEST_CODE_CAMERA_PERMISSIONS) - }, - onScanResult = { result -> - search_scan_button.isChecked = false - activity?.let { - AlertDialog.Builder(it).apply { - val spannable = resources.getSpanned( - R.string.qr_scanner_confirmation_dialog_message, - getString(R.string.app_name) to StyleSpan(BOLD), - result to StyleSpan(ITALIC) - ) - setMessage(spannable) - setNegativeButton(R.string.qr_scanner_dialog_negative) { dialog: DialogInterface, _ -> - requireComponents.analytics.metrics.track(Event.QRScannerNavigationDenied) - dialog.cancel() - resetFocus() - } - setPositiveButton(R.string.qr_scanner_dialog_positive) { dialog: DialogInterface, _ -> - requireComponents.analytics.metrics.track(Event.QRScannerNavigationAllowed) - (activity as HomeActivity) - .openToBrowserAndLoad( - searchTermOrURL = result, - newTab = searchStore.state.tabId == null, - from = BrowserDirection.FromSearch - ) - dialog.dismiss() - resetFocus() - } - create() - }.show() - requireComponents.analytics.metrics.track(Event.QRScannerPromptDisplayed) - } - } - ) - } - - private fun updateToolbarContentDescription(searchState: SearchFragmentState) { - val urlView = toolbarView.view - .findViewById(R.id.mozac_browser_toolbar_edit_url_view) - toolbarView.view.contentDescription = - searchState.searchEngineSource.searchEngine.name + ", " + urlView.hint - urlView?.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - } - - override fun onResume() { - super.onResume() - - val provider = requireComponents.search.provider - - // The user has the option to go to 'Shortcuts' -> 'Search engine settings' to modify the default search engine. - // When returning from that settings screen we need to update it to account for any changes. - val currentDefaultEngine = provider.getDefaultEngine(requireContext()) - - if (searchStore.state.defaultEngineSource.searchEngine != currentDefaultEngine) { - searchStore.dispatch( - SearchFragmentAction.SelectNewDefaultSearchEngine - (currentDefaultEngine) - ) - } - - // Users can from this fragment go to install/uninstall search engines and then return. - val areShortcutsAvailable = provider.areShortcutsAvailable(requireContext()) - if (searchStore.state.areShortcutsAvailable != areShortcutsAvailable) { - searchStore.dispatch(SearchFragmentAction.UpdateShortcutsAvailability(areShortcutsAvailable)) - } - - updateClipboardSuggestion( - searchStore.state, - requireComponents.clipboardHandler.url - ) - - hideToolbar() - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { - if (requestCode == SPEECH_REQUEST_CODE && resultCode == RESULT_OK) { - intent?.getStringArrayListExtra(EXTRA_RESULTS)?.first()?.also { - toolbarView.view.edit.updateUrl(url = it, shouldHighlight = true) - searchInteractor.onTextChanged(it) - toolbarView.view.edit.focus() - } - } - } - - override fun onPause() { - super.onPause() - toolbarView.view.clearFocus() - } - - override fun onBackPressed(): Boolean { - return when { - qrFeature.onBackPressed() -> { - resetFocus() - true - } - else -> false - } - } - - private fun resetFocus() { - search_scan_button.isChecked = false - toolbarView.view.edit.focus() - toolbarView.view.requestFocus() - } - - private fun updateSearchWithLabel(searchState: SearchFragmentState) { - search_engine_shortcut.visibility = - if (searchState.showSearchShortcuts) View.VISIBLE else View.GONE - } - - private fun updateClipboardSuggestion(searchState: SearchFragmentState, clipboardUrl: String?) { - val visibility = - if (searchState.showClipboardSuggestions && searchState.query.isEmpty() && !clipboardUrl.isNullOrEmpty()) - View.VISIBLE else View.GONE - - fill_link_from_clipboard.visibility = visibility - divider_line.visibility = visibility - clipboard_url.text = clipboardUrl - - if (clipboardUrl != null && !((activity as HomeActivity).browsingModeManager.mode.isPrivate)) { - requireComponents.core.engine.speculativeConnect(clipboardUrl) - } - } - - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - when (requestCode) { - REQUEST_CODE_CAMERA_PERMISSIONS -> qrFeature.withFeature { - it.onPermissionsResult(permissions, grantResults) - resetFocus() - requireContext().settings().setCameraPermissionNeededState = false - } - else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults) - } - } - - private fun historyStorageProvider(): HistoryStorage? { - return if (requireContext().settings().shouldShowHistorySuggestions) { - requireComponents.core.historyStorage - } else null - } - - private fun updateSearchSuggestionsHintVisibility(state: SearchFragmentState) { - view?.apply { - findViewById(R.id.search_suggestions_onboarding)?.isVisible = state.showSearchSuggestionsHint - - search_suggestions_onboarding_divider?.isVisible = - search_engine_shortcut.isVisible && state.showSearchSuggestionsHint - } - } - - private fun updateSearchShortcutsIcon(searchState: SearchFragmentState) { - view?.apply { - search_engines_shortcut_button.isVisible = searchState.areShortcutsAvailable - - val showShortcuts = searchState.showSearchShortcuts - search_engines_shortcut_button.isChecked = showShortcuts - - val color = if (showShortcuts) R.attr.contrastText else R.attr.primaryText - search_engines_shortcut_button.compoundDrawables[0]?.setTint( - requireContext().getColorFromAttr(color) - ) - } - } - - companion object { - private const val REQUEST_CODE_CAMERA_PERMISSIONS = 1 - } -} diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 1d3033460..5bf3c9190 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -23,10 +23,6 @@ app:popUpTo="@id/homeFragment" app:popUpToInclusive="false" /> - - @@ -145,7 +141,7 @@ - - - - - - - { it.actionId == R.id.action_global_search }, + match { it.actionId == R.id.action_global_search_dialog }, null ) } diff --git a/app/src/test/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessorTest.kt b/app/src/test/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessorTest.kt index 2f7908383..0bc87d407 100644 --- a/app/src/test/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessorTest.kt @@ -55,7 +55,7 @@ class StartSearchIntentProcessorTest { verify { metrics.track(Event.SearchWidgetNewTabPressed) } verify { navController.navigate( - NavGraphDirections.actionGlobalSearch( + NavGraphDirections.actionGlobalSearchDialog( sessionId = null, searchAccessPoint = Event.PerformedSearch.SearchAccessPoint.WIDGET ), diff --git a/app/src/test/java/org/mozilla/fenix/search/DefaultSearchControllerTest.kt b/app/src/test/java/org/mozilla/fenix/search/DefaultSearchControllerTest.kt deleted file mode 100644 index 52b8dbbf0..000000000 --- a/app/src/test/java/org/mozilla/fenix/search/DefaultSearchControllerTest.kt +++ /dev/null @@ -1,346 +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.search - -import androidx.appcompat.app.AlertDialog -import androidx.navigation.NavController -import androidx.navigation.NavDirections -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.mockk -import io.mockk.mockkObject -import io.mockk.spyk -import io.mockk.unmockkObject -import io.mockk.verify -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest -import mozilla.components.browser.search.SearchEngine -import mozilla.components.browser.session.Session -import mozilla.components.browser.session.SessionManager -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.mozilla.fenix.BrowserDirection -import org.mozilla.fenix.HomeActivity -import org.mozilla.fenix.R -import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.components.metrics.MetricController -import org.mozilla.fenix.components.metrics.MetricsUtils -import org.mozilla.fenix.settings.SupportUtils -import org.mozilla.fenix.utils.Settings - -typealias AlertDialogBuilder = AlertDialog.Builder - -@ExperimentalCoroutinesApi -class DefaultSearchControllerTest { - - @MockK(relaxed = true) private lateinit var activity: HomeActivity - @MockK(relaxed = true) private lateinit var store: SearchFragmentStore - @MockK(relaxed = true) private lateinit var navController: NavController - @MockK private lateinit var searchEngine: SearchEngine - @MockK(relaxed = true) private lateinit var metrics: MetricController - @MockK(relaxed = true) private lateinit var settings: Settings - @MockK private lateinit var sessionManager: SessionManager - @MockK(relaxed = true) private lateinit var clearToolbarFocus: () -> Unit - - private lateinit var controller: DefaultSearchController - - @Before - fun setUp() { - MockKAnnotations.init(this) - mockkObject(MetricsUtils) - - every { store.state.tabId } returns "test-tab-id" - every { store.state.searchEngineSource.searchEngine } returns searchEngine - every { sessionManager.select(any()) } just Runs - every { navController.currentDestination } returns mockk { - every { id } returns R.id.searchFragment - } - every { MetricsUtils.createSearchEvent(searchEngine, activity, any()) } returns null - controller = DefaultSearchController( - activity = activity, - sessionManager = sessionManager, - store = store, - navController = navController, - settings = settings, - metrics = metrics, - clearToolbarFocus = clearToolbarFocus - ) - } - - @After - fun teardown() { - unmockkObject(MetricsUtils) - } - - @Test - fun handleUrlCommitted() { - val url = "https://www.google.com/" - - controller.handleUrlCommitted(url) - - verify { - activity.openToBrowserAndLoad( - searchTermOrURL = url, - newTab = false, - from = BrowserDirection.FromSearch, - engine = searchEngine - ) - } - verify { metrics.track(Event.EnteredUrl(false)) } - } - - @Test - fun handleSearchCommitted() { - val searchTerm = "Firefox" - - controller.handleUrlCommitted(searchTerm) - - verify { - activity.openToBrowserAndLoad( - searchTermOrURL = searchTerm, - newTab = false, - from = BrowserDirection.FromSearch, - engine = searchEngine - ) - } - verify { settings.incrementActiveSearchCount() } - } - - @Test - fun handleCrashesUrlCommitted() { - val url = "about:crashes" - every { activity.packageName } returns "org.mozilla.fenix" - - controller.handleUrlCommitted(url) - - verify { - activity.startActivity(any()) - } - } - - @Test - fun handleAddonsUrlCommitted() { - val url = "about:addons" - val directions = SearchFragmentDirections.actionGlobalAddonsManagementFragment() - - controller.handleUrlCommitted(url) - - verify { navController.navigate(directions) } - } - - @Test - fun handleMozillaUrlCommitted() { - val url = "moz://a" - - controller.handleUrlCommitted(url) - - verify { - activity.openToBrowserAndLoad( - searchTermOrURL = SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.MANIFESTO), - newTab = false, - from = BrowserDirection.FromSearch, - engine = searchEngine - ) - } - verify { metrics.track(Event.EnteredUrl(false)) } - } - - @Test - fun handleEditingCancelled() = runBlockingTest { - controller.handleEditingCancelled() - - verify { - clearToolbarFocus() - } - } - - @Test - fun handleTextChangedNonEmpty() { - val text = "fenix" - - controller.handleTextChanged(text) - - verify { store.dispatch(SearchFragmentAction.UpdateQuery(text)) } - } - - @Test - fun handleTextChangedEmpty() { - val text = "" - - controller.handleTextChanged(text) - - verify { store.dispatch(SearchFragmentAction.UpdateQuery(text)) } - } - - @Test - fun `show search shortcuts when setting enabled AND query empty`() { - val text = "" - every { settings.shouldShowSearchShortcuts } returns true - - controller.handleTextChanged(text) - - verify { store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(true)) } - } - - @Test - fun `show search shortcuts when setting enabled AND query equals url`() { - val text = "mozilla.org" - every { store.state.url } returns "mozilla.org" - every { settings.shouldShowSearchShortcuts } returns true - - controller.handleTextChanged(text) - - verify { store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(true)) } - } - - @Test - fun `do not show search shortcuts when setting enabled AND query non-empty`() { - val text = "mozilla" - - controller.handleTextChanged(text) - - verify { store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(false)) } - } - - @Test - fun `do not show search shortcuts when setting disabled AND query empty AND url not matching query`() { - every { settings.shouldShowSearchShortcuts } returns false - - val text = "" - - controller.handleTextChanged(text) - - verify { store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(false)) } - } - - @Test - fun `do not show search shortcuts when setting disabled AND query non-empty`() { - every { settings.shouldShowSearchShortcuts } returns false - - val text = "mozilla" - - controller.handleTextChanged(text) - - verify { store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(false)) } - } - - @Test - fun handleUrlTapped() { - val url = "https://www.google.com/" - - controller.handleUrlTapped(url) - - verify { - activity.openToBrowserAndLoad( - searchTermOrURL = url, - newTab = false, - from = BrowserDirection.FromSearch - ) - } - verify { metrics.track(Event.EnteredUrl(false)) } - } - - @Test - fun handleSearchTermsTapped() { - val searchTerms = "fenix" - - controller.handleSearchTermsTapped(searchTerms) - - verify { - activity.openToBrowserAndLoad( - searchTermOrURL = searchTerms, - newTab = false, - from = BrowserDirection.FromSearch, - engine = searchEngine, - forceSearch = true - ) - } - } - - @Test - fun handleSearchShortcutEngineSelected() { - val searchEngine: SearchEngine = mockk(relaxed = true) - - controller.handleSearchShortcutEngineSelected(searchEngine) - - verify { store.dispatch(SearchFragmentAction.SearchShortcutEngineSelected(searchEngine)) } - verify { metrics.track(Event.SearchShortcutSelected(searchEngine, false)) } - } - - @Test - fun handleClickSearchEngineSettings() { - val directions: NavDirections = - SearchFragmentDirections.actionGlobalSearchEngineFragment() - - controller.handleClickSearchEngineSettings() - - verify { navController.navigate(directions) } - } - - @Test - fun handleSearchShortcutsButtonClicked_alreadyOpen() { - every { store.state.showSearchShortcuts } returns true - - controller.handleSearchShortcutsButtonClicked() - - verify { store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(false)) } - } - - @Test - fun handleSearchShortcutsButtonClicked_notYetOpen() { - every { store.state.showSearchShortcuts } returns false - - controller.handleSearchShortcutsButtonClicked() - - verify { store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(true)) } - } - - @Test - fun handleExistingSessionSelected() { - val session = mockk() - - controller.handleExistingSessionSelected(session) - - verify { sessionManager.select(session) } - verify { activity.openToBrowser(from = BrowserDirection.FromSearch) } - } - - @Test - fun handleExistingSessionSelected_tabId_nullSession() { - every { sessionManager.findSessionById("tab-id") } returns null - - controller.handleExistingSessionSelected("tab-id") - - verify(inverse = true) { sessionManager.select(any()) } - verify(inverse = true) { activity.openToBrowser(from = BrowserDirection.FromSearch) } - } - - @Test - fun handleExistingSessionSelected_tabId() { - val session = mockk() - every { sessionManager.findSessionById("tab-id") } returns session - - controller.handleExistingSessionSelected("tab-id") - - verify { sessionManager.select(any()) } - verify { activity.openToBrowser(from = BrowserDirection.FromSearch) } - } - - @Test - fun `show camera permissions needed dialog`() { - val dialogBuilder: AlertDialogBuilder = mockk(relaxed = true) - - val spyController = spyk(controller) - every { spyController.buildDialog() } returns dialogBuilder - - spyController.handleCameraPermissionsNeeded() - - verify { dialogBuilder.show() } - } -} diff --git a/app/src/test/java/org/mozilla/fenix/searchdialog/SearchDialogControllerTest.kt b/app/src/test/java/org/mozilla/fenix/search/SearchDialogControllerTest.kt similarity index 95% rename from app/src/test/java/org/mozilla/fenix/searchdialog/SearchDialogControllerTest.kt rename to app/src/test/java/org/mozilla/fenix/search/SearchDialogControllerTest.kt index d30fde735..bbfed3309 100644 --- a/app/src/test/java/org/mozilla/fenix/searchdialog/SearchDialogControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/search/SearchDialogControllerTest.kt @@ -2,8 +2,9 @@ * 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.searchdialog +package org.mozilla.fenix.search +import androidx.appcompat.app.AlertDialog import androidx.navigation.NavController import androidx.navigation.NavDirections import io.mockk.MockKAnnotations @@ -30,8 +31,8 @@ import org.mozilla.fenix.R import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.components.metrics.MetricsUtils -import org.mozilla.fenix.search.AlertDialogBuilder -import org.mozilla.fenix.search.SearchFragmentAction +import org.mozilla.fenix.search.SearchDialogFragmentDirections.Companion.actionGlobalAddonsManagementFragment +import org.mozilla.fenix.search.SearchDialogFragmentDirections.Companion.actionGlobalSearchEngineFragment import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.utils.Settings @@ -140,7 +141,7 @@ class SearchDialogControllerTest { @Test fun handleAddonsUrlCommitted() { val url = "about:addons" - val directions = SearchDialogFragmentDirections.actionGlobalAddonsManagementFragment() + val directions = actionGlobalAddonsManagementFragment() controller.handleUrlCommitted(url) @@ -288,8 +289,7 @@ class SearchDialogControllerTest { @Test fun handleClickSearchEngineSettings() { - val directions: NavDirections = - SearchDialogFragmentDirections.actionGlobalSearchEngineFragment() + val directions: NavDirections = actionGlobalSearchEngineFragment() controller.handleClickSearchEngineSettings() @@ -347,7 +347,7 @@ class SearchDialogControllerTest { @Test fun `show camera permissions needed dialog`() { - val dialogBuilder: AlertDialogBuilder = mockk(relaxed = true) + val dialogBuilder: AlertDialog.Builder = mockk(relaxed = true) val spyController = spyk(controller) every { spyController.buildDialog() } returns dialogBuilder diff --git a/app/src/test/java/org/mozilla/fenix/search/SearchInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/search/SearchDialogInteractorTest.kt similarity index 91% rename from app/src/test/java/org/mozilla/fenix/search/SearchInteractorTest.kt rename to app/src/test/java/org/mozilla/fenix/search/SearchDialogInteractorTest.kt index 7a3af7ef0..7fc3761d1 100644 --- a/app/src/test/java/org/mozilla/fenix/search/SearchInteractorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/search/SearchDialogInteractorTest.kt @@ -14,15 +14,15 @@ import org.junit.Before import org.junit.Test @ExperimentalCoroutinesApi -class SearchInteractorTest { +class SearchDialogInteractorTest { - lateinit var searchController: DefaultSearchController - lateinit var interactor: SearchInteractor + lateinit var searchController: SearchDialogController + lateinit var interactor: SearchDialogInteractor @Before fun setup() { searchController = mockk(relaxed = true) - interactor = SearchInteractor( + interactor = SearchDialogInteractor( searchController ) } @@ -47,7 +47,7 @@ class SearchInteractorTest { @Test fun onTextChanged() { - val interactor = SearchInteractor(searchController) + val interactor = SearchDialogInteractor(searchController) interactor.onTextChanged("test") diff --git a/app/src/test/java/org/mozilla/fenix/settings/account/DefaultSyncControllerTest.kt b/app/src/test/java/org/mozilla/fenix/settings/account/DefaultSyncControllerTest.kt index 1d1424d01..1866d9e44 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/account/DefaultSyncControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/account/DefaultSyncControllerTest.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.settings.account +import androidx.appcompat.app.AlertDialog import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK @@ -13,7 +14,6 @@ import io.mockk.verify import org.junit.Before import org.junit.Test import org.mozilla.fenix.HomeActivity -import org.mozilla.fenix.search.AlertDialogBuilder class DefaultSyncControllerTest { @@ -28,7 +28,7 @@ class DefaultSyncControllerTest { @Test fun `show camera permissions needed dialog`() { - val dialogBuilder: AlertDialogBuilder = mockk(relaxed = true) + val dialogBuilder: AlertDialog.Builder = mockk(relaxed = true) val spyController = spyk(syncController) every { spyController.buildDialog() } returns dialogBuilder From e8855c09e6ec109ec2464ed2258e188e4c30fdec Mon Sep 17 00:00:00 2001 From: Kate Glazko Date: Tue, 22 Sep 2020 12:54:21 -0700 Subject: [PATCH 25/36] For #15320: Ensure Mimetype Other PDF Shows PDF Icon --- app/src/main/java/org/mozilla/fenix/ext/DownloadItem.kt | 1 + app/src/test/java/org/mozilla/fenix/ext/DownloadItemKtTest.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/app/src/main/java/org/mozilla/fenix/ext/DownloadItem.kt b/app/src/main/java/org/mozilla/fenix/ext/DownloadItem.kt index 93fa26aea..df4854561 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/DownloadItem.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/DownloadItem.kt @@ -14,6 +14,7 @@ fun DownloadItem.getIcon(): Int { return when { fileName?.endsWith("apk") == true -> R.drawable.ic_file_type_apk fileName?.endsWith("zip") == true -> R.drawable.ic_file_type_zip + fileName?.endsWith("pdf") == true -> R.drawable.ic_file_type_document else -> R.drawable.ic_file_type_default } } diff --git a/app/src/test/java/org/mozilla/fenix/ext/DownloadItemKtTest.kt b/app/src/test/java/org/mozilla/fenix/ext/DownloadItemKtTest.kt index d4881dddb..e5fdf24b4 100644 --- a/app/src/test/java/org/mozilla/fenix/ext/DownloadItemKtTest.kt +++ b/app/src/test/java/org/mozilla/fenix/ext/DownloadItemKtTest.kt @@ -23,6 +23,7 @@ class DownloadItemKtTest { assertEquals(R.drawable.ic_file_type_zip, downloadItem.copy(contentType = "application/gzip").getIcon()) assertEquals(R.drawable.ic_file_type_apk, downloadItem.copy(contentType = null, fileName = "Fenix.apk").getIcon()) assertEquals(R.drawable.ic_file_type_zip, downloadItem.copy(contentType = null, fileName = "Fenix.zip").getIcon()) + assertEquals(R.drawable.ic_file_type_document, downloadItem.copy(contentType = null, fileName = "Fenix.pdf").getIcon()) assertEquals(R.drawable.ic_file_type_default, downloadItem.copy(contentType = null, fileName = null).getIcon()) } } From f32d2011dfd47aa62da8fe407eebd833f5dcfe93 Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Fri, 25 Sep 2020 00:04:11 +0000 Subject: [PATCH 26/36] Import l10n. --- app/src/main/res/values-cy/strings.xml | 7 + app/src/main/res/values-gn/strings.xml | 7 + app/src/main/res/values-hu/strings.xml | 7 + app/src/main/res/values-kk/strings.xml | 7 + app/src/main/res/values-nn-rNO/strings.xml | 2 + app/src/main/res/values-oc/strings.xml | 2 + app/src/main/res/values-pt-rBR/strings.xml | 7 + app/src/main/res/values-tg/strings.xml | 1151 ++++++++++++++++++++ app/src/main/res/values-vi/strings.xml | 7 + app/src/main/res/values-zh-rCN/strings.xml | 9 +- app/src/main/res/values-zh-rTW/strings.xml | 7 + 11 files changed, 1212 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/values-tg/strings.xml diff --git a/app/src/main/res/values-cy/strings.xml b/app/src/main/res/values-cy/strings.xml index 8fe1ab05a..eb27ed6ce 100644 --- a/app/src/main/res/values-cy/strings.xml +++ b/app/src/main/res/values-cy/strings.xml @@ -1162,6 +1162,8 @@ Fodd bynnag, gall fod yn llai sefydlog. Llwythwch ein porwr Beta i gael profiad Mewngofnodi gyda’ch camera Defnyddiwch e-bost yn lle hynny + + Crëwch un i gydweddu Firefox rhwng dyfeisiau.]]> Bydd Firefox yn peidio cydweddu eich cyfrif ond ni fydd yn dileu eich data pori ar y ddyfais hon. @@ -1270,6 +1272,11 @@ Fodd bynnag, gall fod yn llai sefydlog. Llwythwch ein porwr Beta i gael profiad The first parameter is the app name --> %s| Llyfrgelloedd OSS + + Ailgyfeirio Tracwyr + + Yn clirio cwcis wedi’u gosod i ailgyfeirio i wefannau tracio hysbys. + Cefnogaeth diff --git a/app/src/main/res/values-gn/strings.xml b/app/src/main/res/values-gn/strings.xml index d6f758bee..a5541e849 100644 --- a/app/src/main/res/values-gn/strings.xml +++ b/app/src/main/res/values-gn/strings.xml @@ -1180,6 +1180,8 @@ Eñepyrũ tembiapo ne ra’ãngamýi ndive Eipuru ñandutiveve + + Emoheñói embojuehe hag̃ua Firefox mba’e’oka ndive.]]> Firefox nombojuehemo’ãvéima ne mba’ete, hákatu nomboguemo’ãi ne kundahára mba’ekuaarã ko mba’e’oka pegua. @@ -1292,6 +1294,11 @@ The first parameter is the app name --> %s | OSS Arandukarenda + + Embohapejey tapykuehoha + + Embogue umi kookie oñembohapejeýva ñanduti renda rapykuehohápe. + Ñepytyvõ diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 20ecf2ee8..45e94f06b 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -1174,6 +1174,8 @@ Jelentkezzen be a kamerájával E-mail használata ehelyett + + Hozzon létre egyet, hogy szinkronizálja a Firefoxot az eszközök között.]]> A Firefox leállítja a szinkronizációt a fiókjával, de nem töröl semmilyen böngészési adatot erről az eszközről. @@ -1283,6 +1285,11 @@ The first parameter is the app name --> %s | Nyílt forráskódú programkönyvtárak + + Nyomkövetők átirányítása + + Törli az ismert nyomkövető webhelyekre történő átirányítással beállított sütiket. + Támogatás diff --git a/app/src/main/res/values-kk/strings.xml b/app/src/main/res/values-kk/strings.xml index 005005213..9c81006e0 100644 --- a/app/src/main/res/values-kk/strings.xml +++ b/app/src/main/res/values-kk/strings.xml @@ -1155,6 +1155,8 @@ Камерамен кіріңіз Оның орнына эл. поштаны пайдалану + + тіркелгі жасаңыз.]]> Firefox тіркелгіңізбен синхрондауды тоқтатады, бірақ, бұл құрылғыда барлық шолу деректері қалады. @@ -1267,6 +1269,11 @@ The first parameter is the app name --> %s | Ашық библиотекалар + + Қайта бағыттайтын трекерлер + + Белгілі бақылайтын веб-сайттарға қайта бағыттау арқылы орнатылған cookie файлдарын тазарту. + Қолдау diff --git a/app/src/main/res/values-nn-rNO/strings.xml b/app/src/main/res/values-nn-rNO/strings.xml index f2bc07d25..482e58a99 100644 --- a/app/src/main/res/values-nn-rNO/strings.xml +++ b/app/src/main/res/values-nn-rNO/strings.xml @@ -1176,6 +1176,8 @@ Logg inn med kameraet ditt Bruk e-post i staden + + Lag ein for å synkronisere Firefox mellom einingar.]]> Firefox vil stoppe synkroniseringa med kontoen din, men vil ikkje slette nettleserdataa dine på denne eininga. diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml index abf8ed74d..6696940e1 100644 --- a/app/src/main/res/values-oc/strings.xml +++ b/app/src/main/res/values-oc/strings.xml @@ -139,6 +139,8 @@ Installar Onglets sincronizats + + Tornar sincronizar Recercar dins la pagina diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index a6d9a7000..5b6f43c14 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -1163,6 +1163,8 @@ Entre com sua câmera Usar e-mail + + Crie uma para sincronizar o Firefox entre dispositivos.]]> O Firefox deixará de sincronizar com sua conta, mas não excluirá seus dados de navegação neste dispositivo. @@ -1274,6 +1276,11 @@ The first parameter is the app name --> %s | Bibliotecas de código aberto + + Rastreadores de redirecionamento + + Limpa cookies definidos por redirecionamento de sites de rastreamento conhecidos. + Suporte diff --git a/app/src/main/res/values-tg/strings.xml b/app/src/main/res/values-tg/strings.xml new file mode 100644 index 000000000..f7b2b6379 --- /dev/null +++ b/app/src/main/res/values-tg/strings.xml @@ -0,0 +1,1151 @@ + + + + %s-и махфӣ + + %s (Махфӣ) + + + Имконоти бештар + + Фаъол кардан баррасии махфӣ + + Ғайрифаъол кардан баррасии махфӣ + + Нишониеро ҷустуҷӯ кунед ё ворид намоед + + Варақаҳои кушодашудаи шумо дар ин ҷо нишон дода мешаванд. + + Варақаҳои махфии шумо дар ин ҷо нишон дода мешаванд. + + + 1 варақаи кушодашуда. Барои гузариш байни варақаҳо зарба занед. + + %1$s варақаи кушодашуда. Барои гузариш байни варақаҳо зарба занед. + + %1$d интихоб шуд + + Илова кардани маҷмӯаи нав + + Ном + + Маҷмӯаро интихоб кунед + + Аз реҷаи серинтихоб баромадан + + + Нигоҳ доштани варақаҳои интихобшуда дар маҷмӯа + + %1$s интихоб карда шуд + + Интихоби %1$s бекор карда шуд + + + Аз реҷаи серинтихоб баромад + + Реҷаи серинтихоб фаъол шуд, варақаҳоеро барои нигоҳ доштан дар маҷмӯа интихоб намоед + + Интихоб шуд + + + %1$s аз ҷониби Mozilla истеҳсол карда шудааст. + + + + Шумо дар реҷаи махфӣ қарор доред + + Нест кардани ҷаласа + + + + Барои кушодани варақаҳои махфӣ аз экрани асосӣ миёнбуреро илова кунед. + + Илова кардани миёнбур + + Не, ташаккур + + + Илова кардани виҷет + + Ҳоло не + + + Гузариш ба танзимот + + Нодида гузарондан + + + Гузариш ба танзимот + + + Нодида гузарондан + + + Имконоти намоиш + + Нодида гузарондан + + + + Варақаи нав + + Варақаи махфии нав + + Сомонаҳои беҳтарин + + + + Варақаҳои кушодашуда + + Бозгашт + + Ба пеш + + Нав кардан + + Истодан + + Хатбарак + + Таҳрир кардани хатбарак + + Ҷузъҳои иловагӣ + + Дар ин ҷо ягон ҷузъи иловагӣ нест + + Кумак + + Чӣ нав аст + + Танзимот + + Китобхона + + + Барои компютери мизи корӣ + + Илова кардан ба экрани асосӣ + + Насб кардан + + Варақаҳои ҳамоҳангшуда + + Аз нав ҳамоҳанг кардан + + Ҷустуҷӯ дар саҳифа + + Варақаи махфӣ + + Варақаи нав + + Нигоҳ доштан дар маҷмӯа + + Мубодила кардан + + Мубодила кардан тавассути… + + Кушодан дар %1$s + + ДАР %1$s АСОС МЕЁБАД + + Дар %1$s асос меёбад + + Намоиши хониш + + Пӯшидани намоиши хониш + + Кушодан дар барнома + + Намуди зоҳирӣ + + + Пайваст нашуд. Нақшаи URL шинохтанашаванда аст. + + + + Забони интихобшуда + + Ҷустуҷӯ + + Забон аз дастгоҳ асос меёбад + + Ҷустуҷӯи забон + + + + Ҷустуҷӯ + + Низоми ҷустуҷӯӣ + + Танзимоти низоми ҷустуҷӯӣ + + Ин дафъа бо зерин ҷустуҷӯ кунед: + + Гузоштани пайванд аз ҳофизаи муваққатӣ + + Иҷозат додан + + Иҷозат дода нашавад + + Маълумоти бештар + + + + Ҷустуҷӯ + + Ҷустуҷӯ дар Интернет + + Ҷустуҷӯи овозӣ + + + + Танзимот + + Асосҳо + + Умумӣ + + Дар бораи барнома + + Низоми ҷустуҷӯии пешфарз + + Ҷустуҷӯ + + Лавҳаи нишонӣ + + Кумак + + Баҳодиҳӣ дар Google Play + + Изҳори назари худро пешниҳод кунед + + Дар бораи %1$s + + Ҳуқуқҳои шумо + + Ниҳонвожаҳо + + + Кортҳои қарзӣ ва нишониҳо + + Гузоштан ҳамчун браузери пешфарз + + Иловагӣ + + Махфият + + Махфият ва амният + + Иҷозатҳои сомона + + Баррасии махфӣ + + Кушодани пайвандҳо дар варақаи махфӣ + + Қобилияти дастрасӣ + + Ҳисоб + + Ворид шудан + + Навори абзорҳо + + Мавзӯъ + + Асосӣ + + Ишораҳо + + Фармоишдиҳӣ + + Ҳисоби Firefox + + Барои барқарор кардани ҳамоҳангсозӣ аз нав пайваст шавед + + Забон + + Интихоби маълумот + + Маҷмӯаи маълумот + + Огоҳиномаи махфият + + Абзорҳои барномасозӣ + + Намоиш додани низомҳои ҷустуҷӯӣ + + Намоиш додани пешниҳодҳои ҷустуҷӯ + + Намоиш додани ҷустуҷӯи овозӣ + + Намоиш додан дар ҷаласаҳои махфӣ + + Намоиш додани пешниҳодҳо аз ҳофизаи муваққатӣ + + Намоиш додани таърихи тамошо + + Ҷустуҷӯи хатбаракҳо + + Ҷустуҷӯи варақаҳои ҳамоҳангшуда + + Танзимоти ҳисоб + + Пуркунии худкори нишонаҳои URL + + Кушодани пайвандҳо дар барномаҳо + + Мудири берунии боргириҳо + + Ҷузъҳои иловагӣ + + Огоҳиномаҳо + + + + Ҳозир ҳамоҳанг кунед + + Интихоб кунед, ки чӣ ҳамоҳанг карда мешавад + + Таърих + + Хатбаракҳо + + Воридшавиҳо + + Варақаҳои кушодашуда + + Баромад + + + Номи дастгоҳ + + Номи дастгоҳ наметавонад холӣ бошад. + + Ҳамоҳангсозӣ… + + + %1$s дар %2$s %3$s + + + + Варақаҳо аз дастгоҳҳои дигар + + Огоҳиномаҳо дар бораи варақаҳое, ки аз дигар дастгоҳҳои Firefox гирифта шудаанд. + + Рарақаи қабулшуда + + Рарақаҳои қабулшуда + + Варақа аз %s + + + + Муҳофизат аз пайгирӣ + + Муҳофизат аз пайгирӣ + + Муҳтаво ва скриптҳоеро, ки шуморо онлайн пайгирӣ мекунанд, маҳдуд кунед + + Истисноҳо + + Муҳофизат аз пайгирӣ барои ин сомонаҳо хомӯш аст + + Фаъол кардани барои ҳамаи сомонаҳо + + Истисноҳо ба шумо имкон медиҳанд, ки муҳофизат аз пайгириро барои сомонаҳои интихобшуда ғайрифаъол кунед. + + Маълумоти бештар + + + Комилан ғайрифаъол карда шуд, Барои фаъол кардани он ба Танзимот гузаред. + + + Телеметрия + + Истифодабарӣ ва маълумоти техникӣ + + Маълумоти маркетингӣ + + Таҷрибаҳо + + Хадамоти ҷойгиршавии Mozilla + + Гузориши саломатии %s + + + + Фаъол кардани ҳамоҳангсозӣ + + Ворид шудан + + Барои аз нав пайваст шудан ворид шавед + + Барҳам додани ҳисоб + + + Кушодани камера + + Бекор кардан + + + + Боло + + Поён + + + + Равшан + + Торик + + Аз ҷониби сарфаи батарея муқаррар карда шудааст + + Дар мавзӯи дастгоҳ асос меёбад + + + + Барон нав кардан кашед + + Барои пинҳон кардани навори абзорҳо ҳаракат кунед + + + + Ҷаласаҳо + + Аксҳои экран + + Боргириҳо + + Хатбаракҳо + + Хатбаракҳо дар компютери мизи корӣ + + Менюи хатбаракҳо + + Навори хатбаракҳо + + Хатбаракҳои дигар + + Таърих + + Варақаҳои ҳамоҳангшуда + + Рӯйхати хониш + + Ҷустуҷӯ + + Танзимот + + Менюи унсури таърих + + Пӯшидан + + + Намоиш додани таърихи пурра + + %d варақа + + %d варақа + + + + Пӯшидани варақаҳо + + Ба таври дастӣ + + Пас аз як рӯз + + Пас аз як ҳафта + + Пас аз як моҳ + + + + Варақаҳои кушодашуда + + + Ҷаласаи махфӣ + + Варақаҳои махфӣ + + Илова кардани варақа + + Илова кардани варақаи махфӣ + + Махфӣ + + Варақаҳои кушодашуда + + Нигоҳ доштан дар маҷмӯа + + Мубодила кардани ҳамаи варақаҳо + + Танзимоти варақа + + Пӯшидани ҳамаи варақаҳо + + Варақаи нав + + Гузариш ба саҳифаи асосӣ + + Пӯшидани варақа + + Пӯшидани варақаи %s + + Менюи варақаҳои кушодашуда + + Пӯшидани ҳамаи варақаҳо + + Мубодила кардани варақаҳо + + Нигоҳ доштани варақаҳо дар маҷмӯа + + Менюи варақаҳо + + Мубодила кардани варақа + + Нест кардан + + Нигоҳ доштан + + Мубодила кардан + + Тасвири ҷаласаи ҷорӣ + + Нигоҳ доштан дар маҷмӯа + + Нест кардани маҷмӯа + + Иваз кардани номи маҷмӯа + + Варақаҳои кушодашуда + + Тоза кардан + + Нест кардан аз таърих + + %1$s (Реҷаи махфӣ) + + Нигоҳ доштан + + + + Нест кардани таърих + + Шумо мутмаин ҳастед, ки мехоҳед таърихи худро нест намоед? + + Таърих нест карда шуд + + %1$s нест карда шуд + + Пок кардан + + Нусха бардоштан + + Мубодила кардан + + Кушодан дар варақаи нав + + Кушодан дар варақаи махфӣ + + Нест кардан + + %1$d интихоб шуд + + Нест кардани %1$d ҷузъ + + 24 соати охир + + 7 рӯзи охир + + 30 рӯзи охир + + Пештар + + Ягон таърих нест + + + + Ягон боргирӣ нест + + %1$d интихоб шуд + + + + Мутаассифона, %1$s ин саҳифаро бор карда наметавонад. + + + Шумо метавонед ин варақаро дар поён барқарор кунед ё пӯшед. + + Фиристодани гузориш дар бораи садама ба Mozilla + + Пӯшидани варақа + + Барқарор кардани варақа + + + Имконоти ҷаласа + + + Мубодила кардани ҷаласа + + + + Менюи хатбаракҳо + + Таҳрир кардани хатбарак + + Интихоб кардани ҷузвадон + + Шумо мутмаин ҳастед, ки мехоҳед ин ҷузвадонро нест намоед? + + %s ҷузъҳои интихобшударо нест мекунад. + + %1$s нест карда шуд + + Илова кардани ҷузвадон + + Хатбарак эҷод карда шуд. + + Хатбарак нигоҳ дошта шуд! + + ТАҲРИР КАРДАН + + Таҳрир кардан + + Интихоб кардан + + Нусха бардоштан + + Мубодила кардан + + Кушодан дар варақаи нав + + Кушодан дар варақаи махфӣ + + Нест кардан + + Нигоҳ доштан + + %1$d интихоб шуд + + Таҳрир кардани хатбарак + + Таҳрир кардани ҷузвадон + + Барои дидани хатбаракҳои ҳамоҳангшуда ворид шавед + + Нишонии URL + + ҶУЗВАДОН + + НОМ + + Илова кардани ҷузвадон + + Интихоб кардани ҷузвадон + + Бояд унвон дошта бошад + + Нишонии URL беэътибор аст + + Ягон хатбарак нест + + %1$s нест карда шуд + + Хатбаракҳо нест карда шуданд + + + + Иҷозатҳо + + Гузариш ба Танзимот + + Тавсияшуда + + Идоракунии иҷозатҳои сомона + + Пок кардани иҷозатҳо + + Пок кардани иҷозат + + Пок кардани иҷозатҳо дар ҳамаи сомонаҳо + + Пахши худкор + + Камера + + Микрофон + + Ҷойгиршавӣ + + + Огоҳинома + + Бояд манъ карда шавад + + Бояд иҷозат дода шавад + + Аз тарафи Android манъ карда шуд + + Истисноҳо + + Фаъол + + Ғайрифаъол + + + Иҷозат додани аудио ва видео + + Фаъол + + Ғайрифаъол + + + + Маҷмӯаҳо + + Менюи маҷмӯаҳо + + Пӯшидан + + Нигоҳ доштан + + Намоиш + + + Маҷмӯаи %d + + + + Фиристодан ва мубодила кардан + + Мубодила кардан + + Мубодила кардан + + Мубодила кардани пайванд + + Фиристодан ба дастгоҳ + + Ҳамаи амалҳо + + Барои ҳамоҳангсозӣ ворид шавед + + Фиристодан ба ҳамаи дастгоҳҳо + + Барои ҳамоҳангсозӣ аз нав пайваст шавед + + Офлайн + + Фаҳмо + + + + Ҷаласаи баррасии махфӣ + + Нест кардани варақаҳои махфӣ + + Пӯшидани варақаҳои махфӣ + + Кушодан + + Нест кардан ва кушодан + + Дар асоси + + Маҷмӯа нест карда шуд + + Номи маҷмӯа иваз карда шуд + + Варақа нест карда шуд + + Варақаҳо нест карда шуданд + + Варақа пӯшида шуд + + Варақаҳо пӯшида шуданд + + Ба сомонаҳои беҳтарин илова карда шуд! + + Варақаи махфӣ пӯшида шуд + + Варақаҳои махфӣ пӯшида шуданд + + Варақаҳои махфӣ нест карда шуданд + + Сомона хориҷ карда шуд + + Тасдиқ кардан + + Ба %1$s иҷозат диҳед, ки %2$s-ро кушояд + + ИҶОЗАТ ДОДАН + + РАД КАРДАН + + Шумо мутмаин ҳастед, ки мехоҳед %1$s-ро нест намоед? + + %1$s-ро нест мекунед? + + Нест кардан + + Бекор кардан + + Ба реҷаи экрани пурра ворид шуда истодааст + + URL нусха бардошта шуд + + Андозаи ҳуруф + + + Андозагирии худкори ҳуруф + + + Нест кардани маълумоти баррасӣ + + Варақаҳои кушодашуда + + %d варақа + + Таърих + + %d саҳифа + + Кукиҳо + + + Бекор кардан + + Нест кардан + + + + Хуш омадед ба %s! + + Маълумоти бештар + + Ҳамоҳангсозӣ фаъол аст + + Воридшавӣ иҷро нашуд + + Махфияти худкор + + Кушодани танзимот + + Махфияти шумо + + Огоҳиномаи махфияти моро хонед + + Пӯшидан + + + Оғоз кардани баррасӣ + + + + Мавзӯи худро интихоб кунед + + Худкор + + Ба танзимоти дастгоҳи шумо мутобиқат мекунад + + Мавзӯи торик + + Мавзӯи равшан + + + Варақаҳо фиристода шуданд! + + Варақа фиристода шуд! + + Ирсол ғайриимкон аст + + АЗ НАВ КӮШИШ КАРДАН + + Қатъ кардани пайваст + + Бекор кардан + + Ҷузвдонҳои пешфарзро таҳрир карда наметавонад + + + + Танзимоти муҳофизат + + Муҳофизати такмилёфта аз пайгирӣ + + Маълумоти бештар + + Стандартӣ (пешфарз) + + Фармоишӣ + + + Кукиҳо + + Муҳтавои пайгирикунанда + + Дар ҳамаи варақаҳо + + Танҳо дар варақаҳои махфӣ + + Танҳо дар варақаҳои фармоишӣ + + Криптомайнерҳо + + Хонандаи изи ангушт + Манъ карда мешавад + + Иҷозат дода мешавад + + Криптомайнерҳо + + Хонандаи изи ангушт + + Муҳтавои пайгирикунанда + + Ҳуқуқҳои шумо + + Китобхонаҳо бо манбаи кушоде, ки мо истифода мебарем + + Дар %s чӣ нав аст + + %s | Китобхонаҳои OSS + + + Дастгирӣ + + Садамот + + Огоҳиномаи махфият + + Ҳуқуқҳои худро донед + + Маълумот дар бораи иҷозатнома + + Китобхонаҳое, ки мо истифода мебарем + + + 1 варақа + + %d варақа + + + + Нусха бардоштан + + Нусха бардоштан ва гузаштан + + Гузоштан + + URL ба ҳофизаи муваққатӣ нусха бардошта шуд + + + Илова кардан ба экрани асосӣ + + Бекор кардан + + Илова кардан + + Ба сомона идома диҳед + + Номи миёнбур + + + Воридшавиҳо ва ниҳонвожаҳо + + Нигоҳ доштани воридшавиҳо ва ниҳонвожаҳо + + Ҳеҷ гоҳ нигоҳ дошта нашавад + + Пуркунии худкор + + Воридшавиҳои ҳамоҳангшуда + + Фаъол + + Ғайрифаъол + + Аз нав пайваст кардан + + Барои ҳамоҳангсозӣ ворид шавед + + Воридшавиҳои нигоҳдошташуда + + Маълумоти бештар дар бораи ҳамоҳангсозӣ + + Истисноҳо + + Нест кардани ҳамаи истисноҳо + + Ҷустуҷӯи воридшавиҳо + + Аз рӯи алифбо + + Сомона + + Номи корбар + + Ниҳонвожа + + PIN-и худро такроран ворид намоед + + Маълумоти бештар + + Шумо мехоҳед, ки %s воридшавии шуморо нигоҳ дорад? + + Нигоҳ доштан + + Нигоҳ дошта нашавад + + Ниҳонвожа ба ҳофизаи муваққатӣ нусха бардошта шуд + + Номи корбар ба ҳофизаи муваққатӣ нусха бардошта шуд + + Сомона ба ҳофизаи муваққатӣ нусха бардошта шуд + + Нусха бардоштани ниҳонвожа + + Пок кардани ниҳонвожа + + Нусха бардоштани номи корбар + + Пок кардани номи корбар + + Нусха бардоштани сомона + + Кушодани сомона дар браузер + + Нишон додани ниҳонвожа + + Пинҳон кардани ниҳонвожа + + Дертар + + Ҳозир насб кунед + + Қулфи дастгоҳи худро кушоед + + Тағйири андоза дар ҳамаи сомонаҳо + + Ном (А-Я) + + + Илова кардани низоми ҷустуҷӯӣ + + Таҳрир кардани низоми ҷустуҷӯӣ + + Илова кардан + + Нигоҳ доштан + + Таҳрир кардан + + Нест кардан + + + Дигар + + Ном + + Маълумоти бештар + + Пайванди «Маълумоти бештар» + + + %s эҷод карда шуд + + %s нигоҳ дошта шуд + + %s нест карда шуд + + + Оғоз кардани %s + + Интиқол анҷом ёфт + + Ниҳонвожаҳо + + + Барои иҷозат додан: + + 1. Ба Танзимоти Android гузаред + + Иҷозатҳо-ро зер кунед]]> + + %1$s ба ФАЪОЛ иваз намоед]]> + + + Пайвасти боэътимод + + Пайвасти беэътимод + + Мақолаҳои беҳтарин + + Илова кардан ба сомонаҳои беҳтарин + + Нест кардан + + Таҳрир кардан + + Шумо мутмаин ҳастед, ки мехоҳед ин воридшавиро нест намоед? + + Нест кардан + + Имконоти воридшавӣ + + Нигоҳ доштани тағйирот барои воридшавӣ + + Рад кардани тағйирот + + Таҳрир кардан + + + Ниҳонвожа лозим аст + + Ҷустуҷӯи овозӣ + + Акнун ҳарф занед + + + Воридшавӣ бо ин номи корбар аллакай вуҷуд дорад + + + + Дастгоҳи дигареро пайваст кунед. + + + Лутфан, санҷиши ҳаққониятро аз нав такрор кунед + + Лутфан ҳамоҳангсозии варақаҳоро фаъол кунед. + + Ягон варақаи кушодашуда нест + + + Хуб, фаҳмидам + + + Тоза кардан + + diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 91080d92e..2f0abc666 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -1158,6 +1158,8 @@ Đăng nhập bằng máy ảnh của bạn Sử dụng email thay thế + + Tạo một cái để đồng bộ hóa Firefox giữa các thiết bị.]]> Firefox sẽ ngừng đồng bộ hóa với tài khoản của bạn, nhưng sẽ không xóa mọi dữ liệu duyệt web của bạn trên thiết bị này. @@ -1265,6 +1267,11 @@ The first parameter is the app name --> %s | Thư viện OSS + + Trình theo dõi chuyển hướng + + Xóa cookie được đặt bởi chuyển hướng đến các trang web theo dõi đã biết. + Hỗ trợ diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ee3a85370..482671157 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1203,6 +1203,8 @@ 使用相机登录 改用电子邮件 + + 立即创建以在设备间同步 Firefox。]]> Firefox 将停止与您的账号的同步,但不会删除此设备上的任何浏览数据。 @@ -1311,6 +1313,11 @@ The first parameter is the app name --> %s | 开源软件库 + + 重定向跟踪器 + + 清除通过重定向设置到已知跟踪网站的 Cookie。 + 用户支持 @@ -1594,7 +1601,7 @@ 请启用标签页同步。 - 您其他设备上的 Firefox 中没有打开任何标签页。 + 您其他设备上的 Firefox 没有打开任何标签页。 查看您其他设备上的标签页列表。 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 069c4004a..d62f61586 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1192,6 +1192,8 @@ 使用攝影機登入 改用電子郵件 + + 註冊一組就能在不同裝置間同步 Firefox。]]> Firefox 將會停止與您帳號間的同步,但不會刪除此裝置上的任何瀏覽資料。 @@ -1299,6 +1301,11 @@ The first parameter is the app name --> %s | 開放原始碼程式庫 + + 重新導向追蹤器 + + 清除已知網站在重新導向時所設定的 Cookie。 + 技術支援 From 04e59e0ac5207ce693210ed8917d938c2e745069 Mon Sep 17 00:00:00 2001 From: mcarare Date: Fri, 25 Sep 2020 12:49:28 +0300 Subject: [PATCH 27/36] For #15130: Remove "what's new card" from onboarding screen. --- .../org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt index 5aaaa3854..3258fb31d 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt @@ -94,7 +94,6 @@ private fun onboardingAdapterItems(onboardingState: OnboardingState): List Date: Fri, 25 Sep 2020 14:12:03 +0300 Subject: [PATCH 28/36] For #15130: Remove "what's new card" check in UI test. --- .../androidTest/java/org/mozilla/fenix/ui/HomeScreenTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/HomeScreenTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/HomeScreenTest.kt index 28ccdad03..f83061359 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/HomeScreenTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/HomeScreenTest.kt @@ -72,9 +72,9 @@ class HomeScreenTest { verifyGetToKnowHeader() // See What's new - scrollToElementByText("See what’s new") - verifyWhatsNewHeader() - verifyWhatsNewLink() + // scrollToElementByText("See what’s new") + // verifyWhatsNewHeader() + // verifyWhatsNewLink() // Automatic privacy scrollToElementByText("Automatic privacy") From 81aaacda613d20b900411d1ea75f32a539c9c206 Mon Sep 17 00:00:00 2001 From: Christian Sadilek Date: Thu, 24 Sep 2020 17:48:47 -0400 Subject: [PATCH 29/36] Configure M5 AMO collection for Nightly/Debug --- app/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index 2174f507f..88c9a8b72 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -59,12 +59,14 @@ android { shrinkResources false minifyEnabled false applicationIdSuffix ".fenix.debug" + buildConfigField "String", "AMO_COLLECTION", "\"7dfae8669acc4312a65e8ba5553036\"" resValue "bool", "IS_DEBUG", "true" pseudoLocalesEnabled true } nightly releaseTemplate >> { applicationIdSuffix ".fenix" buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true" + buildConfigField "String", "AMO_COLLECTION", "\"7dfae8669acc4312a65e8ba5553036\"" def deepLinkSchemeValue = "fenix-nightly" buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\"" manifestPlaceholders = ["deepLinkScheme": deepLinkSchemeValue] From 47c94d887a654d845e5ebee8e5f98b1ce30b2e8d Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Fri, 25 Sep 2020 14:08:18 +0000 Subject: [PATCH 30/36] Update Android Components version to 61.0.20200925130131. --- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 3eaedf542..9ba9677f4 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 = "61.0.20200923190103" + const val VERSION = "61.0.20200925130131" } From b15850d9a331514dc495391c0826c36effb6a9f6 Mon Sep 17 00:00:00 2001 From: Aaron Train Date: Fri, 25 Sep 2020 13:29:14 -0400 Subject: [PATCH 31/36] No issue: Update Flank to v20.09.3 (#15399) --- taskcluster/docker/ui-tests/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taskcluster/docker/ui-tests/Dockerfile b/taskcluster/docker/ui-tests/Dockerfile index 445ae433b..4a14d2ebf 100644 --- a/taskcluster/docker/ui-tests/Dockerfile +++ b/taskcluster/docker/ui-tests/Dockerfile @@ -11,7 +11,7 @@ USER worker:worker ENV GOOGLE_SDK_DOWNLOAD ./gcloud.tar.gz ENV GOOGLE_SDK_VERSION 233 -ENV FLANK_VERSION v20.08.0 +ENV FLANK_VERSION v20.09.3 ENV TEST_TOOLS /builds/worker/test-tools ENV PATH ${PATH}:${TEST_TOOLS}:${TEST_TOOLS}/google-cloud-sdk/bin From 24985d54fae99cf7d44f0013514357de07008365 Mon Sep 17 00:00:00 2001 From: Grisha Kruglov Date: Fri, 25 Sep 2020 13:41:23 -0700 Subject: [PATCH 32/36] Closes #15436: Auto-close tabs during startup on the main thread --- app/src/main/java/org/mozilla/fenix/HomeActivity.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 7523c5ab4..b1109f73f 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -32,6 +32,7 @@ import kotlinx.android.synthetic.main.activity_home.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -283,10 +284,12 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { settings().wasDefaultBrowserOnLastResume = settings().isDefaultBrowser() if (!settings().manuallyCloseTabs) { - components.core.store.state.tabs.filter { + val toClose = components.core.store.state.tabs.filter { (System.currentTimeMillis() - it.lastAccess) > settings().getTabTimeout() - }.forEach { - components.useCases.tabsUseCases.removeTab(it.id) + } + // Removal needs to happen on the main thread. + lifecycleScope.launch(Main) { + toClose.forEach { components.useCases.tabsUseCases.removeTab(it.id) } } } } From d98eba1d6431e0d1fc0e484c1f8a8c9b1b64869e Mon Sep 17 00:00:00 2001 From: Grisha Kruglov Date: Fri, 25 Sep 2020 13:08:24 -0700 Subject: [PATCH 33/36] Closes #15432: Invoke UI updates on main thread in response to account events --- .../settings/logins/SyncLoginsPreferenceView.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/SyncLoginsPreferenceView.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/SyncLoginsPreferenceView.kt index 49e313b57..c8106882b 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/logins/SyncLoginsPreferenceView.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/logins/SyncLoginsPreferenceView.kt @@ -7,6 +7,8 @@ package org.mozilla.fenix.settings.logins import androidx.lifecycle.LifecycleOwner import androidx.navigation.NavController import androidx.preference.Preference +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch import mozilla.components.concept.sync.AccountObserver import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.OAuthAccount @@ -28,10 +30,15 @@ class SyncLoginsPreferenceView( init { accountManager.register(object : AccountObserver { - override fun onAuthenticated(account: OAuthAccount, authType: AuthType) = - updateSyncPreferenceStatus() - override fun onLoggedOut() = updateSyncPreferenceNeedsLogin() - override fun onAuthenticationProblems() = updateSyncPreferenceNeedsReauth() + override fun onAuthenticated(account: OAuthAccount, authType: AuthType) { + MainScope().launch { updateSyncPreferenceStatus() } + } + override fun onLoggedOut() { + MainScope().launch { updateSyncPreferenceNeedsLogin() } + } + override fun onAuthenticationProblems() { + MainScope().launch { updateSyncPreferenceNeedsReauth() } + } }, owner = lifecycleOwner) val accountExists = accountManager.authenticatedAccount() != null From 2999f64d0aa27b00b89ab41423dc467dc7885b3b Mon Sep 17 00:00:00 2001 From: Grisha Kruglov Date: Fri, 25 Sep 2020 13:19:50 -0700 Subject: [PATCH 34/36] Closes #15434: Don't try to display a 'signed in' snackbar in a 'headless' mode --- .../org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt index 262d19b1d..fae824378 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt @@ -135,6 +135,11 @@ class TurnOnSyncFragment : Fragment(), AccountObserver { } override fun onAuthenticated(account: OAuthAccount, authType: AuthType) { + // If we're in a `shouldLoginJustWithEmail = true` state, we won't have a view available, + // and can't display a snackbar. + if (view == null) { + return + } val snackbarText = requireContext().getString(R.string.sync_syncing_in_progress) val snackbarLength = FenixSnackbar.LENGTH_SHORT From 7d5c199e5188f1d8d553a4e732fbf9b649fd38d3 Mon Sep 17 00:00:00 2001 From: ekager Date: Thu, 24 Sep 2020 15:39:24 -0700 Subject: [PATCH 35/36] For #12383 #15407 #12860 - Switch to using shared view model for session to delete on home --- .../fenix/browser/BaseBrowserFragment.kt | 12 ++++- .../toolbar/BrowserToolbarController.kt | 9 ++-- .../org/mozilla/fenix/home/HomeFragment.kt | 10 ++-- .../mozilla/fenix/home/HomeScreenViewModel.kt | 5 ++ .../fenix/tabtray/TabTrayDialogFragment.kt | 21 ++++++-- app/src/main/res/navigation/nav_graph.xml | 5 -- .../DefaultBrowserToolbarControllerTest.kt | 49 +++++++++++++------ 7 files changed, 75 insertions(+), 36 deletions(-) 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 7b224d6c7..72c2cd9d5 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -19,6 +19,7 @@ import androidx.core.net.toUri import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager @@ -109,6 +110,7 @@ import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.home.HomeScreenViewModel import org.mozilla.fenix.home.SharedViewModel import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.utils.allowUndo @@ -205,6 +207,10 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session requireContext().accessibilityManager.addAccessibilityStateChangeListener(this) } + private val homeViewModel: HomeScreenViewModel by activityViewModels { + ViewModelProvider.NewInstanceFactory() // this is a workaround for #4652 + } + @Suppress("ComplexMethod", "LongMethod") @CallSuper protected open fun initializeUI(view: View): Session? { @@ -244,7 +250,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session readerModeController = readerMenuController, sessionManager = requireComponents.core.sessionManager, engineView = engineView, - browserAnimator = browserAnimator, + homeViewModel = homeViewModel, customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) }, onTabCounterClicked = { thumbnailsFeature.get()?.requestScreenshot() @@ -811,7 +817,9 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session @CallSuper override fun onSessionSelected(session: Session) { - updateThemeForSession(session) + if (!this.isRemoving) { + updateThemeForSession(session) + } if (!browserInitialized) { // Initializing a new coroutineScope to avoid ConcurrentModificationException in ObserverRegistry // This will be removed when ObserverRegistry is deprecated by browser-state. 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 fbc5eddb1..9b932bab1 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 @@ -11,7 +11,6 @@ import mozilla.components.concept.engine.EngineView import mozilla.components.support.ktx.kotlin.isUrl import org.mozilla.fenix.HomeActivity 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 @@ -22,6 +21,7 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.sessionsOfType import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.home.HomeScreenViewModel /** * An interface that handles the view manipulation of the BrowserToolbar, triggered by the Interactor @@ -43,7 +43,7 @@ class DefaultBrowserToolbarController( private val readerModeController: ReaderModeController, private val sessionManager: SessionManager, private val engineView: EngineView, - private val browserAnimator: BrowserAnimator, + private val homeViewModel: HomeScreenViewModel, private val customTabSession: Session?, private val onTabCounterClicked: () -> Unit, private val onCloseTab: (Session) -> Unit @@ -110,10 +110,9 @@ class DefaultBrowserToolbarController( if (sessionManager.sessionsOfType(it.private).count() == 1) { // The tab tray always returns to normal mode so do that here too activity.browsingModeManager.mode = BrowsingMode.Normal + homeViewModel.sessionToDelete = it.id navController.navigate( - BrowserFragmentDirections.actionGlobalHome( - sessionToDelete = it.id - ) + BrowserFragmentDirections.actionGlobalHome() ) } else { onCloseTab.invoke(it) 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 708e5e19b..6df4a4131 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -33,7 +33,6 @@ import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.fragment.app.viewModels import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope @@ -118,8 +117,8 @@ class HomeFragment : Fragment() { private val args by navArgs() private lateinit var bundleArgs: Bundle - private val homeViewModel: HomeScreenViewModel by viewModels { - ViewModelProvider.AndroidViewModelFactory(requireActivity().application) + private val homeViewModel: HomeScreenViewModel by activityViewModels { + ViewModelProvider.NewInstanceFactory() // this is a workaround for #4652 } private val snackbarAnchorView: View? @@ -452,7 +451,7 @@ class HomeFragment : Fragment() { updateTabCounter(it) } - bundleArgs.getString(SESSION_TO_DELETE)?.also { + homeViewModel.sessionToDelete?.also { if (it == ALL_NORMAL_TABS || it == ALL_PRIVATE_TABS) { removeAllTabsAndShowSnackbar(it) } else { @@ -460,6 +459,8 @@ class HomeFragment : Fragment() { } } + homeViewModel.sessionToDelete = null + updateTabCounter(requireComponents.core.store.state) if (bundleArgs.getBoolean(FOCUS_ON_ADDRESS_BAR)) { @@ -989,7 +990,6 @@ class HomeFragment : Fragment() { const val ALL_PRIVATE_TABS = "all_private" private const val FOCUS_ON_ADDRESS_BAR = "focusOnAddressBar" - private const val SESSION_TO_DELETE = "session_to_delete" private const val ANIMATION_DELAY = 100L private const val NON_TAB_ITEM_NUM = 3 diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeScreenViewModel.kt b/app/src/main/java/org/mozilla/fenix/home/HomeScreenViewModel.kt index ae3f3fcc1..3c0b80149 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeScreenViewModel.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeScreenViewModel.kt @@ -8,6 +8,11 @@ import android.os.Parcelable import androidx.lifecycle.ViewModel class HomeScreenViewModel : ViewModel() { + /** + * Used to delete a specific session once the home screen is resumed + */ + var sessionToDelete: String? = null + var layoutManagerState: Parcelable? = null /** diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt index 9d3b72546..f03bd170c 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt @@ -16,6 +16,8 @@ import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatDialogFragment import androidx.core.view.isVisible import androidx.core.view.updatePadding +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs @@ -41,8 +43,8 @@ import mozilla.components.support.base.feature.UserInteractionHandler import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.support.ktx.android.view.showKeyboard import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.NavGraphDirections import org.mozilla.fenix.R -import org.mozilla.fenix.browser.BrowserFragmentDirections import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.components.TabCollectionStorage @@ -53,6 +55,7 @@ import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.normalSessionSize import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.home.HomeScreenViewModel import org.mozilla.fenix.tabtray.TabTrayDialogFragmentState.Mode import org.mozilla.fenix.utils.allowUndo @@ -263,7 +266,8 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler val session = sessionManager.findSessionById(sessionId) ?: return // Check if this is the last tab of this session type - val isLastOpenTab = store.state.tabs.filter { it.content.private == tab.content.private }.size == 1 + val isLastOpenTab = + store.state.tabs.filter { it.content.private == tab.content.private }.size == 1 if (isLastOpenTab) { dismissTabTrayAndNavigateHome(sessionId) return @@ -282,7 +286,11 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler snackbarMessage, getString(R.string.snackbar_deleted_undo), { - sessionManager.add(session, isSelected, engineSessionState = tab.engineState.engineSessionState) + sessionManager.add( + session, + isSelected, + engineSessionState = tab.engineState.engineSessionState + ) _tabTrayView?.scrollToTab(session.id) }, operation = { }, @@ -291,8 +299,13 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler ) } + private val homeViewModel: HomeScreenViewModel by activityViewModels { + ViewModelProvider.NewInstanceFactory() // this is a workaround for #4652 + } + private fun dismissTabTrayAndNavigateHome(sessionId: String) { - val directions = BrowserFragmentDirections.actionGlobalHome(sessionToDelete = sessionId) + homeViewModel.sessionToDelete = sessionId + val directions = NavGraphDirections.actionGlobalHome() findNavController().navigate(directions) dismissAllowingStateLoss() } diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 5bf3c9190..67edefc7e 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -132,11 +132,6 @@ android:name="focusOnAddressBar" android:defaultValue="false" app:argType="boolean" /> - Unit - @RelaxedMockK private lateinit var onCloseTab: (Session) -> Unit - @RelaxedMockK private lateinit var sessionManager: SessionManager - @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 topSitesUseCase: TopSitesUseCases - @RelaxedMockK private lateinit var readerModeController: ReaderModeController + @RelaxedMockK + private lateinit var activity: HomeActivity + @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 + @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 topSitesUseCase: TopSitesUseCases + @RelaxedMockK + private lateinit var readerModeController: ReaderModeController + @RelaxedMockK + private lateinit var homeViewModel: HomeScreenViewModel @Before fun setUp() { @@ -193,7 +209,10 @@ class DefaultBrowserToolbarControllerTest { val controller = createController() controller.handleTabCounterItemInteraction(item) - verify { navController.navigate(BrowserFragmentDirections.actionGlobalHome(sessionToDelete = "1")) } + verify { + homeViewModel.sessionToDelete = "1" + navController.navigate(BrowserFragmentDirections.actionGlobalHome()) + } assertEquals(BrowsingMode.Normal, browsingModeManager.mode) } @@ -262,7 +281,7 @@ class DefaultBrowserToolbarControllerTest { navController = navController, metrics = metrics, engineView = engineView, - browserAnimator = browserAnimator, + homeViewModel = homeViewModel, customTabSession = customTabSession, readerModeController = readerModeController, sessionManager = sessionManager, From 0d0a28d551d5083a6d9beeb2ec86746adf04eb10 Mon Sep 17 00:00:00 2001 From: ekager Date: Fri, 25 Sep 2020 17:17:54 -0700 Subject: [PATCH 36/36] No issue: Update AC to 61.0.20200925190057 --- .../java/org/mozilla/fenix/components/TabCollectionStorage.kt | 4 ++-- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt b/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt index 214012cba..10523589d 100644 --- a/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt +++ b/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt @@ -75,8 +75,8 @@ class TabCollectionStorage( return collectionStorage.getTabCollectionsCount() } - fun getCollections(limit: Int = 20): LiveData> { - return collectionStorage.getCollections(limit).asLiveData() + fun getCollections(): LiveData> { + return collectionStorage.getCollections().asLiveData() } fun getCollectionsPaged(): DataSource.Factory { diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 9ba9677f4..0d7401dd0 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 = "61.0.20200925130131" + const val VERSION = "61.0.20200925190057" }