From 169071ac61d96873b303cd6df01a2ce62d57ac3d Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Tue, 21 Jul 2020 13:33:46 -0700 Subject: [PATCH 01/15] No issue: extract startMetricsIfEnabled function. This refactor, done entirely by IDE, is a no-op clean-up of this file. --- .../org/mozilla/fenix/FenixApplication.kt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index b91e40fca..3088d5428 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -139,14 +139,7 @@ open class FenixApplication : LocaleAwareApplication() { prefetchForHomeFragment() setupLeakCanary() - if (settings().isTelemetryEnabled) { - components.analytics.metrics.start(MetricServiceType.Data) - } - - if (settings().isMarketingTelemetryEnabled) { - components.analytics.metrics.start(MetricServiceType.Marketing) - } - + startMetricsIfEnabled() setupPush() visibilityLifecycleCallback = VisibilityLifecycleCallback(getSystemService()) @@ -203,6 +196,16 @@ open class FenixApplication : LocaleAwareApplication() { } } + private fun startMetricsIfEnabled() { + if (settings().isTelemetryEnabled) { + components.analytics.metrics.start(MetricServiceType.Data) + } + + if (settings().isMarketingTelemetryEnabled) { + components.analytics.metrics.start(MetricServiceType.Marketing) + } + } + // See https://github.com/mozilla-mobile/fenix/issues/7227 for context. // To re-enable this, we need to do so in a way that won't interfere with any startup operations // which acquire reserved+ sqlite lock. Currently, Fennec migrations need to write to storage From 633bc4f2f28f7cc33b37eb696998f1b7f66b3478 Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Tue, 21 Jul 2020 13:36:06 -0700 Subject: [PATCH 02/15] No issue: extract initVisualCompletenessQueueAndQueueTasks. This refactor, done entirely by IDE, is a no-op cleanup. --- app/src/main/java/org/mozilla/fenix/FenixApplication.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index 3088d5428..ac8800818 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -151,10 +151,12 @@ open class FenixApplication : LocaleAwareApplication() { // runStorageMaintenance() // } + initVisualCompletenessQueueAndQueueTasks() + } + + private fun initVisualCompletenessQueueAndQueueTasks() { val taskQueue = components.performance.visualCompletenessQueue - registerActivityLifecycleCallbacks( - PerformanceActivityLifecycleCallbacks(taskQueue) - ) + registerActivityLifecycleCallbacks(PerformanceActivityLifecycleCallbacks(taskQueue)) // Enable the service-experiments component to be initialized after visual completeness // for performance wins. From 6e06c7fda131d3f7ccb183fa2004eeb2049562b0 Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Tue, 21 Jul 2020 13:41:16 -0700 Subject: [PATCH 03/15] No issue: clean up initVisualCompletenessQueue... method. A no-op clean up. --- .../org/mozilla/fenix/FenixApplication.kt | 74 +++++++++++-------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index ac8800818..ef0379ce3 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -156,46 +156,58 @@ open class FenixApplication : LocaleAwareApplication() { private fun initVisualCompletenessQueueAndQueueTasks() { val taskQueue = components.performance.visualCompletenessQueue - registerActivityLifecycleCallbacks(PerformanceActivityLifecycleCallbacks(taskQueue)) - // Enable the service-experiments component to be initialized after visual completeness - // for performance wins. - if (settings().isExperimentationEnabled) { - taskQueue.runIfReadyOrQueue { - Experiments.initialize( - applicationContext = applicationContext, - onExperimentsUpdated = { - ExperimentsManager.initSearchWidgetExperiment(this) - }, - configuration = mozilla.components.service.experiments.Configuration( - httpClient = components.core.client, - kintoEndpoint = KINTO_ENDPOINT_PROD + fun initQueue() { + registerActivityLifecycleCallbacks(PerformanceActivityLifecycleCallbacks(taskQueue)) + } + + fun queueInitExperiments() { + if (settings().isExperimentationEnabled) { + taskQueue.runIfReadyOrQueue { + Experiments.initialize( + applicationContext = applicationContext, + onExperimentsUpdated = { + ExperimentsManager.initSearchWidgetExperiment(this) + }, + configuration = mozilla.components.service.experiments.Configuration( + httpClient = components.core.client, + kintoEndpoint = KINTO_ENDPOINT_PROD + ) ) - ) - ExperimentsManager.initSearchWidgetExperiment(this) + ExperimentsManager.initSearchWidgetExperiment(this) + } + } else { + // We should make a better way to opt out for when we have more experiments + // See https://github.com/mozilla-mobile/fenix/issues/6278 + ExperimentsManager.optOutSearchWidgetExperiment(this) } - } else { - // We should make a better way to opt out for when we have more experiments - // See https://github.com/mozilla-mobile/fenix/issues/6278 - ExperimentsManager.optOutSearchWidgetExperiment(this) } - components.performance.visualCompletenessQueue.runIfReadyOrQueue { - GlobalScope.launch(Dispatchers.IO) { - logger.info("Running post-visual completeness tasks...") - logElapsedTime(logger, "Storage initialization") { - components.core.historyStorage.warmUp() - components.core.bookmarksStorage.warmUp() - components.core.passwordsStorage.warmUp() + fun queueInitStorageAndServices() { + components.performance.visualCompletenessQueue.runIfReadyOrQueue { + GlobalScope.launch(Dispatchers.IO) { + logger.info("Running post-visual completeness tasks...") + logElapsedTime(logger, "Storage initialization") { + components.core.historyStorage.warmUp() + components.core.bookmarksStorage.warmUp() + components.core.passwordsStorage.warmUp() + } } - } - // Account manager initialization needs to happen on the main thread. - GlobalScope.launch(Dispatchers.Main) { - logElapsedTime(logger, "Kicking-off account manager") { - components.backgroundServices.accountManager + // Account manager initialization needs to happen on the main thread. + GlobalScope.launch(Dispatchers.Main) { + logElapsedTime(logger, "Kicking-off account manager") { + components.backgroundServices.accountManager + } } } } + + initQueue() + + // We init these items in the visual completeness queue to avoid them initing in the critical + // startup path, before the UI finishes drawing (i.e. visual completeness). + queueInitExperiments() + queueInitStorageAndServices() } private fun startMetricsIfEnabled() { From 2ae88aec816e3d6262954f1e8253a11adf2eb0df Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Tue, 21 Jul 2020 13:43:00 -0700 Subject: [PATCH 04/15] No issue: add missing newline. --- app/src/main/java/org/mozilla/fenix/FenixApplication.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index ef0379ce3..ec45fd058 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -248,6 +248,7 @@ open class FenixApplication : LocaleAwareApplication() { components.core.topSiteStorage.prefetch() } } + private fun setupPush() { // Sets the PushFeature as the singleton instance for push messages to go to. // We need the push feature setup here to deliver messages in the case where the service From 638e7c6e47eb4510d77c011254292899061db4a5 Mon Sep 17 00:00:00 2001 From: Johan Lorenzo Date: Wed, 22 Jul 2020 14:56:27 +0200 Subject: [PATCH 05/15] Bug 1631839 - part 3: Remove "project.mobile" routes (#12821) --- .taskcluster.yml | 7 ------- taskcluster/fenix_taskgraph/routes.py | 5 ----- 2 files changed, 12 deletions(-) diff --git a/.taskcluster.yml b/.taskcluster.yml index d349fa519..b175ab5da 100644 --- a/.taskcluster.yml +++ b/.taskcluster.yml @@ -174,19 +174,12 @@ tasks: - index.mobile.v2.${project}.branch.${short_head_branch}.latest.taskgraph.decision - index.mobile.v2.${project}.branch.${short_head_branch}.revision.${head_sha}.taskgraph.decision - index.mobile.v2.${project}.revision.${head_sha}.taskgraph.decision - # TODO Bug 1631839: Remove the following routes once all consumers have migrated - - index.project.mobile.${project}.v2.branch.${short_head_branch}.latest.taskgraph.decision - - index.project.mobile.${project}.v2.branch.${short_head_branch}.revision.${head_sha}.taskgraph.decision - $if: 'tasks_for == "cron"' then: # cron context provides ${head_branch} as a short one - index.mobile.v2.${project}.branch.${head_branch}.latest.taskgraph.decision-${cron.job_name} - index.mobile.v2.${project}.branch.${head_branch}.revision.${head_sha}.taskgraph.decision-${cron.job_name} - index.mobile.v2.${project}.branch.${head_branch}.revision.${head_sha}.taskgraph.cron.${ownTaskId} - # TODO Bug 1631839: Remove the following routes once all consumers have migrated - - index.project.mobile.${project}.v2.branch.${head_branch}.latest.taskgraph.decision-${cron.job_name} - - index.project.mobile.${project}.v2.branch.${head_branch}.revision.${head_sha}.taskgraph.decision-${cron.job_name} - - index.project.mobile.${project}.v2.branch.${head_branch}.revision.${head_sha}.taskgraph.cron.${ownTaskId} scopes: $if: 'tasks_for == "github-push"' then: diff --git a/taskcluster/fenix_taskgraph/routes.py b/taskcluster/fenix_taskgraph/routes.py index 6f7d03beb..32892295e 100644 --- a/taskcluster/fenix_taskgraph/routes.py +++ b/taskcluster/fenix_taskgraph/routes.py @@ -16,11 +16,6 @@ SIGNING_ROUTE_TEMPLATES = [ "index.{trust-domain}.v2.{project}.{variant}.{build_date}.revision.{head_rev}.{abi}", "index.{trust-domain}.v2.{project}.{variant}.{build_date}.latest.{abi}", "index.{trust-domain}.v2.{project}.{variant}.revision.{head_rev}.{abi}", - - # TODO Bug 1631839: Remove the following scopes once all consumers have migrated - "index.project.{trust-domain}.{project}.v2.{variant}.{build_date}.revision.{head_rev}", - "index.project.{trust-domain}.{project}.v2.{variant}.{build_date}.latest", - "index.project.{trust-domain}.{project}.v2.{variant}.latest", ] From d5fbc17ccf6efd8816f3e84008a6cdb7de25f0e8 Mon Sep 17 00:00:00 2001 From: Jonathan Almeida Date: Tue, 21 Jul 2020 23:09:49 -0400 Subject: [PATCH 06/15] Update to Android Components 52.0.20200722023149 Fixes breaking APIs in SyncedTabsFeature and BookmarksStorageSuggestionProvider --- .../org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt | 8 ++++---- .../java/org/mozilla/fenix/sync/SyncedTabsFragment.kt | 1 + buildSrc/src/main/java/AndroidComponents.kt | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) 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 3cfc8f25b..1172d0192 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 @@ -119,10 +119,10 @@ class AwesomeBarView( bookmarksStorageSuggestionProvider = BookmarksStorageSuggestionProvider( - components.core.bookmarksStorage, - loadUrlUseCase, - components.core.icons, - engineForSpeculativeConnects + bookmarksStorage = components.core.bookmarksStorage, + loadUrlUseCase = loadUrlUseCase, + icons = components.core.icons, + engine = engineForSpeculativeConnects ) val searchBitmap = getDrawable(context, R.drawable.ic_search)!!.apply { diff --git a/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsFragment.kt b/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsFragment.kt index 8f27a2cb9..4059d5159 100644 --- a/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsFragment.kt @@ -37,6 +37,7 @@ class SyncedTabsFragment : LibraryPageFragment() { syncedTabsFeature.set( feature = SyncedTabsFeature( + context = requireContext(), storage = backgroundServices.syncedTabsStorage, accountManager = backgroundServices.accountManager, view = synced_tabs_layout, diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 71516a6cd..448860de0 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 = "51.0.20200721130108" + const val VERSION = "52.0.20200722023149" } From d9357f1e32ba66980ee78afc7169478197537f8d Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Tue, 21 Jul 2020 15:12:37 -0700 Subject: [PATCH 07/15] For #12801 - Extra debug menu trigger, disable if already triggered --- .../fenix/settings/about/AboutFragment.kt | 46 +------ .../settings/about/SecretDebugMenuTrigger.kt | 71 +++++++++++ .../about/SecretDebugMenuTriggerTest.kt | 114 ++++++++++++++++++ 3 files changed, 190 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/settings/about/SecretDebugMenuTrigger.kt create mode 100644 app/src/test/java/org/mozilla/fenix/settings/about/SecretDebugMenuTriggerTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/settings/about/AboutFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/about/AboutFragment.kt index 363bf901c..7bda04ec7 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/about/AboutFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/about/AboutFragment.kt @@ -10,13 +10,11 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.Toast import androidx.core.content.pm.PackageInfoCompat import androidx.fragment.app.Fragment import androidx.recyclerview.widget.DividerItemDecoration import com.google.android.gms.oss.licenses.OssLicensesMenuActivity import kotlinx.android.synthetic.main.fragment_about.* -import kotlinx.coroutines.ExperimentalCoroutinesApi import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.BuildConfig import org.mozilla.fenix.HomeActivity @@ -39,10 +37,9 @@ import org.mozilla.geckoview.BuildConfig as GeckoViewBuildConfig * Displays the logo and information about the app, including library versions. */ class AboutFragment : Fragment(), AboutPageListener { + private lateinit var appName: String private val aboutPageAdapter: AboutPageAdapter = AboutPageAdapter(this) - private var secretDebugMenuClicks = 0 - private var lastDebugMenuToast: Toast? = null override fun onCreateView( inflater: LayoutInflater, @@ -56,15 +53,7 @@ class AboutFragment : Fragment(), AboutPageListener { return rootView } - override fun onResume() { - super.onResume() - secretDebugMenuClicks = 0 - } - - @ExperimentalCoroutinesApi override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - about_list.run { adapter = aboutPageAdapter addItemDecoration( @@ -75,33 +64,10 @@ class AboutFragment : Fragment(), AboutPageListener { ) } - // 5 taps on the logo activate the "secret" debug menu. - wordmark.setOnClickListener { - // Because the user will mostly likely tap the logo in rapid succession, - // we ensure only 1 toast is shown at any given time. - lastDebugMenuToast?.let { toast -> toast.cancel() } - secretDebugMenuClicks += 1 - when (secretDebugMenuClicks) { - in 2 until SECRET_DEBUG_MENU_CLICKS -> { - val clicksLeft = SECRET_DEBUG_MENU_CLICKS - secretDebugMenuClicks - val toast = Toast.makeText( - context, - getString(R.string.about_debug_menu_toast_progress, clicksLeft), - Toast.LENGTH_SHORT - ) - toast.show() - lastDebugMenuToast = toast - } - SECRET_DEBUG_MENU_CLICKS -> { - Toast.makeText( - context, - getString(R.string.about_debug_menu_toast_done), - Toast.LENGTH_LONG - ).show() - requireContext().settings().showSecretDebugMenuThisSession = true - } - } - } + lifecycle.addObserver(SecretDebugMenuTrigger( + logoView = wordmark, + settings = view.context.settings() + )) populateAboutHeader() aboutPageAdapter.submitList(populateAboutList()) @@ -233,7 +199,5 @@ class AboutFragment : Fragment(), AboutPageListener { companion object { private const val ABOUT_LICENSE_URL = "about:license" - // Number of clicks on the app logo to enable the "secret" debug menu. - private const val SECRET_DEBUG_MENU_CLICKS = 5 } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/about/SecretDebugMenuTrigger.kt b/app/src/main/java/org/mozilla/fenix/settings/about/SecretDebugMenuTrigger.kt new file mode 100644 index 000000000..75f42943b --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/about/SecretDebugMenuTrigger.kt @@ -0,0 +1,71 @@ +/* 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.settings.about + +import android.view.View +import android.widget.Toast +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.OnLifecycleEvent +import org.mozilla.fenix.R +import org.mozilla.fenix.utils.Settings + +/** + * Triggers the "secret" debug menu when logoView is tapped 5 times. + */ +class SecretDebugMenuTrigger( + logoView: View, + private val settings: Settings +) : View.OnClickListener, LifecycleObserver { + + private var secretDebugMenuClicks = 0 + private var lastDebugMenuToast: Toast? = null + + init { + if (!settings.showSecretDebugMenuThisSession) { + logoView.setOnClickListener(this) + } + } + + /** + * Reset the [secretDebugMenuClicks] counter. + */ + @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) + fun clearClickCounter() { + secretDebugMenuClicks = 0 + } + + override fun onClick(v: View) { + // Because the user will mostly likely tap the logo in rapid succession, + // we ensure only 1 toast is shown at any given time. + lastDebugMenuToast?.cancel() + secretDebugMenuClicks += 1 + when (secretDebugMenuClicks) { + in 2 until SECRET_DEBUG_MENU_CLICKS -> { + val clicksLeft = SECRET_DEBUG_MENU_CLICKS - secretDebugMenuClicks + val toast = Toast.makeText( + v.context, + v.context.getString(R.string.about_debug_menu_toast_progress, clicksLeft), + Toast.LENGTH_SHORT + ) + toast.show() + lastDebugMenuToast = toast + } + SECRET_DEBUG_MENU_CLICKS -> { + Toast.makeText( + v.context, + R.string.about_debug_menu_toast_done, + Toast.LENGTH_LONG + ).show() + settings.showSecretDebugMenuThisSession = true + } + } + } + + companion object { + // Number of clicks on the app logo to enable the "secret" debug menu. + private const val SECRET_DEBUG_MENU_CLICKS = 5 + } +} diff --git a/app/src/test/java/org/mozilla/fenix/settings/about/SecretDebugMenuTriggerTest.kt b/app/src/test/java/org/mozilla/fenix/settings/about/SecretDebugMenuTriggerTest.kt new file mode 100644 index 000000000..5440ddba8 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/settings/about/SecretDebugMenuTriggerTest.kt @@ -0,0 +1,114 @@ +/* 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.settings.about + +import android.content.Context +import android.view.View +import android.widget.Toast +import io.mockk.CapturingSlot +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.mockkStatic +import io.mockk.slot +import io.mockk.unmockkStatic +import io.mockk.verify +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.mozilla.fenix.R +import org.mozilla.fenix.utils.Settings + +class SecretDebugMenuTriggerTest { + + @MockK private lateinit var logoView: View + @MockK private lateinit var context: Context + @MockK private lateinit var settings: Settings + @MockK(relaxUnitFun = true) private lateinit var toast: Toast + private lateinit var clickListener: CapturingSlot + + @Before + fun setup() { + MockKAnnotations.init(this) + mockkStatic(Toast::class) + clickListener = slot() + + every { logoView.setOnClickListener(capture(clickListener)) } just Runs + every { logoView.context } returns context + every { + context.getString(R.string.about_debug_menu_toast_progress, any()) + } returns "Debug menu: x click(s) left to enable" + every { settings.showSecretDebugMenuThisSession } returns false + every { settings.showSecretDebugMenuThisSession = any() } just Runs + every { Toast.makeText(context, any(), any()) } returns toast + every { Toast.makeText(context, any(), any()) } returns toast + } + + @After + fun teardown() { + unmockkStatic(Toast::class) + } + + @Test + fun `toast is not displayed on first click`() { + SecretDebugMenuTrigger(logoView, settings) + clickListener.captured.onClick(logoView) + + verify(inverse = true) { Toast.makeText(context, any(), any()) } + verify(inverse = true) { toast.show() } + } + + @Test + fun `toast is displayed on second click`() { + SecretDebugMenuTrigger(logoView, settings) + clickListener.captured.onClick(logoView) + clickListener.captured.onClick(logoView) + + verify { context.getString(R.string.about_debug_menu_toast_progress, 3) } + verify { Toast.makeText(context, any(), Toast.LENGTH_SHORT) } + verify { toast.show() } + } + + @Test + fun `clearClickCounter resets counter`() { + val trigger = SecretDebugMenuTrigger(logoView, settings) + + clickListener.captured.onClick(logoView) + trigger.clearClickCounter() + + clickListener.captured.onClick(logoView) + + verify(inverse = true) { Toast.makeText(context, any(), any()) } + verify(inverse = true) { toast.show() } + } + + @Test + fun `toast is displayed on fifth click`() { + SecretDebugMenuTrigger(logoView, settings) + clickListener.captured.onClick(logoView) + clickListener.captured.onClick(logoView) + clickListener.captured.onClick(logoView) + clickListener.captured.onClick(logoView) + clickListener.captured.onClick(logoView) + + verify { Toast.makeText( + context, + R.string.about_debug_menu_toast_done, + Toast.LENGTH_LONG + ) } + verify { toast.show() } + verify { settings.showSecretDebugMenuThisSession = true } + } + + @Test + fun `don't register click listener if menu is already shown`() { + every { settings.showSecretDebugMenuThisSession } returns true + SecretDebugMenuTrigger(logoView, settings) + + verify(inverse = true) { logoView.setOnClickListener(any()) } + } +} From c2d940cf0685483d180610f01b4686f15e76ef72 Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Wed, 22 Jul 2020 09:48:36 -0700 Subject: [PATCH 08/15] Use AC RunWhenReadyQueue (#12800) --- .../java/org/mozilla/fenix/HomeActivity.kt | 2 +- .../fenix/components/BackgroundServices.kt | 2 +- .../fenix/components/PerformanceComponent.kt | 2 +- .../PerformanceActivityLifecycleCallbacks.kt | 2 +- .../mozilla/fenix/utils/RunWhenReadyQueue.kt | 57 ------------------- 5 files changed, 4 insertions(+), 61 deletions(-) delete mode 100644 app/src/main/java/org/mozilla/fenix/utils/RunWhenReadyQueue.kt diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 0efa5e962..9c7f3cfab 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -52,6 +52,7 @@ import mozilla.components.support.ktx.android.content.share import mozilla.components.support.ktx.kotlin.isUrl import mozilla.components.support.ktx.kotlin.toNormalizedUrl import mozilla.components.support.locale.LocaleAwareAppCompatActivity +import mozilla.components.support.utils.RunWhenReadyQueue import mozilla.components.support.utils.SafeIntent import mozilla.components.support.utils.toSafeIntent import mozilla.components.support.webextensions.WebExtensionPopupFeature @@ -97,7 +98,6 @@ import org.mozilla.fenix.theme.DefaultThemeManager import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.trackingprotectionexceptions.TrackingProtectionExceptionsFragmentDirections import org.mozilla.fenix.utils.BrowsersCache -import org.mozilla.fenix.utils.RunWhenReadyQueue /** * The main activity of the application. The application is primarily a single Activity (this one) 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 feb74728c..4ac0145d9 100644 --- a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt +++ b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt @@ -30,6 +30,7 @@ import mozilla.components.service.fxa.manager.SCOPE_SYNC import mozilla.components.service.fxa.manager.SyncEnginesStorage import mozilla.components.service.fxa.sync.GlobalSyncableStoreProvider import mozilla.components.service.sync.logins.SyncableLoginsStorage +import mozilla.components.support.utils.RunWhenReadyQueue import org.mozilla.fenix.Config import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.R @@ -39,7 +40,6 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings import org.mozilla.fenix.sync.SyncedTabsIntegration import org.mozilla.fenix.utils.Mockable -import org.mozilla.fenix.utils.RunWhenReadyQueue import org.mozilla.fenix.utils.Settings /** diff --git a/app/src/main/java/org/mozilla/fenix/components/PerformanceComponent.kt b/app/src/main/java/org/mozilla/fenix/components/PerformanceComponent.kt index 88f03aa40..615d348a8 100644 --- a/app/src/main/java/org/mozilla/fenix/components/PerformanceComponent.kt +++ b/app/src/main/java/org/mozilla/fenix/components/PerformanceComponent.kt @@ -4,7 +4,7 @@ package org.mozilla.fenix.components -import org.mozilla.fenix.utils.RunWhenReadyQueue +import mozilla.components.support.utils.RunWhenReadyQueue /** * Component group for all functionality related to performance. diff --git a/app/src/main/java/org/mozilla/fenix/session/PerformanceActivityLifecycleCallbacks.kt b/app/src/main/java/org/mozilla/fenix/session/PerformanceActivityLifecycleCallbacks.kt index 63a14cf5e..793a222cb 100644 --- a/app/src/main/java/org/mozilla/fenix/session/PerformanceActivityLifecycleCallbacks.kt +++ b/app/src/main/java/org/mozilla/fenix/session/PerformanceActivityLifecycleCallbacks.kt @@ -7,11 +7,11 @@ package org.mozilla.fenix.session import android.app.Activity import android.app.Application import android.os.Bundle +import mozilla.components.support.utils.RunWhenReadyQueue import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.IntentReceiverActivity import org.mozilla.fenix.browser.BrowserPerformanceTestActivity import org.mozilla.fenix.settings.account.AuthIntentReceiverActivity -import org.mozilla.fenix.utils.RunWhenReadyQueue import org.mozilla.fenix.widget.VoiceSearchActivity /** diff --git a/app/src/main/java/org/mozilla/fenix/utils/RunWhenReadyQueue.kt b/app/src/main/java/org/mozilla/fenix/utils/RunWhenReadyQueue.kt deleted file mode 100644 index 9b7167d1c..000000000 --- a/app/src/main/java/org/mozilla/fenix/utils/RunWhenReadyQueue.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.utils - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import java.util.concurrent.CopyOnWriteArrayList -import java.util.concurrent.atomic.AtomicBoolean - -/** - * A queue that acts as a gate, either executing tasks right away if the queue is marked as "ready", - * i.e. gate is open, or queues them to be executed whenever the queue is marked as ready in the - * future, i.e. gate becomes open. - */ -class RunWhenReadyQueue { - private val tasks = CopyOnWriteArrayList<() -> Unit>() - private val isReady = AtomicBoolean(false) - - /** - * Was this queue ever marked as 'ready' via a call to [ready]? - * - * @return Boolean value indicating if this queue is 'ready'. - */ - fun isReady(): Boolean = isReady.get() - - /** - * Runs the [task] if this queue is marked as ready, or queues it for later execution. - * Task will be executed on the main thread. - * - * @param task: The task to run now if queue is ready or queue for later execution. - */ - fun runIfReadyOrQueue(task: () -> Unit) { - if (isReady.get()) { - CoroutineScope(Dispatchers.Main).launch { task.invoke() } - } else { - tasks.add(task) - } - } - - /** - * Mark queue as ready. Pending tasks will execute, and all tasks passed to [runIfReadyOrQueue] - * after this point will be executed immediately. - */ - fun ready() { - // Make sure that calls to `ready` are idempotent. - if (!isReady.compareAndSet(false, true)) { - return - } - - CoroutineScope(Dispatchers.Main).launch { - tasks.forEach { it.invoke() }.also { tasks.clear() } - } - } -} From 2a0a11f740a07dc986d131022f0bb6c18d2173ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakk=C4=B1=20Kaan=20=C3=87al=C4=B1=C5=9Fkan?= Date: Tue, 21 Jul 2020 19:02:26 +0300 Subject: [PATCH 09/15] For #12571: Rename 'Shortcuts' to 'Search engines' Co-Authored-By: Khushraj Rathod --- .../ui/robots/SettingsSubMenuSearchRobot.kt | 8 ++++---- app/src/main/res/layout/fragment_search.xml | 6 +++--- app/src/main/res/values/strings.xml | 20 +++++++++++++------ app/src/main/res/xml/search_preferences.xml | 2 +- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuSearchRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuSearchRobot.kt index 9a9ced7e5..55c1dc3d6 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuSearchRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuSearchRobot.kt @@ -85,10 +85,10 @@ private fun assertShowSearchSuggestions() { private fun assertShowSearchShortcuts() { onView(withId(androidx.preference.R.id.recycler_view)).perform( RecyclerViewActions.scrollTo( - hasDescendant(withText("Show search shortcuts")) + hasDescendant(withText("Show search engines")) ) ) - onView(withText("Show search shortcuts")) + onView(withText("Show search engines")) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) } @@ -146,11 +146,11 @@ private fun toggleShowSearchSuggestions() { private fun toggleShowSearchShortcuts() { onView(withId(androidx.preference.R.id.recycler_view)).perform( RecyclerViewActions.scrollTo( - hasDescendant(withText("Show search shortcuts")) + hasDescendant(withText("Show search engines")) ) ) - onView(withText("Show search shortcuts")) + onView(withText("Show search engines")) .perform(click()) } diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index c2a77fa70..ebdc0bc21 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -147,7 +147,7 @@ android:layout_marginTop="@dimen/search_fragment_shortcuts_label_margin_vertical" android:layout_marginEnd="@dimen/search_fragment_shortcuts_label_margin_horizontal" android:visibility="gone" - android:text="@string/search_shortcuts_search_with_2" + android:text="@string/search_engines_search_with" app:layout_constraintStart_toStartOf="@id/scrollable_area" app:layout_constraintTop_toBottomOf="@id/awesomeBar_barrier" tools:text="This time, search with:" /> @@ -197,8 +197,8 @@ \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 33d219f38..51601631d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -146,13 +146,11 @@ Scan - Shortcuts + Search Engine Search engine settings - - Search with - This time, search with: + This time, search with: Fill link from clipboard @@ -258,8 +256,8 @@ Developer tools Remote debugging via USB - - Show search shortcuts + + Show search engines Show search suggestions @@ -1432,4 +1430,14 @@ To add a new top site, remove one. Long press the site and select remove. OK, Got It + + + + Shortcuts + + Search with + + This time, search with: + + Show search shortcuts diff --git a/app/src/main/res/xml/search_preferences.xml b/app/src/main/res/xml/search_preferences.xml index be7c2604e..5c6d3939b 100644 --- a/app/src/main/res/xml/search_preferences.xml +++ b/app/src/main/res/xml/search_preferences.xml @@ -27,7 +27,7 @@ + android:title="@string/preferences_show_search_engines" /> Date: Tue, 14 Jul 2020 16:20:42 -0700 Subject: [PATCH 10/15] For #1048 - Add ability to view tab history by long-pressing the back or forward button. --- .../java/org/mozilla/fenix/HomeActivity.kt | 52 ++++++++++++ .../fenix/OnBackLongPressedListener.kt | 21 +++++ .../fenix/browser/BaseBrowserFragment.kt | 9 ++- .../toolbar/BrowserToolbarController.kt | 10 ++- .../components/toolbar/DefaultToolbarMenu.kt | 5 +- .../fenix/components/toolbar/ToolbarMenu.kt | 2 +- .../fenix/customtabs/CustomTabToolbarMenu.kt | 5 +- .../fenix/tabhistory/TabHistoryAdapter.kt | 40 ++++++++++ .../fenix/tabhistory/TabHistoryController.kt | 24 ++++++ .../tabhistory/TabHistoryDialogFragment.kt | 56 +++++++++++++ .../fenix/tabhistory/TabHistoryInteractor.kt | 14 ++++ .../fenix/tabhistory/TabHistoryView.kt | 79 +++++++++++++++++++ .../fenix/tabhistory/TabHistoryViewHolder.kt | 34 ++++++++ .../main/res/layout/component_tabhistory.xml | 31 ++++++++ .../layout/fragment_tab_history_dialog.xml | 10 +++ app/src/main/res/navigation/nav_graph.xml | 6 ++ .../DefaultBrowserToolbarControllerTest.kt | 13 ++- .../tabhistory/TabHistoryControllerTest.kt | 38 +++++++++ .../tabhistory/TabHistoryInteractorTest.kt | 24 ++++++ 19 files changed, 464 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/OnBackLongPressedListener.kt create mode 100644 app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryAdapter.kt create mode 100644 app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryController.kt create mode 100644 app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryDialogFragment.kt create mode 100644 app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryInteractor.kt create mode 100644 app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryView.kt create mode 100644 app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryViewHolder.kt create mode 100644 app/src/main/res/layout/component_tabhistory.xml create mode 100644 app/src/main/res/layout/fragment_tab_history_dialog.xml create mode 100644 app/src/test/java/org/mozilla/fenix/tabhistory/TabHistoryControllerTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/tabhistory/TabHistoryInteractorTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 9c7f3cfab..254898460 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -6,11 +6,14 @@ package org.mozilla.fenix import android.content.Context import android.content.Intent +import android.os.Build import android.os.Bundle import android.os.StrictMode import android.text.format.DateUtils import android.util.AttributeSet +import android.view.KeyEvent import android.view.View +import android.view.ViewConfiguration import android.view.WindowManager import androidx.annotation.CallSuper import androidx.annotation.IdRes @@ -30,6 +33,8 @@ import kotlinx.android.synthetic.main.activity_home.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import mozilla.components.browser.search.SearchEngine import mozilla.components.browser.session.SessionManager @@ -139,6 +144,9 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { ) } + // See onKeyDown for why this is necessary + private var backLongPressJob: Job? = null + private lateinit var navigationToolbar: Toolbar final override fun onCreate(savedInstanceState: Bundle?) { @@ -349,6 +357,50 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { super.onBackPressed() } + private fun isAndroidN(): Boolean = + Build.VERSION.SDK_INT == Build.VERSION_CODES.N || Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1 + + private fun handleBackLongPress(): Boolean { + supportFragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.forEach { + if (it is OnBackLongPressedListener && it.onBackLongPressed()) { + return true + } + } + return false + } + + final override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { + // Inspired by https://searchfox.org/mozilla-esr68/source/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java#584-613 + // Android N has broken passing onKeyLongPress events for the back button, so we + // instead implement the long press behavior ourselves + // - For short presses, we cancel the callback in onKeyUp + // - For long presses, the normal keypress is marked as cancelled, hence won't be handled elsewhere + // (but Android still provides the haptic feedback), and the long press action is run + if (isAndroidN() && keyCode == KeyEvent.KEYCODE_BACK) { + backLongPressJob = lifecycleScope.launch { + delay(ViewConfiguration.getLongPressTimeout().toLong()) + handleBackLongPress() + } + } + return super.onKeyDown(keyCode, event) + } + + final override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean { + if (isAndroidN() && keyCode == KeyEvent.KEYCODE_BACK) { + backLongPressJob?.cancel() + } + return super.onKeyUp(keyCode, event) + } + + final override fun onKeyLongPress(keyCode: Int, event: KeyEvent?): Boolean { + // onKeyLongPress is broken in Android N so we don't handle back button long presses here + // for N. The version check ensures we don't handle back button long presses twice. + if (!isAndroidN() && keyCode == KeyEvent.KEYCODE_BACK) { + return handleBackLongPress() + } + return super.onKeyLongPress(keyCode, event) + } + final override fun onUserLeaveHint() { supportFragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.forEach { if (it is UserInteractionHandler && it.onHomePressed()) { diff --git a/app/src/main/java/org/mozilla/fenix/OnBackLongPressedListener.kt b/app/src/main/java/org/mozilla/fenix/OnBackLongPressedListener.kt new file mode 100644 index 000000000..e47a0b71b --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/OnBackLongPressedListener.kt @@ -0,0 +1,21 @@ +/* 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 + +/** + * Interface for features and fragments that want to handle long presses of the system back button + */ +interface OnBackLongPressedListener { + + /** + * Called when the system back button is long pressed. + * + * Note: This cannot be called when gesture navigation is enabled on Android 10+ due to system + * limitations. + * + * @return true if the event was handled + */ + fun onBackLongPressed(): Boolean +} 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 25be5f7b5..55288f861 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -77,6 +77,7 @@ import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.IntentReceiverActivity import org.mozilla.fenix.NavGraphDirections +import org.mozilla.fenix.OnBackLongPressedListener import org.mozilla.fenix.R import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.readermode.DefaultReaderModeController @@ -116,7 +117,8 @@ import java.lang.ref.WeakReference */ @ExperimentalCoroutinesApi @Suppress("TooManyFunctions", "LargeClass") -abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, SessionManager.Observer { +abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, SessionManager.Observer, + OnBackLongPressedListener { private lateinit var browserFragmentStore: BrowserFragmentStore private lateinit var browserAnimator: BrowserAnimator @@ -757,6 +759,11 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session removeSessionIfNeeded() } + override fun onBackLongPressed(): Boolean { + findNavController().navigate(R.id.action_global_tabHistoryDialogFragment) + return true + } + /** * Saves the external app session ID to be restored later in [onViewStateRestored]. */ 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 41795a446..565224752 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 @@ -170,7 +170,13 @@ class DefaultBrowserToolbarController( Do exhaustive when (item) { ToolbarMenu.Item.Back -> sessionUseCases.goBack.invoke(currentSession) - ToolbarMenu.Item.Forward -> sessionUseCases.goForward.invoke(currentSession) + is ToolbarMenu.Item.Forward -> { + if (item.viewHistory) { + navController.navigate(R.id.action_global_tabHistoryDialogFragment) + } else { + sessionUseCases.goForward.invoke(currentSession) + } + } ToolbarMenu.Item.Reload -> sessionUseCases.reload.invoke(currentSession) ToolbarMenu.Item.Stop -> sessionUseCases.stopLoading.invoke(currentSession) ToolbarMenu.Item.Settings -> browserAnimator.captureEngineViewAndDrawStatically { @@ -333,7 +339,7 @@ class DefaultBrowserToolbarController( private fun trackToolbarItemInteraction(item: ToolbarMenu.Item) { val eventItem = when (item) { ToolbarMenu.Item.Back -> Event.BrowserMenuItemTapped.Item.BACK - ToolbarMenu.Item.Forward -> Event.BrowserMenuItemTapped.Item.FORWARD + is ToolbarMenu.Item.Forward -> Event.BrowserMenuItemTapped.Item.FORWARD ToolbarMenu.Item.Reload -> Event.BrowserMenuItemTapped.Item.RELOAD ToolbarMenu.Item.Stop -> Event.BrowserMenuItemTapped.Item.STOP ToolbarMenu.Item.Settings -> Event.BrowserMenuItemTapped.Item.SETTINGS diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt index a27691bac..caf922208 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt @@ -82,9 +82,10 @@ class DefaultToolbarMenu( session?.canGoForward ?: true }, secondaryImageTintResource = ThemeManager.resolveAttribute(R.attr.disabled, context), - disableInSecondaryState = true + disableInSecondaryState = true, + longClickListener = { onItemTapped.invoke(ToolbarMenu.Item.Forward(viewHistory = true)) } ) { - onItemTapped.invoke(ToolbarMenu.Item.Forward) + onItemTapped.invoke(ToolbarMenu.Item.Forward(viewHistory = false)) } val refresh = BrowserMenuItemToolbar.TwoStateButton( diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt index db8dbbe14..aeb33dd15 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt @@ -14,7 +14,7 @@ interface ToolbarMenu { object FindInPage : Item() object Share : Item() object Back : Item() - object Forward : Item() + data class Forward(val viewHistory: Boolean) : Item() object Reload : Item() object Stop : Item() object OpenInFenix : Item() diff --git a/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarMenu.kt index 32a33b8d4..a5f0a9017 100644 --- a/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarMenu.kt @@ -75,9 +75,10 @@ class CustomTabToolbarMenu( R.attr.disabled, context ), - disableInSecondaryState = true + disableInSecondaryState = true, + longClickListener = { onItemTapped.invoke(ToolbarMenu.Item.Forward(viewHistory = true)) } ) { - onItemTapped.invoke(ToolbarMenu.Item.Forward) + onItemTapped.invoke(ToolbarMenu.Item.Forward(viewHistory = false)) } val refresh = BrowserMenuItemToolbar.TwoStateButton( diff --git a/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryAdapter.kt b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryAdapter.kt new file mode 100644 index 000000000..433c620e1 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryAdapter.kt @@ -0,0 +1,40 @@ +/* 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.tabhistory + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.mozilla.fenix.R + +data class TabHistoryItem( + val title: String, + val url: String, + val index: Int, + val isSelected: Boolean +) + +class TabHistoryAdapter( + private val interactor: TabHistoryViewInteractor +) : RecyclerView.Adapter() { + + var historyList: List = emptyList() + set(value) { + field = value + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TabHistoryViewHolder { + val view = + LayoutInflater.from(parent.context).inflate(R.layout.history_list_item, parent, false) + return TabHistoryViewHolder(view, interactor) + } + + override fun onBindViewHolder(holder: TabHistoryViewHolder, position: Int) { + holder.bind(historyList[position]) + } + + override fun getItemCount(): Int = historyList.size +} diff --git a/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryController.kt b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryController.kt new file mode 100644 index 000000000..2d6ecc784 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryController.kt @@ -0,0 +1,24 @@ +/* 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.tabhistory + +import androidx.navigation.NavController +import mozilla.components.feature.session.SessionUseCases +import org.mozilla.fenix.R + +interface TabHistoryController { + fun handleGoToHistoryItem(item: TabHistoryItem) +} + +class DefaultTabHistoryController( + private val navController: NavController, + private val goToHistoryIndexUseCase: SessionUseCases.GoToHistoryIndexUseCase +) : TabHistoryController { + + override fun handleGoToHistoryItem(item: TabHistoryItem) { + navController.popBackStack(R.id.browserFragment, false) + goToHistoryIndexUseCase.invoke(item.index) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryDialogFragment.kt new file mode 100644 index 000000000..5ae534b73 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryDialogFragment.kt @@ -0,0 +1,56 @@ +/* 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.tabhistory + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.navigation.fragment.findNavController +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import kotlinx.android.synthetic.main.fragment_tab_history_dialog.* +import kotlinx.coroutines.ExperimentalCoroutinesApi +import mozilla.components.lib.state.ext.consumeFrom +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.requireComponents + +class TabHistoryDialogFragment : BottomSheetDialogFragment() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(STYLE_NO_TITLE, R.style.BottomSheet) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = inflater.inflate(R.layout.fragment_tab_history_dialog, container, false) + + @ExperimentalCoroutinesApi + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val controller = DefaultTabHistoryController( + navController = findNavController(), + goToHistoryIndexUseCase = requireComponents.useCases.sessionUseCases.goToHistoryIndex + ) + val tabHistoryView = TabHistoryView( + container = tabHistoryLayout, + expandDialog = ::expand, + interactor = TabHistoryInteractor(controller) + ) + + consumeFrom(requireComponents.core.store) { + tabHistoryView.updateState(it) + } + } + + private fun expand() { + (dialog as BottomSheetDialog).behavior.state = BottomSheetBehavior.STATE_EXPANDED + } +} diff --git a/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryInteractor.kt b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryInteractor.kt new file mode 100644 index 000000000..1c659acf0 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryInteractor.kt @@ -0,0 +1,14 @@ +/* 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.tabhistory + +class TabHistoryInteractor( + private val controller: TabHistoryController +) : TabHistoryViewInteractor { + + override fun goToHistoryItem(item: TabHistoryItem) { + controller.handleGoToHistoryItem(item) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryView.kt b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryView.kt new file mode 100644 index 000000000..c02ba038d --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryView.kt @@ -0,0 +1,79 @@ +/* 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.tabhistory + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.component_tabhistory.* +import mozilla.components.browser.state.selector.selectedTab +import mozilla.components.browser.state.state.BrowserState +import org.mozilla.fenix.R + +interface TabHistoryViewInteractor { + + /** + * Jump to a specific index in the tab's history. + */ + fun goToHistoryItem(item: TabHistoryItem) +} + +class TabHistoryView( + private val container: ViewGroup, + private val expandDialog: () -> Unit, + interactor: TabHistoryViewInteractor +) : LayoutContainer { + + override val containerView: View? + get() = container + + val view: View = LayoutInflater.from(container.context) + .inflate(R.layout.component_tabhistory, container, true) + + private val adapter = TabHistoryAdapter(interactor) + private val layoutManager = object : LinearLayoutManager(view.context) { + override fun onLayoutCompleted(state: RecyclerView.State?) { + super.onLayoutCompleted(state) + currentIndex?.let { index -> + // Force expansion of the dialog, otherwise scrolling to the current history item + // won't work when its position is near the bottom of the recyclerview. + expandDialog.invoke() + // Also, attempt to center the current history item. + val itemView = tabHistoryRecyclerView.findViewHolderForLayoutPosition( + findFirstCompletelyVisibleItemPosition() + )?.itemView + val offset = tabHistoryRecyclerView.height / 2 - (itemView?.height ?: 0) / 2 + scrollToPositionWithOffset(index, offset) + } + } + }.apply { + reverseLayout = true + } + + private var currentIndex: Int? = null + + init { + tabHistoryRecyclerView.adapter = adapter + tabHistoryRecyclerView.layoutManager = layoutManager + } + + fun updateState(state: BrowserState) { + state.selectedTab?.content?.history?.let { historyState -> + currentIndex = historyState.currentIndex + val items = historyState.items.mapIndexed { index, historyItem -> + TabHistoryItem( + title = historyItem.title, + url = historyItem.uri, + index = index, + isSelected = index == historyState.currentIndex + ) + } + adapter.historyList = items + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryViewHolder.kt b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryViewHolder.kt new file mode 100644 index 000000000..88bd26d55 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryViewHolder.kt @@ -0,0 +1,34 @@ +/* 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.tabhistory + +import android.view.View +import androidx.core.text.bold +import androidx.core.text.buildSpannedString +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.synthetic.main.history_list_item.view.* + +class TabHistoryViewHolder( + private val view: View, + private val interactor: TabHistoryViewInteractor +) : RecyclerView.ViewHolder(view) { + + fun bind(item: TabHistoryItem) { + view.history_layout.overflowView.isVisible = false + view.history_layout.urlView.text = item.url + view.history_layout.loadFavicon(item.url) + + view.history_layout.titleView.text = if (item.isSelected) { + buildSpannedString { + bold { append(item.title) } + } + } else { + item.title + } + + view.setOnClickListener { interactor.goToHistoryItem(item) } + } +} diff --git a/app/src/main/res/layout/component_tabhistory.xml b/app/src/main/res/layout/component_tabhistory.xml new file mode 100644 index 000000000..3095029a2 --- /dev/null +++ b/app/src/main/res/layout/component_tabhistory.xml @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_tab_history_dialog.xml b/app/src/main/res/layout/fragment_tab_history_dialog.xml new file mode 100644 index 000000000..3be7d859a --- /dev/null +++ b/app/src/main/res/layout/fragment_tab_history_dialog.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 9daebae5a..d543ac084 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -102,6 +102,9 @@ + + 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 20fb905e1..a9d530bbf 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 @@ -220,7 +220,7 @@ class DefaultBrowserToolbarControllerTest { @Test fun handleToolbarForwardPress() = runBlockingTest { - val item = ToolbarMenu.Item.Forward + val item = ToolbarMenu.Item.Forward(false) val controller = createController(scope = this) controller.handleToolbarItemInteraction(item) @@ -229,6 +229,17 @@ class DefaultBrowserToolbarControllerTest { verify { sessionUseCases.goForward(currentSession) } } + @Test + fun handleToolbarForwardLongPress() = runBlockingTest { + val item = ToolbarMenu.Item.Forward(true) + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.FORWARD)) } + verify { navController.navigate(R.id.action_global_tabHistoryDialogFragment) } + } + @Test fun handleToolbarReloadPress() = runBlockingTest { val item = ToolbarMenu.Item.Reload diff --git a/app/src/test/java/org/mozilla/fenix/tabhistory/TabHistoryControllerTest.kt b/app/src/test/java/org/mozilla/fenix/tabhistory/TabHistoryControllerTest.kt new file mode 100644 index 000000000..8b32cc10f --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/tabhistory/TabHistoryControllerTest.kt @@ -0,0 +1,38 @@ +/* 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.tabhistory + +import androidx.navigation.NavController +import io.mockk.mockk +import io.mockk.verify +import mozilla.components.browser.session.SessionManager +import mozilla.components.feature.session.SessionUseCases +import org.junit.Test + +class TabHistoryControllerTest { + + val sessionManager: SessionManager = mockk(relaxed = true) + val navController: NavController = mockk(relaxed = true) + val sessionUseCases = SessionUseCases(sessionManager) + val goToHistoryIndexUseCase = sessionUseCases.goToHistoryIndex + val controller = DefaultTabHistoryController( + navController = navController, + goToHistoryIndexUseCase = goToHistoryIndexUseCase + ) + + val currentItem = TabHistoryItem( + index = 0, + title = "", + url = "", + isSelected = true + ) + + @Test + fun handleGoToHistoryIndex() { + controller.handleGoToHistoryItem(currentItem) + + verify { goToHistoryIndexUseCase.invoke(currentItem.index) } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/tabhistory/TabHistoryInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/tabhistory/TabHistoryInteractorTest.kt new file mode 100644 index 000000000..d9ffc5ca2 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/tabhistory/TabHistoryInteractorTest.kt @@ -0,0 +1,24 @@ +/* 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.tabhistory + +import io.mockk.mockk +import io.mockk.verify +import org.junit.Test + +class TabHistoryInteractorTest { + + val controller: TabHistoryController = mockk(relaxed = true) + val interactor = TabHistoryInteractor(controller) + + @Test + fun onGoToHistoryItem() { + val item: TabHistoryItem = mockk() + + interactor.goToHistoryItem(item) + + verify { controller.handleGoToHistoryItem(item) } + } +} From 9a2da5bb0f9dfeb8a50afdb2e2d55d9f786b54ee Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Wed, 22 Jul 2020 09:45:32 +0000 Subject: [PATCH 11/15] Import l10n. --- app/src/main/res/values-en-rCA/strings.xml | 18 +++++++++++++++--- app/src/main/res/values-es-rAR/strings.xml | 16 +++++++++++++++- app/src/main/res/values-hu/strings.xml | 18 +++++++++++++++--- app/src/main/res/values-kk/strings.xml | 18 +++++++++++++++--- app/src/main/res/values-ko/strings.xml | 16 +++++++++++++++- app/src/main/res/values-lt/strings.xml | 16 +++++++++++++--- app/src/main/res/values-nb-rNO/strings.xml | 18 +++++++++++++++--- app/src/main/res/values-nl/strings.xml | 18 +++++++++++++++--- app/src/main/res/values-pt-rBR/strings.xml | 16 +++++++++++++++- app/src/main/res/values-sv-rSE/strings.xml | 18 +++++++++++++++--- app/src/main/res/values-tr/strings.xml | 2 +- app/src/main/res/values-vi/strings.xml | 16 +++++++++++++++- app/src/main/res/values-zh-rCN/strings.xml | 16 +++++++++++++++- app/src/main/res/values-zh-rTW/strings.xml | 16 +++++++++++++++- 14 files changed, 194 insertions(+), 28 deletions(-) diff --git a/app/src/main/res/values-en-rCA/strings.xml b/app/src/main/res/values-en-rCA/strings.xml index 704aef3df..29a9b6b6c 100644 --- a/app/src/main/res/values-en-rCA/strings.xml +++ b/app/src/main/res/values-en-rCA/strings.xml @@ -573,6 +573,8 @@ Select folder Are you sure you want to delete this folder? + + %s will delete the selected items. Deleted %1$s @@ -627,8 +629,10 @@ Deleted %1$s - + Bookmarks deleted + + Deleting selected folders UNDO @@ -721,6 +725,8 @@ %d tab selected Tabs saved! + + Collection saved! Tab saved! @@ -825,6 +831,10 @@ DENY Are you sure you want to delete %1$s? + + Deleting this tab will delete the entire collection. You can create new collections at any time. + + Delete %1$s? Delete @@ -879,8 +889,6 @@ Automatically deletes browsing data when you select "Quit" from the main menu Automatically deletes browsing data when you select \"Quit\" from the main menu - - Browsing history Quit @@ -1223,6 +1231,8 @@ Logins and passwords that are not saved will be shown here. Logins and passwords will not be saved for these sites. + + Delete all exceptions Search logins @@ -1261,6 +1271,8 @@ Copy username Copy site + + Open site in browser Show password diff --git a/app/src/main/res/values-es-rAR/strings.xml b/app/src/main/res/values-es-rAR/strings.xml index c2d6cefbb..4f578f553 100644 --- a/app/src/main/res/values-es-rAR/strings.xml +++ b/app/src/main/res/values-es-rAR/strings.xml @@ -593,6 +593,8 @@ Seleccionar carpeta ¿Estás seguro de que querés eliminar eliminar esta carpeta? + + %s va a eliminar los elementos seleccionados. Se eliminó %1$s @@ -650,8 +652,10 @@ The first parameter is the host part of the URL of the bookmark deleted, if any --> Se eliminó %1$s - + Marcadores eliminados + + Eliminar carpetas seleccionadas UNDO @@ -746,6 +750,8 @@ ¡Pestañas guardadas! + + ¡Colección guardada! ¡Pestaña guardada! @@ -852,6 +858,10 @@ DENY ¿Estás seguro de que querés eliminar %1$s? + + Eliminar esta pestaña va a eliminar toda la colección. Podés crear nuevas colecciones en cualquier momento. + + ¿Eliminar %1$s? Eliminar @@ -1249,6 +1259,8 @@ Los inicios de sesión y las contraseñas que no se guardan se mostrarán aquí. Los inicios de sesión y las contraseñas no se van a guardar para estos sitios. + + Eliminar todas las excepciones Buscar inicios de sesión @@ -1287,6 +1299,8 @@ Copiar nombre de usuario Copiar sitio + + Abrir sitio en el navegador Mostrar contraseña diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index efd81663e..34b10de40 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -581,6 +581,8 @@ Válasszon mappát Biztos, hogy törölni szeretné ezt a mappát? + + A(z) %s törölni fogja a kiválasztott elemeket. %1$s törölve @@ -636,8 +638,10 @@ %1$s törölve - + Könyvjelzők törölve + + Kiválasztott mappák törlése VISSZAVONÁS @@ -731,6 +735,8 @@ %d lap kiválasztva Lapok mentve. + + Gyűjtemény mentve. Lap mentve. @@ -838,6 +844,10 @@ ELUTASÍTÁS Biztos, hogy törli ezt: %1$s? + + A lap törlésével törli az egész gyűjteményt. Bármikor létrehozhat új gyűjteményeket. + + Törli ezt: %1$s? Törlés @@ -893,8 +903,6 @@ Automatikusan törli a böngészési adatokat, ha a főmenüben a „Kilépés” lehetőséget választja - - Böngészési előzmények Kilépés @@ -1244,6 +1252,8 @@ Itt jelennek meg a nem mentett bejelentkezések és jelszavak. A bejelentkezéseket és a jelszavak nem lesznek elmentve ezeknél a webhelyeknél. + + Összes kivétel törlése Bejelentkezések keresése @@ -1282,6 +1292,8 @@ Felhasználónév másolása Oldal másolása + + Oldal megnyitása böngészőben Jelszó megjelenítése diff --git a/app/src/main/res/values-kk/strings.xml b/app/src/main/res/values-kk/strings.xml index f62480695..6cc73e343 100644 --- a/app/src/main/res/values-kk/strings.xml +++ b/app/src/main/res/values-kk/strings.xml @@ -569,6 +569,8 @@ Буманы таңдау Бұл буманы өшіруді шынымен қалайсыз ба? + + %s таңдалған элементтерді өшіреді. %1$s өшірілді @@ -623,8 +625,10 @@ %1$s өшірілді - + Бетбелгілер өшірілді + + Таңдалған бумаларды өшіру БОЛДЫРМАУ @@ -718,6 +722,8 @@ %d бет таңдалды Беттер сақталды! + + Жинақ сақталды! Бет сақталды! @@ -822,6 +828,10 @@ ТЫЙЫМ САЛУ %1$s өшіруді шынымен қалайсыз ба? + + Бұл бетті өшіру жинақты толығымен өшіреді. Жаңа жинақтарды кез келген уақытта жасауға болады. + + %1$s өшіру керек пе? Өшіру @@ -878,8 +888,6 @@ Негізгі мәзірден "Шығу" таңдау кезінде, шолу деректерін автоматты түрде өшіреді Негізгі мәзірден \"Шығу\" таңдау кезінде, шолу деректерін автоматты түрде өшіреді - - Шолу тарихы Шығу @@ -1227,6 +1235,8 @@ Сақталмаған логиндер мен парольдер осында көрсетіледі. Бұл сайттар үшін логиндер мен парольдер сақталмайды. + + Барлық ережеден тыс жағдайларды өшіру Логиндерден іздеу @@ -1265,6 +1275,8 @@ Пайдаланушы атын көшіріп алу Сайтты көшіріп алу + + Сайтты браузерде ашу Парольді көрсету diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index fac8150c4..e5804fd6a 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -593,6 +593,8 @@ 폴더 선택 이 폴더를 삭제하시겠습니까? + + %s가 선택된 항목들을 삭제합니다. %1$s 삭제됨 @@ -652,8 +654,10 @@ %1$s 삭제됨 - + 북마크 삭제됨 + + 선택한 폴더 삭제 중 실행 취소 @@ -757,6 +761,8 @@ 탭이 저장되었습니다! + + 모음집 저장됨! 탭이 저장되었습니다! @@ -869,6 +875,10 @@ 거부 %1$s 파일을 삭제하시겠습니까? + + 이 탭을 삭제하면 전체 모음집이 삭제됩니다. 언제든지 새 모음집을 만들 수 있습니다. + + %1$s 모음집을 삭제하시겠습니까? 삭제 @@ -1276,6 +1286,8 @@ 저장되지 않은 로그인과 비밀번호가 여기에 표시됩니다. 이 사이트에 대한 로그인과 비밀번호는 저장되지 않습니다. + + 모든 예외 삭제 로그인 검색 @@ -1314,6 +1326,8 @@ 사용자 이름 복사 사이트 복사 + + 브라우저에서 사이트 열기 비밀번호 보이기 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 9d85866e9..51452ce7b 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -582,6 +582,8 @@ Ar tikrai norite pašalinti šį aplanką? + + „%s“ pašalins pasirinktus elementus. Pašalintas „%1$s“ @@ -637,8 +639,10 @@ The first parameter is the host part of the URL of the bookmark deleted, if any --> Pašalintas %1$s - + Adresyno įrašai pašalinti + + Šalinami pasirinkti aplankai Atšaukti @@ -732,6 +736,8 @@ Pažymėta %d kortelė Kortelės įrašytos! + + Rinkinys įrašytas! Kortelė įrašyta! @@ -837,6 +843,10 @@ Drausti Ar tikrai norite pašalinti „%1$s“? + + Pašalindami šią kortelę, pašalinsite visą rinkinį. Naujus rinkinius galite sukurti bet kada. + + Pašalinti „%1$s“? Pašalinti @@ -894,8 +904,6 @@ Naršymo duomenys bus pašalinami automatiškai, pagrindiniame meniu pasirinkus „Išeiti“ Naršymo duomenys bus pašalinami automatiškai, pagrindiniame meniu pasirinkus „Išeiti“ - - Naršymo žurnalas Išeiti @@ -1281,6 +1289,8 @@ Kopijuoti naudotojo vardą Kopijuoti svetainę + + Atverti svetainę naršyklėje Rodyti slaptažodį diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 7157f81b0..7fa5cc3d8 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -582,6 +582,8 @@ Velg mappe Er du sikker på at du vil slette denne mappen? + + %s vil slette de valgte elementene. Slettet %1$s @@ -637,8 +639,10 @@ The first parameter is the host part of the URL of the bookmark deleted, if any --> Slettet %1$s - + Bokmerker slettet + + Sletter valgte mapper ANGRE @@ -734,6 +738,8 @@ Faner lagret! + + Samling lagret! Fane lagret! @@ -838,6 +844,10 @@ AVSLÅ Er du sikker på at du vil slette %1$s? + + Hvis du sletter denne fanen, blir hele samlingen slettet. Du kan når som helst lage nye samlinger. + + Vil du slette %1$s? Slett @@ -894,8 +904,6 @@ Sletter nettleserdata automatisk når du velger «Avslutt» fra hovedmenyen Sletter nettleserdata automatisk når du velger «Avslutt» fra hovedmenyen - - Nettleserhistorikk Avslutt @@ -1250,6 +1258,8 @@ Innlogginger og passord som ikke er lagret vil vises her. Innlogginger og passord vil ikke bli lagret for disse nettstedene. + + Slett alle unntak Søk innlogginger @@ -1289,6 +1299,8 @@ Kopier brukernavn Kopier nettsted + + Åpne nettsted i nettleseren Vis passord diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 1c91ae40e..da3638ceb 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -584,6 +584,8 @@ Map selecteren Weet u zeker dat u deze map wilt verwijderen? + + %s zal de geselecteerde items verwijderen. %1$s verwijderd @@ -638,8 +640,10 @@ %1$s verwijderd - + Bladwijzers verwijderd + + Geselecteerde mappen verwijderen ONGEDAAN MAKEN @@ -733,6 +737,8 @@ %d tabblad geselecteerd Tabbladen opgeslagen! + + Collectie opgeslagen! Tabblad opgeslagen! @@ -840,6 +846,10 @@ WEIGEREN Weet u zeker dat u %1$s wilt verwijderen? + + Als u dit tabblad verwijdert, wordt de hele collectie verwijderd. U kunt op elk moment nieuwe collecties maken. + + %1$s verwijderen? Verwijderen @@ -895,8 +905,6 @@ Verwijdert automatisch navigatiegegevens wanneer u in het hoofdmenu ‘Afsluiten’ selecteert Verwijdert automatisch navigatiegegevens wanneer u in het hoofdmenu ‘Afsluiten’ selecteert - - Navigatiegeschiedenis Afsluiten @@ -1243,6 +1251,8 @@ Niet-opgeslagen aanmeldingen en wachtwoorden worden hier weergegeven. Aanmeldingen en wachtwoorden worden voor deze websites niet opgeslagen. + + Alle uitzonderingen verwijderen Aanmeldingen zoeken @@ -1281,6 +1291,8 @@ Gebruikersnaam kopiëren Website kopiëren + + Website openen in browser Wachtwoord tonen diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 063da93c1..a85a6e3c3 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -576,6 +576,8 @@ Selecionar pasta Tem certeza que deseja excluir esta pasta? + + O %s excluirá os itens selecionados. %1$s excluída @@ -630,8 +632,10 @@ Excluiu %1$s - + Favoritos excluídos + + Excluindo pastas selecionadas DESFAZER @@ -726,6 +730,8 @@ %d aba selecionada Abas salvas! + + Coleção salva! Aba salva! @@ -830,6 +836,10 @@ NEGAR Tem certeza que deseja excluir %1$s? + + Excluir esta aba também excluirá toda a coleção. Você pode criar novas coleções quando quiser. + + Excluir %1$s? Excluir @@ -1234,6 +1244,8 @@ Contas e senhas que não são salvas são mostradas aqui. Contas e senhas desses sites não serão salvas. + + Excluir todas as exceções Pesquisar contas @@ -1272,6 +1284,8 @@ Copiar nome de usuário Copiar site + + Abrir site no navegador Exibir senha diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 7e477b53e..9304c25b2 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -586,6 +586,8 @@ Välj mapp Är du säker på att du vill ta bort den här mappen? + + %s kommer att radera de markerade objekten. Tog bort %1$s @@ -640,8 +642,10 @@ %1$s har tagits bort - + Bokmärken borttagna + + Tar bort valda mappar ÅNGRA @@ -735,6 +739,8 @@ %d flik vald Flikar sparade! + + Samling sparad! Flik sparad! @@ -843,6 +849,10 @@ NEKA Är du säker att du vill ta bort %1$s? + + Om du tar bort den här fliken raderas hela samlingen. Du kan skapa nya samlingar när som helst. + + Tog bort %1$s? Ta bort @@ -899,8 +909,6 @@ Tar automatiskt bort surfdata när du väljer "Avsluta" från huvudmenyn Tar automatiskt bort surfdata när du väljer \"Avsluta\" från huvudmenyn - - Webbläsarhistorik Avsluta @@ -1249,6 +1257,8 @@ Inloggningar och lösenord som inte sparas visas här. Inloggningar och lösenord sparas inte för dessa webbplatser. + + Ta bort alla undantag Sök inloggningar @@ -1287,6 +1297,8 @@ Kopiera användarnamn Kopiera webbplats + + Öppna webbplatsen i webbläsaren Visa lösenord diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 3bc20595b..13a458b21 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -117,7 +117,7 @@ %1$s ile aç - %1$s TARAFINDAN GELİŞTİRİLDİ + %1$s SEKMESİ diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 5d2534f8c..65fdc0699 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -575,6 +575,8 @@ Chọn thư mục Bạn có chắc chắn muốn xóa thư mục này? + + %s sẽ xóa các mục đã chọn. Đã xóa %1$s @@ -629,8 +631,10 @@ Đã xóa %1$s - + Đã xóa dấu trang + + Đang xóa các thư mục đã chọn HOÀN TÁC @@ -724,6 +728,8 @@ %d thẻ được chọn Đã lưu các thẻ! + + Đã lưu bộ sưu tập! Đã lưu thẻ! @@ -828,6 +834,10 @@ TỪ CHỐI Bạn có chắc chắn muốn xóa %1$s không? + + Xóa thẻ này sẽ xóa bộ sưu tập này. Bạn có thể tạo bộ sưu tập mới bất cứ lúc nào. + + Xóa %1$s? Xóa @@ -1224,6 +1234,8 @@ Đăng nhập và mật khẩu không được lưu sẽ được hiển thị ở đây. Đăng nhập và mật khẩu sẽ không được lưu cho các trang web này. + + Xóa tất cả các ngoại lệ Tìm thông tin đăng nhập @@ -1262,6 +1274,8 @@ Sao chép tên người dùng Sao chép URL trang web + + Mở trang web trong trình duyệt Hiện mật khẩu diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 919c11e20..d1e4e6f10 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -593,6 +593,8 @@ 选择文件夹 您确定要删除这个文件夹吗? + + %s 将删除所选项目。 已删除 %1$s @@ -650,8 +652,10 @@ 已删除 %1$s 条书签 - + 书签已删除 + + 正在删除所选文件夹 撤销 @@ -756,6 +760,8 @@ 标签页已保存! + + 收藏集已保存! 标签页已保存! @@ -867,6 +873,10 @@ 拒绝 您确定要删除“%1$s”吗? + + 删除此标签页将删除整个收藏集。您可以随时新建收藏集。 + + 要删除 %1$s 吗? 删除 @@ -1268,6 +1278,8 @@ 不保存登录名和密码的网站将显示于此处。 将不保存这些网站的登录名和密码。 + + 删除所有例外 搜索登录信息 @@ -1306,6 +1318,8 @@ 复制用户名 复制网站 + + 在浏览器中打开网站 显示密码 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index fc4c5b7a3..001d531d5 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -588,6 +588,8 @@ 選擇資料夾 您確定要刪除這個資料夾嗎? + + %s 將刪除選擇的項目。 已刪除 %1$s @@ -643,8 +645,10 @@ 已刪除書籤 %1$s - + 已刪除書籤 + + 刪除選擇的資料夾 還原 @@ -748,6 +752,8 @@ 已儲存分頁! + + 已儲存收藏集! 已儲存分頁! @@ -859,6 +865,10 @@ 拒絕 您確定要刪除 %1$s 嗎? + + 刪除此分頁也會同時刪除整個收藏集,您可以之後再建立新的收藏集。 + + 要刪除 %1$s 嗎? 刪除 @@ -1258,6 +1268,8 @@ 不儲存登入資訊與密碼的網站將顯示於此處。 將不儲存這些網站的登入資訊與密碼。 + + 刪除所有例外 搜尋登入資訊 @@ -1296,6 +1308,8 @@ 複製使用者名稱 複製網站 + + 用瀏覽器開啟 顯示密碼 From e7dc5580b2cc9151f83c144549d39769be43e2d0 Mon Sep 17 00:00:00 2001 From: Kainalu Hagiwara Date: Thu, 25 Jun 2020 14:52:52 -0700 Subject: [PATCH 12/15] For #3481 - Implement swipe on toolbar to switch tabs. --- app/build.gradle | 1 + .../java/org/mozilla/fenix/FeatureFlags.kt | 5 + .../mozilla/fenix/browser/BrowserFragment.kt | 15 + .../fenix/browser/SwipeGestureLayout.kt | 136 +++++++ .../org/mozilla/fenix/browser/TabPreview.kt | 67 ++++ .../fenix/browser/ToolbarGestureHandler.kt | 356 ++++++++++++++++++ app/src/main/res/layout/fragment_browser.xml | 93 +++-- app/src/main/res/layout/tab_preview.xml | 44 +++ buildSrc/src/main/java/Dependencies.kt | 2 + 9 files changed, 680 insertions(+), 39 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/browser/SwipeGestureLayout.kt create mode 100644 app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt create mode 100644 app/src/main/java/org/mozilla/fenix/browser/ToolbarGestureHandler.kt create mode 100644 app/src/main/res/layout/tab_preview.xml diff --git a/app/build.gradle b/app/build.gradle index ff6ef3d6a..ddf6ce535 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -503,6 +503,7 @@ dependencies { implementation Deps.androidx_lifecycle_viewmodel implementation Deps.androidx_core implementation Deps.androidx_core_ktx + implementation Deps.androidx_dynamic_animation implementation Deps.androidx_transition implementation Deps.androidx_work_ktx implementation Deps.google_material diff --git a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt index 2f76237ed..6e6d4a04d 100644 --- a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt +++ b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt @@ -24,4 +24,9 @@ object FeatureFlags { * Enables new tab tray pref */ val tabTray = Config.channel.isNightlyOrDebug + + /** + * Enables swipe on toolbar to switch tabs + */ + val swipeToSwitchTabs = Config.channel.isNightlyOrDebug } 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 4c3b5882b..ae3f9a158 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -15,6 +15,7 @@ import androidx.core.content.ContextCompat import androidx.lifecycle.Observer import androidx.navigation.fragment.findNavController import com.google.android.material.snackbar.Snackbar +import kotlinx.android.synthetic.main.fragment_browser.* import kotlinx.android.synthetic.main.fragment_browser.view.* import kotlinx.coroutines.ExperimentalCoroutinesApi import mozilla.components.browser.session.Session @@ -29,6 +30,7 @@ import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.tabs.WindowFeature import mozilla.components.support.base.feature.UserInteractionHandler import mozilla.components.support.base.feature.ViewBoundFeatureWrapper +import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.R import org.mozilla.fenix.addons.runIfFragmentIsAttached import org.mozilla.fenix.components.FenixSnackbar @@ -66,11 +68,24 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { return view } + @Suppress("LongMethod") override fun initializeUI(view: View): Session? { val context = requireContext() val components = context.components return super.initializeUI(view)?.also { + if (FeatureFlags.swipeToSwitchTabs) { + gestureLayout.addGestureListener( + ToolbarGestureHandler( + activity = requireActivity(), + contentLayout = browserLayout, + tabPreview = tabPreview, + toolbarLayout = browserToolbarView.view, + sessionManager = components.core.sessionManager + ) + ) + } + val readerModeAction = BrowserToolbar.ToggleButton( image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_readermode)!!, diff --git a/app/src/main/java/org/mozilla/fenix/browser/SwipeGestureLayout.kt b/app/src/main/java/org/mozilla/fenix/browser/SwipeGestureLayout.kt new file mode 100644 index 000000000..c1bccab6c --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/browser/SwipeGestureLayout.kt @@ -0,0 +1,136 @@ +/* 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.browser + +import android.content.Context +import android.graphics.PointF +import android.util.AttributeSet +import android.view.GestureDetector +import android.view.MotionEvent +import android.widget.FrameLayout +import androidx.core.view.GestureDetectorCompat + +/** + * Interface that allows intercepting and handling swipe gestures received in a [SwipeGestureLayout]. + */ +interface SwipeGestureListener { + + /** + * Called when the [SwipeGestureLayout] detects the start of a swipe gesture. The listener + * should return true if it wants to handle the swipe gesture. If the listener returns false + * it will not receive any callbacks for future events that the swipe produces. + * + * @param start the initial point where the gesture started + * @param next the next point in the gesture + */ + fun onSwipeStarted(start: PointF, next: PointF): Boolean + + /** + * Called when the swipe gesture receives a new event. + * + * @param distanceX the change along the x-axis since the last swipe update + * @param distanceY the change along the y-axis since the last swipe update + */ + fun onSwipeUpdate(distanceX: Float, distanceY: Float) + + /** + * Called when the user finishes the swipe gesture (ie lifts their finger off the screen) + * + * @param velocityX the velocity of the swipe along the x-axis + * @param velocityY the velocity of the swipe along the y-axis + */ + fun onSwipeFinished(velocityX: Float, velocityY: Float) +} + +/** + * A [FrameLayout] that allows listeners to intercept and handle swipe events. + * + * Listeners are called in the order they are added and the first listener to intercept a swipe event + * is the only listener that will receive events for the duration of that swipe. + */ +class SwipeGestureLayout @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : FrameLayout(context, attrs, defStyleAttr) { + + private val gestureListener = object : GestureDetector.SimpleOnGestureListener() { + override fun onDown(e: MotionEvent?): Boolean { + return true + } + + override fun onScroll( + e1: MotionEvent?, + e2: MotionEvent?, + distanceX: Float, + distanceY: Float + ): Boolean { + val start = e1?.let { event -> PointF(event.rawX, event.rawY) } ?: return false + val next = e2?.let { event -> PointF(event.rawX, event.rawY) } ?: return false + + if (activeListener == null && !handledInitialScroll) { + activeListener = listeners.firstOrNull { listener -> + listener.onSwipeStarted(start, next) + } + handledInitialScroll = true + } + activeListener?.onSwipeUpdate(distanceX, distanceY) + return activeListener != null + } + + override fun onFling( + e1: MotionEvent?, + e2: MotionEvent?, + velocityX: Float, + velocityY: Float + ): Boolean { + activeListener?.onSwipeFinished(velocityX, velocityY) + return if (activeListener != null) { + activeListener = null + true + } else { + false + } + } + } + + private val gestureDetector = GestureDetectorCompat(context, gestureListener) + + private val listeners = mutableListOf() + private var activeListener: SwipeGestureListener? = null + private var handledInitialScroll = false + + fun addGestureListener(listener: SwipeGestureListener) { + listeners.add(listener) + } + + override fun onInterceptTouchEvent(event: MotionEvent?): Boolean { + return when (event?.actionMasked) { + MotionEvent.ACTION_DOWN -> { + handledInitialScroll = false + gestureDetector.onTouchEvent(event) + false + } + else -> gestureDetector.onTouchEvent(event) + } + } + + override fun onTouchEvent(event: MotionEvent?): Boolean { + return when (event?.actionMasked) { + MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> { + gestureDetector.onTouchEvent(event) + // If the active listener is not null here, then we haven't detected a fling + // so notify the listener that the swipe was finished with 0 velocity + activeListener?.onSwipeFinished( + velocityX = 0f, + velocityY = 0f + ) + activeListener = null + false + } + else -> gestureDetector.onTouchEvent(event) + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt b/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt new file mode 100644 index 000000000..2c14e7661 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt @@ -0,0 +1,67 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.browser + +import android.content.Context +import android.util.AttributeSet +import android.view.Gravity +import android.view.LayoutInflater +import android.widget.FrameLayout +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import androidx.core.view.updateLayoutParams +import kotlinx.android.synthetic.main.tab_preview.view.* +import mozilla.components.browser.thumbnails.loader.ThumbnailLoader +import mozilla.components.support.images.ext.loadIntoView +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.theme.ThemeManager + +class TabPreview @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyle: Int = 0 +) : FrameLayout(context, attrs, defStyle) { + + private val thumbnailLoader = ThumbnailLoader(context.components.core.thumbnailStorage) + + init { + val inflater = LayoutInflater.from(context) + inflater.inflate(R.layout.tab_preview, this, true) + + if (!context.settings().shouldUseBottomToolbar) { + fakeToolbar.updateLayoutParams { + gravity = Gravity.TOP + } + + fakeToolbar.background = ResourcesCompat.getDrawable( + resources, + ThemeManager.resolveAttribute(R.attr.bottomBarBackgroundTop, context), + null + ) + } + + menuButton.setColorFilter( + ContextCompat.getColor( + context, + ThemeManager.resolveAttribute(R.attr.primaryText, context) + ) + ) + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + previewThumbnail.translationY = if (!context.settings().shouldUseBottomToolbar) { + fakeToolbar.height.toFloat() + } else { + 0f + } + } + + fun loadPreviewThumbnail(thumbnailId: String) { + thumbnailLoader.loadIntoView(previewThumbnail, thumbnailId) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/browser/ToolbarGestureHandler.kt b/app/src/main/java/org/mozilla/fenix/browser/ToolbarGestureHandler.kt new file mode 100644 index 000000000..00508690d --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/browser/ToolbarGestureHandler.kt @@ -0,0 +1,356 @@ +/* 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.browser + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.app.Activity +import android.graphics.PointF +import android.graphics.Rect +import android.os.Build +import android.util.TypedValue +import android.view.View +import android.view.ViewConfiguration +import androidx.annotation.Dimension +import androidx.annotation.Dimension.DP +import androidx.core.graphics.contains +import androidx.core.graphics.toPoint +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.isVisible +import androidx.dynamicanimation.animation.DynamicAnimation +import androidx.dynamicanimation.animation.FlingAnimation +import mozilla.components.browser.session.Session +import mozilla.components.browser.session.SessionManager +import mozilla.components.support.ktx.android.util.dpToPx +import mozilla.components.support.ktx.android.view.getRectWithViewLocation +import org.mozilla.fenix.ext.sessionsOfType +import org.mozilla.fenix.ext.settings +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min + +/** + * Handles intercepting touch events on the toolbar for swipe gestures and executes the + * necessary animations. + */ +@Suppress("LargeClass", "TooManyFunctions") +class ToolbarGestureHandler( + private val activity: Activity, + private val contentLayout: View, + private val tabPreview: TabPreview, + private val toolbarLayout: View, + private val sessionManager: SessionManager +) : SwipeGestureListener { + + private enum class GestureDirection { + LEFT_TO_RIGHT, RIGHT_TO_LEFT + } + + private sealed class Destination { + data class Tab(val session: Session) : Destination() + object None : Destination() + } + + private val windowWidth: Int + get() = activity.resources.displayMetrics.widthPixels + + private val windowInsets: WindowInsetsCompat? + get() = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + // In theory, the rootWindowInsets should exist at this point but if the decorView is + // not attached for some reason we'll get a NullPointerException without the check. + activity.window.decorView.rootWindowInsets?.let { + WindowInsetsCompat.toWindowInsetsCompat(it) + } + } else { + null + } + + private val previewOffset = PREVIEW_OFFSET.dpToPx(activity.resources.displayMetrics) + + private val touchSlop = ViewConfiguration.get(activity).scaledTouchSlop + private val minimumFlingVelocity = ViewConfiguration.get(activity).scaledMinimumFlingVelocity + private val defaultVelocity = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + MINIMUM_ANIMATION_VELOCITY, + activity.resources.displayMetrics + ) + + private var gestureDirection = GestureDirection.LEFT_TO_RIGHT + + override fun onSwipeStarted(start: PointF, next: PointF): Boolean { + val dx = next.x - start.x + val dy = next.y - start.y + gestureDirection = if (dx < 0) { + GestureDirection.RIGHT_TO_LEFT + } else { + GestureDirection.LEFT_TO_RIGHT + } + + return if (start.isInToolbar() && abs(dx) > touchSlop && abs(dy) < abs(dx)) { + preparePreview(getDestination()) + true + } else { + false + } + } + + override fun onSwipeUpdate(distanceX: Float, distanceY: Float) { + when (getDestination()) { + is Destination.Tab -> { + // Restrict the range of motion for the views so you can't start a swipe in one direction + // then move your finger far enough in the other direction and make the content visually + // start sliding off screen the other way. + tabPreview.translationX = when (gestureDirection) { + GestureDirection.RIGHT_TO_LEFT -> min( + windowWidth.toFloat() + previewOffset, + tabPreview.translationX - distanceX + ) + GestureDirection.LEFT_TO_RIGHT -> max( + -windowWidth.toFloat() - previewOffset, + tabPreview.translationX - distanceX + ) + } + contentLayout.translationX = when (gestureDirection) { + GestureDirection.RIGHT_TO_LEFT -> min( + 0f, + contentLayout.translationX - distanceX + ) + GestureDirection.LEFT_TO_RIGHT -> max( + 0f, + contentLayout.translationX - distanceX + ) + } + } + is Destination.None -> { + // If there is no "next" tab to swipe to in the gesture direction, only do a + // partial animation to show that we are at the end of the tab list + val maxContentHidden = contentLayout.width * OVERSCROLL_HIDE_PERCENT + contentLayout.translationX = when (gestureDirection) { + GestureDirection.RIGHT_TO_LEFT -> max( + -maxContentHidden.toFloat(), + contentLayout.translationX - distanceX + ).coerceAtMost(0f) + GestureDirection.LEFT_TO_RIGHT -> min( + maxContentHidden.toFloat(), + contentLayout.translationX - distanceX + ).coerceAtLeast(0f) + } + } + } + } + + override fun onSwipeFinished( + velocityX: Float, + velocityY: Float + ) { + val destination = getDestination() + if (destination is Destination.Tab && isGestureComplete(velocityX)) { + animateToNextTab(velocityX, destination.session) + } else { + animateCanceledGesture(velocityX) + } + } + + private fun createFlingAnimation( + view: View, + minValue: Float, + maxValue: Float, + startVelocity: Float + ): FlingAnimation = + FlingAnimation(view, DynamicAnimation.TRANSLATION_X).apply { + setMinValue(minValue) + setMaxValue(maxValue) + setStartVelocity(startVelocity) + friction = ViewConfiguration.getScrollFriction() + } + + private fun getDestination(): Destination { + val isLtr = activity.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR + val currentSession = sessionManager.selectedSession ?: return Destination.None + val currentIndex = sessionManager.sessionsOfType(currentSession.private).indexOfFirst { + it.id == currentSession.id + } + + return if (currentIndex == -1) { + Destination.None + } else { + val sessions = sessionManager.sessionsOfType(currentSession.private) + val index = when (gestureDirection) { + GestureDirection.RIGHT_TO_LEFT -> if (isLtr) { + currentIndex + 1 + } else { + currentIndex - 1 + } + GestureDirection.LEFT_TO_RIGHT -> if (isLtr) { + currentIndex - 1 + } else { + currentIndex + 1 + } + } + + if (index < sessions.count() && index >= 0) { + Destination.Tab(sessions.elementAt(index)) + } else { + Destination.None + } + } + } + + private fun preparePreview(destination: Destination) { + val thumbnailId = when (destination) { + is Destination.Tab -> destination.session.id + is Destination.None -> return + } + + tabPreview.loadPreviewThumbnail(thumbnailId) + tabPreview.alpha = 1f + tabPreview.translationX = when (gestureDirection) { + GestureDirection.RIGHT_TO_LEFT -> windowWidth.toFloat() + previewOffset + GestureDirection.LEFT_TO_RIGHT -> -windowWidth.toFloat() - previewOffset + } + tabPreview.isVisible = true + } + + /** + * Checks if the gesture is complete based on the position of tab preview and the velocity of + * the gesture. A completed gesture means the user has indicated they want to swipe to the next + * tab. The gesture is considered complete if one of the following is true: + * + * 1. The user initiated a fling in the same direction as the initial movement + * 2. There is no fling initiated, but the percentage of the tab preview shown is at least + * [GESTURE_FINISH_PERCENT] + * + * If the user initiated a fling in the opposite direction of the initial movement, the + * gesture is always considered incomplete. + */ + private fun isGestureComplete(velocityX: Float): Boolean { + val previewWidth = tabPreview.getRectWithViewLocation().visibleWidth.toDouble() + val velocityMatchesDirection = when (gestureDirection) { + GestureDirection.RIGHT_TO_LEFT -> velocityX <= 0 + GestureDirection.LEFT_TO_RIGHT -> velocityX >= 0 + } + val reverseFling = + abs(velocityX) >= minimumFlingVelocity && !velocityMatchesDirection + + return !reverseFling && (previewWidth / windowWidth >= GESTURE_FINISH_PERCENT || + abs(velocityX) >= minimumFlingVelocity) + } + + private fun getVelocityFromFling(velocityX: Float): Float { + return max(abs(velocityX), defaultVelocity) + } + + private fun animateToNextTab(velocityX: Float, session: Session) { + val browserFinalXCoordinate: Float = when (gestureDirection) { + GestureDirection.RIGHT_TO_LEFT -> -windowWidth.toFloat() - previewOffset + GestureDirection.LEFT_TO_RIGHT -> windowWidth.toFloat() + previewOffset + } + val animationVelocity = when (gestureDirection) { + GestureDirection.RIGHT_TO_LEFT -> -getVelocityFromFling(velocityX) + GestureDirection.LEFT_TO_RIGHT -> getVelocityFromFling(velocityX) + } + + // Finish animating the contentLayout off screen and tabPreview on screen + createFlingAnimation( + view = contentLayout, + minValue = min(0f, browserFinalXCoordinate), + maxValue = max(0f, browserFinalXCoordinate), + startVelocity = animationVelocity + ).addUpdateListener { _, value, _ -> + tabPreview.translationX = when (gestureDirection) { + GestureDirection.RIGHT_TO_LEFT -> value + windowWidth + previewOffset + GestureDirection.LEFT_TO_RIGHT -> value - windowWidth - previewOffset + } + }.addEndListener { _, _, _, _ -> + contentLayout.translationX = 0f + sessionManager.select(session) + + // Fade out the tab preview to prevent flickering + val shortAnimationDuration = + activity.resources.getInteger(android.R.integer.config_shortAnimTime) + tabPreview.animate() + .alpha(0f) + .setDuration(shortAnimationDuration.toLong()) + .setListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + tabPreview.isVisible = false + } + }) + }.start() + } + + private fun animateCanceledGesture(gestureVelocity: Float) { + val velocity = if (getDestination() is Destination.None) { + defaultVelocity + } else { + getVelocityFromFling(gestureVelocity) + }.let { v -> + when (gestureDirection) { + GestureDirection.RIGHT_TO_LEFT -> v + GestureDirection.LEFT_TO_RIGHT -> -v + } + } + + createFlingAnimation( + view = contentLayout, + minValue = min(0f, contentLayout.translationX), + maxValue = max(0f, contentLayout.translationX), + startVelocity = velocity + ).addUpdateListener { _, value, _ -> + tabPreview.translationX = when (gestureDirection) { + GestureDirection.RIGHT_TO_LEFT -> value + windowWidth + previewOffset + GestureDirection.LEFT_TO_RIGHT -> value - windowWidth - previewOffset + } + }.addEndListener { _, _, _, _ -> + tabPreview.isVisible = false + }.start() + } + + private fun PointF.isInToolbar(): Boolean { + val toolbarLocation = toolbarLayout.getRectWithViewLocation() + // In Android 10, the system gesture touch area overlaps the bottom of the toolbar, so + // lets make our swipe area taller by that amount + windowInsets?.let { insets -> + if (activity.settings().shouldUseBottomToolbar) { + toolbarLocation.top -= (insets.mandatorySystemGestureInsets.bottom - insets.stableInsetBottom) + } + } + return toolbarLocation.contains(toPoint()) + } + + private val Rect.visibleWidth: Int + get() = if (left < 0) { + right + } else { + windowWidth - left + } + + companion object { + /** + * The percentage of the tab preview that needs to be visible to consider the + * tab switching gesture complete. + */ + private const val GESTURE_FINISH_PERCENT = 0.25 + + /** + * The percentage of the content view that can be hidden by the tab switching gesture if + * there is not tab available to switch to + */ + private const val OVERSCROLL_HIDE_PERCENT = 0.20 + + /** + * The speed of the fling animation (in dp per second). + */ + @Dimension(unit = DP) + private const val MINIMUM_ANIMATION_VELOCITY = 1500f + + /** + * The size of the gap between the tab preview and content layout. + */ + @Dimension(unit = DP) + private const val PREVIEW_OFFSET = 48 + } +} diff --git a/app/src/main/res/layout/fragment_browser.xml b/app/src/main/res/layout/fragment_browser.xml index 08dc10d49..63e9fde8d 100644 --- a/app/src/main/res/layout/fragment_browser.xml +++ b/app/src/main/res/layout/fragment_browser.xml @@ -2,51 +2,66 @@ - - + + + + + + + + + + + + + + + + - - - - - - - - - - + diff --git a/app/src/main/res/layout/tab_preview.xml b/app/src/main/res/layout/tab_preview.xml new file mode 100644 index 000000000..7f9869060 --- /dev/null +++ b/app/src/main/res/layout/tab_preview.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 4421c1b0a..060e72ecf 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -28,6 +28,7 @@ object Versions { const val androidx_paging = "2.1.0" const val androidx_transition = "1.3.0" const val androidx_work = "2.2.0" + const val androidx_dynamic_animation = "1.0.0" const val google_material = "1.1.0" const val google_flexbox = "2.0.1" @@ -170,6 +171,7 @@ object Deps { const val androidx_recyclerview = "androidx.recyclerview:recyclerview:${Versions.androidx_recyclerview}" const val androidx_core = "androidx.core:core:${Versions.androidx_core}" const val androidx_core_ktx = "androidx.core:core-ktx:${Versions.androidx_core}" + const val androidx_dynamic_animation = "androidx.dynamicanimation:dynamicanimation:${Versions.androidx_dynamic_animation}" const val androidx_transition = "androidx.transition:transition:${Versions.androidx_transition}" const val androidx_work_ktx = "androidx.work:work-runtime-ktx:${Versions.androidx_work}" const val androidx_work_testing = "androidx.work:work-testing:${Versions.androidx_work}" From 58ccc64386af029e4f19707b69b3d3654ef2ff2a Mon Sep 17 00:00:00 2001 From: Kainalu Hagiwara Date: Thu, 16 Jul 2020 16:30:27 -0700 Subject: [PATCH 13/15] Fix UI tests. We need to pass a CoordinatorLayout instead of a SwipeGestureLayout to FenixSnackbar.make() in BaseBrowserFragment to prevent UI tests from breaking. We also need to remove a few view IDs from the tab preview. --- .../mozilla/fenix/browser/BaseBrowserFragment.kt | 8 ++++---- .../org/mozilla/fenix/browser/BrowserFragment.kt | 2 +- .../java/org/mozilla/fenix/browser/TabPreview.kt | 12 +++++------- app/src/main/res/layout/tab_preview.xml | 13 +++++++++---- 4 files changed, 19 insertions(+), 16 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 55288f861..0c5387476 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -385,7 +385,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session tryAgain = downloadFeature::tryAgain, onCannotOpenFile = { FenixSnackbar.make( - view = view, + view = view.browserLayout, duration = Snackbar.LENGTH_SHORT, isDisplayedWithBrowserToolbar = true ) @@ -649,7 +649,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session val onCannotOpenFile = { FenixSnackbar.make( - view = view, + view = view.browserLayout, duration = Snackbar.LENGTH_SHORT, isDisplayedWithBrowserToolbar = true ) @@ -932,7 +932,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session view?.let { view -> FenixSnackbar.make( - view = view, + view = view.browserLayout, duration = FenixSnackbar.LENGTH_LONG, isDisplayedWithBrowserToolbar = true ) @@ -978,7 +978,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session // Close find in page bar if opened findInPageIntegration.onBackPressed() FenixSnackbar.make( - view = requireView(), + view = requireView().browserLayout, duration = Snackbar.LENGTH_SHORT, isDisplayedWithBrowserToolbar = false ) 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 ae3f9a158..9c4f33728 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -258,7 +258,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { } } FenixSnackbar.make( - view = view, + view = view.browserLayout, duration = Snackbar.LENGTH_SHORT, isDisplayedWithBrowserToolbar = true ) diff --git a/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt b/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt index 2c14e7661..0f65766c8 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt @@ -8,10 +8,11 @@ import android.content.Context import android.util.AttributeSet import android.view.Gravity import android.view.LayoutInflater +import android.view.View import android.widget.FrameLayout -import androidx.core.content.ContextCompat import androidx.core.content.res.ResourcesCompat import androidx.core.view.updateLayoutParams +import kotlinx.android.synthetic.main.mozac_ui_tabcounter_layout.view.* import kotlinx.android.synthetic.main.tab_preview.view.* import mozilla.components.browser.thumbnails.loader.ThumbnailLoader import mozilla.components.support.images.ext.loadIntoView @@ -44,12 +45,9 @@ class TabPreview @JvmOverloads constructor( ) } - menuButton.setColorFilter( - ContextCompat.getColor( - context, - ThemeManager.resolveAttribute(R.attr.primaryText, context) - ) - ) + // Change view properties to avoid confusing the UI tests + tab_button.counter_box.id = View.NO_ID + tab_button.counter_text.id = View.NO_ID } override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { diff --git a/app/src/main/res/layout/tab_preview.xml b/app/src/main/res/layout/tab_preview.xml index 7f9869060..e7da4ce04 100644 --- a/app/src/main/res/layout/tab_preview.xml +++ b/app/src/main/res/layout/tab_preview.xml @@ -2,7 +2,9 @@ - + + - + android:layout_gravity="center" + android:scaleType="center" + app:srcCompat="@drawable/ic_menu" + app:tint="?primaryText" /> From b26e39906db511850e71c9ffe62cbf693d27aca5 Mon Sep 17 00:00:00 2001 From: Kainalu Hagiwara Date: Wed, 22 Jul 2020 11:56:55 -0700 Subject: [PATCH 14/15] No issue - Fix compilation error caused by removed extension function. --- .../main/java/org/mozilla/fenix/browser/TabPreview.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt b/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt index 0f65766c8..ba90b09bb 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt @@ -11,15 +11,17 @@ import android.view.LayoutInflater import android.view.View import android.widget.FrameLayout import androidx.core.content.res.ResourcesCompat +import androidx.core.view.doOnNextLayout import androidx.core.view.updateLayoutParams import kotlinx.android.synthetic.main.mozac_ui_tabcounter_layout.view.* import kotlinx.android.synthetic.main.tab_preview.view.* import mozilla.components.browser.thumbnails.loader.ThumbnailLoader -import mozilla.components.support.images.ext.loadIntoView +import mozilla.components.support.images.ImageLoadRequest import org.mozilla.fenix.R import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings import org.mozilla.fenix.theme.ThemeManager +import kotlin.math.max class TabPreview @JvmOverloads constructor( context: Context, @@ -60,6 +62,9 @@ class TabPreview @JvmOverloads constructor( } fun loadPreviewThumbnail(thumbnailId: String) { - thumbnailLoader.loadIntoView(previewThumbnail, thumbnailId) + doOnNextLayout { + val thumbnailSize = max(previewThumbnail.height, previewThumbnail.width) + thumbnailLoader.loadIntoView(previewThumbnail, ImageLoadRequest(thumbnailId, thumbnailSize)) + } } } From c143f95819aa34e65017ded6bc519d87c2c8a0e0 Mon Sep 17 00:00:00 2001 From: Jonathan Almeida Date: Thu, 9 Jul 2020 15:15:58 -0400 Subject: [PATCH 15/15] Issue #11333: Set ContentBlocking settings directly on GeckoRuntime We set the ContentBlockingSettings directly on the GeckoRuntime now to improve the startup of the engine. This change has requirements from Android Components and GeckoView, so we would only see the full perf benefits in Nightly as the changes ride the train, although we might start to see some of them as we're updating the GeckoProvider for the `geckoBeta` variant as well. Co-authored-by: Arturo Mejia --- .../java/org/mozilla/fenix/engine/GeckoProvider.kt | 11 ++++++++--- .../java/org/mozilla/fenix/engine/GeckoProvider.kt | 11 ++++++++--- .../main/java/org/mozilla/fenix/components/Core.kt | 12 ++++++++++-- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/app/src/geckoBeta/java/org/mozilla/fenix/engine/GeckoProvider.kt b/app/src/geckoBeta/java/org/mozilla/fenix/engine/GeckoProvider.kt index 96aa0d767..b5171c98a 100644 --- a/app/src/geckoBeta/java/org/mozilla/fenix/engine/GeckoProvider.kt +++ b/app/src/geckoBeta/java/org/mozilla/fenix/engine/GeckoProvider.kt @@ -5,7 +5,9 @@ import android.content.Context import android.os.Bundle import mozilla.components.browser.engine.gecko.autofill.GeckoLoginDelegateWrapper +import mozilla.components.browser.engine.gecko.ext.toContentBlockingSetting import mozilla.components.browser.engine.gecko.glean.GeckoAdapter +import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy import mozilla.components.concept.storage.LoginsStorage import mozilla.components.lib.crash.handler.CrashHandlerService import mozilla.components.service.sync.logins.GeckoLoginStorageDelegate @@ -21,10 +23,11 @@ object GeckoProvider { @Synchronized fun getOrCreateRuntime( context: Context, - storage: Lazy + storage: Lazy, + trackingProtectionPolicy: TrackingProtectionPolicy ): GeckoRuntime { if (runtime == null) { - runtime = createRuntime(context, storage) + runtime = createRuntime(context, storage, trackingProtectionPolicy) } return runtime!! @@ -32,7 +35,8 @@ object GeckoProvider { private fun createRuntime( context: Context, - storage: Lazy + storage: Lazy, + policy: TrackingProtectionPolicy ): GeckoRuntime { val builder = GeckoRuntimeSettings.Builder() @@ -44,6 +48,7 @@ object GeckoProvider { val runtimeSettings = builder .crashHandler(CrashHandlerService::class.java) .telemetryDelegate(GeckoAdapter()) + .contentBlocking(policy.toContentBlockingSetting()) .aboutConfigEnabled(Config.channel.isBeta) .debugLogging(Config.channel.isDebug) .build() diff --git a/app/src/geckoNightly/java/org/mozilla/fenix/engine/GeckoProvider.kt b/app/src/geckoNightly/java/org/mozilla/fenix/engine/GeckoProvider.kt index 2508f6f57..43a2dd510 100644 --- a/app/src/geckoNightly/java/org/mozilla/fenix/engine/GeckoProvider.kt +++ b/app/src/geckoNightly/java/org/mozilla/fenix/engine/GeckoProvider.kt @@ -5,7 +5,9 @@ import android.content.Context import android.os.Bundle import mozilla.components.browser.engine.gecko.autofill.GeckoLoginDelegateWrapper +import mozilla.components.browser.engine.gecko.ext.toContentBlockingSetting import mozilla.components.browser.engine.gecko.glean.GeckoAdapter +import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy import mozilla.components.concept.storage.LoginsStorage import mozilla.components.lib.crash.handler.CrashHandlerService import mozilla.components.service.sync.logins.GeckoLoginStorageDelegate @@ -21,10 +23,11 @@ object GeckoProvider { @Synchronized fun getOrCreateRuntime( context: Context, - storage: Lazy + storage: Lazy, + trackingProtectionPolicy: TrackingProtectionPolicy ): GeckoRuntime { if (runtime == null) { - runtime = createRuntime(context, storage) + runtime = createRuntime(context, storage, trackingProtectionPolicy) } return runtime!! @@ -32,7 +35,8 @@ object GeckoProvider { private fun createRuntime( context: Context, - storage: Lazy + storage: Lazy, + policy: TrackingProtectionPolicy ): GeckoRuntime { val builder = GeckoRuntimeSettings.Builder() @@ -44,6 +48,7 @@ object GeckoProvider { val runtimeSettings = builder .crashHandler(CrashHandlerService::class.java) .telemetryDelegate(GeckoAdapter()) + .contentBlocking(policy.toContentBlockingSetting()) .debugLogging(Config.channel.isDebug) .aboutConfigEnabled(true) .build() diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt index d3ec37d3a..5574844f1 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -86,7 +86,11 @@ class Core(private val context: Context) { GeckoEngine( context, defaultSettings, - GeckoProvider.getOrCreateRuntime(context, lazyPasswordsStorage) + GeckoProvider.getOrCreateRuntime( + context, + lazyPasswordsStorage, + trackingProtectionPolicyFactory.createTrackingProtectionPolicy() + ) ).also { WebCompatFeature.install(it) @@ -108,7 +112,11 @@ class Core(private val context: Context) { val client: Client by lazy { GeckoViewFetchClient( context, - GeckoProvider.getOrCreateRuntime(context, lazyPasswordsStorage) + GeckoProvider.getOrCreateRuntime( + context, + lazyPasswordsStorage, + trackingProtectionPolicyFactory.createTrackingProtectionPolicy() + ) ) }