From cc8aa1f3811ac213ba2a36e0e1e90538f8313e52 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 27 Aug 2020 16:28:37 +0200 Subject: [PATCH 01/22] For #13423: Pass isPrivate flag to ToolbarView --- .../org/mozilla/fenix/searchdialog/SearchDialogFragment.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt index a88a5f149..01327dc52 100644 --- a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt @@ -117,6 +117,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { val args by navArgs() val view = inflater.inflate(R.layout.fragment_search_dialog, container, false) val activity = requireActivity() as HomeActivity + val isPrivate = activity.browsingModeManager.mode.isPrivate requireComponents.analytics.metrics.track(Event.InteractWithSearchURLArea) @@ -149,7 +150,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { requireContext(), interactor, null, - false, + isPrivate, view.toolbar, requireComponents.core.engine ).also(::addSearchButton) @@ -174,7 +175,6 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { .findViewById(R.id.mozac_browser_toolbar_edit_url_view) urlView?.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - val isPrivate = (requireActivity() as HomeActivity).browsingModeManager.mode.isPrivate requireComponents.core.engine.speculativeCreateSession(isPrivate) return view From 4142c368201698aed5d0364caef47f7de6c692e9 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 27 Aug 2020 16:32:17 +0200 Subject: [PATCH 02/22] For #14212: Pass historyStorage to ToolbarView --- .../mozilla/fenix/searchdialog/SearchDialogFragment.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt index 01327dc52..f87bbc983 100644 --- a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt @@ -37,6 +37,7 @@ import kotlinx.android.synthetic.main.fragment_search_dialog.view.* import kotlinx.android.synthetic.main.search_suggestions_hint.view.* import kotlinx.coroutines.ExperimentalCoroutinesApi import mozilla.components.browser.toolbar.BrowserToolbar +import mozilla.components.concept.storage.HistoryStorage import mozilla.components.feature.qr.QrFeature import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.support.base.feature.UserInteractionHandler @@ -149,7 +150,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { toolbarView = ToolbarView( requireContext(), interactor, - null, + historyStorageProvider(), isPrivate, view.toolbar, requireComponents.core.engine @@ -304,6 +305,12 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { } } + private fun historyStorageProvider(): HistoryStorage? { + return if (requireContext().settings().shouldShowHistorySuggestions) { + requireComponents.core.historyStorage + } else null + } + private fun createQrFeature(): QrFeature { return QrFeature( requireContext(), From 806f74abe7da7d166e5cee340e39feef22193af3 Mon Sep 17 00:00:00 2001 From: ekager Date: Wed, 9 Sep 2020 14:58:11 -0700 Subject: [PATCH 03/22] For #14680 - Revert ObserverRegistry changes to WifiConnectionMonitor --- .../mozilla/fenix/components/Components.kt | 4 +- .../wifi/SitePermissionsWifiIntegration.kt | 32 ++++--- .../fenix/wifi/WifiConnectionMonitor.kt | 53 +++++++---- .../SitePermissionsWifiIntegrationTest.kt | 89 ------------------ .../fenix/wifi/WifiConnectionMonitorTest.kt | 91 ------------------- 5 files changed, 59 insertions(+), 210 deletions(-) delete mode 100644 app/src/test/java/org/mozilla/fenix/wifi/SitePermissionsWifiIntegrationTest.kt delete mode 100644 app/src/test/java/org/mozilla/fenix/wifi/WifiConnectionMonitorTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/components/Components.kt b/app/src/main/java/org/mozilla/fenix/components/Components.kt index 5a313401c..f6e94701b 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Components.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -4,9 +4,9 @@ package org.mozilla.fenix.components +import android.app.Application import android.content.Context import android.content.Intent -import androidx.core.content.getSystemService import androidx.core.net.toUri import mozilla.components.feature.addons.AddonManager import mozilla.components.feature.addons.amo.AddonCollectionProvider @@ -111,7 +111,7 @@ class Components(private val context: Context) { val migrationStore by lazy { MigrationStore() } val performance by lazy { PerformanceComponent() } val push by lazy { Push(context, analytics.crashReporter) } - val wifiConnectionMonitor by lazy { WifiConnectionMonitor(context.getSystemService()!!) } + val wifiConnectionMonitor by lazy { WifiConnectionMonitor(context as Application) } val settings by lazy { Settings(context) } diff --git a/app/src/main/java/org/mozilla/fenix/wifi/SitePermissionsWifiIntegration.kt b/app/src/main/java/org/mozilla/fenix/wifi/SitePermissionsWifiIntegration.kt index cee0a6a03..eec7d25e6 100644 --- a/app/src/main/java/org/mozilla/fenix/wifi/SitePermissionsWifiIntegration.kt +++ b/app/src/main/java/org/mozilla/fenix/wifi/SitePermissionsWifiIntegration.kt @@ -18,21 +18,29 @@ import org.mozilla.fenix.utils.Settings class SitePermissionsWifiIntegration( private val settings: Settings, private val wifiConnectionMonitor: WifiConnectionMonitor -) : LifecycleAwareFeature, WifiConnectionMonitor.Observer { +) : LifecycleAwareFeature { /** * Adds listener for autoplay setting [AUTOPLAY_ALLOW_ON_WIFI]. Sets all autoplay to allowed when * WIFI is connected, blocked otherwise. */ - override fun onWifiConnectionChanged(connected: Boolean) { - val setting = - if (connected) SitePermissionsRules.Action.ALLOWED else SitePermissionsRules.Action.BLOCKED - if (settings.getAutoplayUserSetting(default = AUTOPLAY_BLOCK_ALL) == AUTOPLAY_ALLOW_ON_WIFI) { - settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.AUTOPLAY_AUDIBLE, setting) - settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.AUTOPLAY_INAUDIBLE, setting) - } else { - // The autoplay setting has changed, we can remove the listener - removeWifiConnectedListener() + private val wifiConnectedListener: ((Boolean) -> Unit) by lazy { + { connected: Boolean -> + val setting = + if (connected) SitePermissionsRules.Action.ALLOWED else SitePermissionsRules.Action.BLOCKED + if (settings.getAutoplayUserSetting(default = AUTOPLAY_BLOCK_ALL) == AUTOPLAY_ALLOW_ON_WIFI) { + settings.setSitePermissionsPhoneFeatureAction( + PhoneFeature.AUTOPLAY_AUDIBLE, + setting + ) + settings.setSitePermissionsPhoneFeatureAction( + PhoneFeature.AUTOPLAY_INAUDIBLE, + setting + ) + } else { + // The autoplay setting has changed, we can remove the listener + removeWifiConnectedListener() + } } } @@ -47,11 +55,11 @@ class SitePermissionsWifiIntegration( } fun addWifiConnectedListener() { - wifiConnectionMonitor.register(this) + wifiConnectionMonitor.addOnWifiConnectedChangedListener(wifiConnectedListener) } fun removeWifiConnectedListener() { - wifiConnectionMonitor.unregister(this) + wifiConnectionMonitor.removeOnWifiConnectedChangedListener(wifiConnectedListener) } // Until https://bugzilla.mozilla.org/show_bug.cgi?id=1621825 is fixed, AUTOPLAY_ALLOW_ALL diff --git a/app/src/main/java/org/mozilla/fenix/wifi/WifiConnectionMonitor.kt b/app/src/main/java/org/mozilla/fenix/wifi/WifiConnectionMonitor.kt index b8e0c2ee9..34a6e5f7a 100644 --- a/app/src/main/java/org/mozilla/fenix/wifi/WifiConnectionMonitor.kt +++ b/app/src/main/java/org/mozilla/fenix/wifi/WifiConnectionMonitor.kt @@ -5,12 +5,11 @@ package org.mozilla.fenix.wifi import android.app.Application +import android.content.Context import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities import android.net.NetworkRequest -import mozilla.components.support.base.observer.Observable -import mozilla.components.support.base.observer.ObserverRegistry /** * Attaches itself to the [Application] and listens for WIFI available/not available events. This @@ -26,28 +25,30 @@ import mozilla.components.support.base.observer.ObserverRegistry * app.components.wifiConnectionListener.start() * ``` */ -class WifiConnectionMonitor( - private val connectivityManager: ConnectivityManager -) : Observable by ObserverRegistry() { +class WifiConnectionMonitor(app: Application) { + private val callbacks = mutableSetOf<(Boolean) -> Unit>() + private val connectivityManager = app.getSystemService(Context.CONNECTIVITY_SERVICE) as + ConnectivityManager - private var callbackReceived: Boolean = false + private var lastKnownStateWasAvailable: Boolean? = null private var isRegistered = false private val frameworkListener = object : ConnectivityManager.NetworkCallback() { override fun onLost(network: Network?) { - notifyAtLeastOneObserver { onWifiConnectionChanged(connected = false) } - callbackReceived = true + callbacks.forEach { it(false) } + lastKnownStateWasAvailable = false } override fun onAvailable(network: Network?) { - notifyAtLeastOneObserver { onWifiConnectionChanged(connected = true) } - callbackReceived = true + callbacks.forEach { it(true) } + lastKnownStateWasAvailable = true } } /** * Attaches the [WifiConnectionMonitor] to the application. After this has been called, callbacks - * added via [register] will be called until either the app exits, or [stop] is called. + * added via [addOnWifiConnectedChangedListener] will be called until either the app exits, or + * [stop] is called. * * Any existing callbacks will be called with the current state when this is called. */ @@ -61,8 +62,10 @@ class WifiConnectionMonitor( // AFAICT, the framework does not send an event when a new NetworkCallback is registered // while the WIFI is not connected, so we push this manually. If the WIFI is on, it will send // a follow up event shortly - if (!callbackReceived) { - notifyAtLeastOneObserver { onWifiConnectionChanged(connected = false) } + val noCallbacksReceivedYet = lastKnownStateWasAvailable == null + if (noCallbacksReceivedYet) { + lastKnownStateWasAvailable = false + callbacks.forEach { it(false) } } connectivityManager.registerNetworkCallback(request, frameworkListener) @@ -71,7 +74,7 @@ class WifiConnectionMonitor( /** * Detatches the [WifiConnectionMonitor] from the app. No callbacks added via - * [register] will be called after this has been called. + * [addOnWifiConnectedChangedListener] will be called after this has been called. */ fun stop() { // Framework code will throw if an unregistered listener attempts to unregister. @@ -80,7 +83,25 @@ class WifiConnectionMonitor( isRegistered = false } - interface Observer { - fun onWifiConnectionChanged(connected: Boolean) + /** + * Adds [onWifiChanged] to a list of listeners that will be called whenever WIFI connects or + * disconnects. + * + * If [onWifiChanged] is successfully added (i.e., it is a new listener), it will be immediately + * called with the last known state. + */ + fun addOnWifiConnectedChangedListener(onWifiChanged: (Boolean) -> Unit) { + val lastKnownState = lastKnownStateWasAvailable + if (callbacks.add(onWifiChanged) && lastKnownState != null) { + onWifiChanged(lastKnownState) + } + } + + /** + * Removes [onWifiChanged] from the list of listeners to be called whenever WIFI connects or + * disconnects. + */ + fun removeOnWifiConnectedChangedListener(onWifiChanged: (Boolean) -> Unit) { + callbacks.remove(onWifiChanged) } } diff --git a/app/src/test/java/org/mozilla/fenix/wifi/SitePermissionsWifiIntegrationTest.kt b/app/src/test/java/org/mozilla/fenix/wifi/SitePermissionsWifiIntegrationTest.kt deleted file mode 100644 index 77c481bf2..000000000 --- a/app/src/test/java/org/mozilla/fenix/wifi/SitePermissionsWifiIntegrationTest.kt +++ /dev/null @@ -1,89 +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.wifi - -import io.mockk.Called -import io.mockk.Runs -import io.mockk.every -import io.mockk.just -import io.mockk.mockk -import io.mockk.verify -import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action -import org.junit.Before -import org.junit.Test -import org.mozilla.fenix.settings.PhoneFeature.AUTOPLAY_AUDIBLE -import org.mozilla.fenix.settings.PhoneFeature.AUTOPLAY_INAUDIBLE -import org.mozilla.fenix.settings.sitepermissions.AUTOPLAY_ALLOW_ALL -import org.mozilla.fenix.settings.sitepermissions.AUTOPLAY_ALLOW_ON_WIFI -import org.mozilla.fenix.settings.sitepermissions.AUTOPLAY_BLOCK_ALL -import org.mozilla.fenix.utils.Settings - -class SitePermissionsWifiIntegrationTest { - - private lateinit var settings: Settings - private lateinit var wifiConnectionMonitor: WifiConnectionMonitor - private lateinit var wifiIntegration: SitePermissionsWifiIntegration - - @Before - fun setup() { - settings = mockk() - wifiConnectionMonitor = mockk(relaxed = true) - wifiIntegration = SitePermissionsWifiIntegration(settings, wifiConnectionMonitor) - - every { settings.setSitePermissionsPhoneFeatureAction(any(), any()) } just Runs - } - - @Test - fun `add and remove wifi connected listener`() { - wifiIntegration.addWifiConnectedListener() - verify { wifiConnectionMonitor.register(any()) } - - wifiIntegration.removeWifiConnectedListener() - verify { wifiConnectionMonitor.unregister(any()) } - } - - @Test - fun `start and stop wifi connection monitor`() { - wifiIntegration.start() - verify { wifiConnectionMonitor.start() } - - wifiIntegration.stop() - verify { wifiConnectionMonitor.stop() } - } - - @Test - fun `add only if autoplay is only allowed on wifi`() { - every { settings.getAutoplayUserSetting(default = AUTOPLAY_BLOCK_ALL) } returns AUTOPLAY_ALLOW_ALL - wifiIntegration.maybeAddWifiConnectedListener() - verify { wifiConnectionMonitor wasNot Called } - - every { settings.getAutoplayUserSetting(default = AUTOPLAY_BLOCK_ALL) } returns AUTOPLAY_ALLOW_ON_WIFI - wifiIntegration.maybeAddWifiConnectedListener() - verify { wifiConnectionMonitor.register(any()) } - } - - @Test - fun `listener removes itself if autoplay is not only allowed on wifi`() { - every { settings.getAutoplayUserSetting(default = AUTOPLAY_BLOCK_ALL) } returns AUTOPLAY_ALLOW_ALL - wifiIntegration.onWifiConnectionChanged(connected = true) - verify { wifiConnectionMonitor.unregister(any()) } - } - - @Test - fun `listener sets audible and inaudible settings to allowed on connect`() { - every { settings.getAutoplayUserSetting(default = AUTOPLAY_BLOCK_ALL) } returns AUTOPLAY_ALLOW_ON_WIFI - wifiIntegration.onWifiConnectionChanged(connected = true) - verify { settings.setSitePermissionsPhoneFeatureAction(AUTOPLAY_AUDIBLE, Action.ALLOWED) } - verify { settings.setSitePermissionsPhoneFeatureAction(AUTOPLAY_INAUDIBLE, Action.ALLOWED) } - } - - @Test - fun `listener sets audible and inaudible settings to blocked on disconnected`() { - every { settings.getAutoplayUserSetting(default = AUTOPLAY_BLOCK_ALL) } returns AUTOPLAY_ALLOW_ON_WIFI - wifiIntegration.onWifiConnectionChanged(connected = false) - verify { settings.setSitePermissionsPhoneFeatureAction(AUTOPLAY_AUDIBLE, Action.BLOCKED) } - verify { settings.setSitePermissionsPhoneFeatureAction(AUTOPLAY_INAUDIBLE, Action.BLOCKED) } - } -} diff --git a/app/src/test/java/org/mozilla/fenix/wifi/WifiConnectionMonitorTest.kt b/app/src/test/java/org/mozilla/fenix/wifi/WifiConnectionMonitorTest.kt deleted file mode 100644 index 94b8a7f10..000000000 --- a/app/src/test/java/org/mozilla/fenix/wifi/WifiConnectionMonitorTest.kt +++ /dev/null @@ -1,91 +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.wifi - -import android.net.ConnectivityManager -import android.net.NetworkRequest -import io.mockk.Runs -import io.mockk.every -import io.mockk.just -import io.mockk.mockk -import io.mockk.mockkConstructor -import io.mockk.slot -import io.mockk.unmockkConstructor -import io.mockk.verify -import org.junit.After -import org.junit.Before -import org.junit.Test - -class WifiConnectionMonitorTest { - - private lateinit var connectivityManager: ConnectivityManager - private lateinit var wifiConnectionMonitor: WifiConnectionMonitor - - @Before - fun setup() { - mockkConstructor(NetworkRequest.Builder::class) - connectivityManager = mockk(relaxUnitFun = true) - wifiConnectionMonitor = WifiConnectionMonitor(connectivityManager) - - every { - anyConstructed().addTransportType(any()) - } answers { self as NetworkRequest.Builder } - } - - @After - fun teardown() { - unmockkConstructor(NetworkRequest.Builder::class) - } - - @Test - fun `start runs only once`() { - wifiConnectionMonitor.start() - wifiConnectionMonitor.start() - - verify(exactly = 1) { - connectivityManager.registerNetworkCallback(any(), any()) - } - } - - @Test - fun `stop only runs after start`() { - wifiConnectionMonitor.stop() - verify(exactly = 0) { - connectivityManager.unregisterNetworkCallback(any()) - } - - wifiConnectionMonitor.start() - wifiConnectionMonitor.stop() - verify { - connectivityManager.unregisterNetworkCallback(any()) - } - } - - @Test - fun `passes results from connectivity manager to observers`() { - val slot = slot() - every { connectivityManager.registerNetworkCallback(any(), capture(slot)) } just Runs - - wifiConnectionMonitor.start() - - // Immediately notifies observer when registered - val observer = mockk(relaxed = true) - wifiConnectionMonitor.register(observer) - verify { observer.onWifiConnectionChanged(connected = false) } - - // Notifies observer when network is available or lost - slot.captured.onAvailable(mockk()) - verify { observer.onWifiConnectionChanged(connected = true) } - - slot.captured.onLost(mockk()) - verify { observer.onWifiConnectionChanged(connected = false) } - } - - private fun captureNetworkCallback(): ConnectivityManager.NetworkCallback { - val slot = slot() - verify { connectivityManager.registerNetworkCallback(any(), capture(slot)) } - return slot.captured - } -} From 82d7313add8c91a86a9d5236e709ac7ac188e56f Mon Sep 17 00:00:00 2001 From: ekager Date: Wed, 9 Sep 2020 14:39:04 -0700 Subject: [PATCH 04/22] For #13467 For #14910 - Dismiss dialog on blank URL commit --- .../fenix/searchdialog/SearchDialogController.kt | 6 +++++- .../fenix/searchdialog/SearchDialogFragment.kt | 1 + .../searchdialog/SearchDialogControllerTest.kt | 13 +++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogController.kt b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogController.kt index c4e81c041..8f210124a 100644 --- a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogController.kt +++ b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogController.kt @@ -33,6 +33,7 @@ class SearchDialogController( private val navController: NavController, private val settings: Settings, private val metrics: MetricController, + private val dismissDialog: () -> Unit, private val clearToolbarFocus: () -> Unit ) : SearchController { @@ -45,12 +46,15 @@ class SearchDialogController( activity.startActivity(Intent(activity, CrashListActivity::class.java)) } "about:addons" -> { - val directions = SearchDialogFragmentDirections.actionGlobalAddonsManagementFragment() + val directions = + SearchDialogFragmentDirections.actionGlobalAddonsManagementFragment() navController.navigateSafe(R.id.searchDialogFragment, directions) } "moz://a" -> openSearchOrUrl(SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.MANIFESTO)) else -> if (url.isNotBlank()) { openSearchOrUrl(url) + } else { + dismissDialog() } } } diff --git a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt index f87bbc983..9a1d1d0b8 100644 --- a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt @@ -140,6 +140,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { navController = findNavController(), settings = requireContext().settings(), metrics = requireComponents.analytics.metrics, + dismissDialog = { dismissAllowingStateLoss() }, clearToolbarFocus = { toolbarView.view.hideKeyboard() toolbarView.view.clearFocus() diff --git a/app/src/test/java/org/mozilla/fenix/searchdialog/SearchDialogControllerTest.kt b/app/src/test/java/org/mozilla/fenix/searchdialog/SearchDialogControllerTest.kt index 762f5e866..0aaed6fe8 100644 --- a/app/src/test/java/org/mozilla/fenix/searchdialog/SearchDialogControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/searchdialog/SearchDialogControllerTest.kt @@ -44,6 +44,7 @@ class SearchDialogControllerTest { @MockK(relaxed = true) private lateinit var settings: Settings @MockK private lateinit var sessionManager: SessionManager @MockK(relaxed = true) private lateinit var clearToolbarFocus: () -> Unit + @MockK(relaxed = true) private lateinit var dismissDialog: () -> Unit private lateinit var controller: SearchDialogController @@ -67,6 +68,7 @@ class SearchDialogControllerTest { navController = navController, settings = settings, metrics = metrics, + dismissDialog = dismissDialog, clearToolbarFocus = clearToolbarFocus ) } @@ -93,6 +95,17 @@ class SearchDialogControllerTest { verify { metrics.track(Event.EnteredUrl(false)) } } + @Test + fun handleBlankUrlCommitted() { + val url = "" + + controller.handleUrlCommitted(url) + + verify { + dismissDialog() + } + } + @Test fun handleSearchCommitted() { val searchTerm = "Firefox" From 2e3863578187d7fce4ca8e90b26ace165c1ea15c Mon Sep 17 00:00:00 2001 From: Oana Horvath Date: Thu, 10 Sep 2020 15:17:47 +0300 Subject: [PATCH 05/22] For #13829: Disables verifyAddonsCanBeUninstalled UI test --- .../androidTest/java/org/mozilla/fenix/ui/SettingsAddonsTest.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAddonsTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAddonsTest.kt index 4c6f93a8a..036273f24 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAddonsTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAddonsTest.kt @@ -11,6 +11,7 @@ import okhttp3.mockwebserver.MockWebServer import org.junit.Rule import org.junit.Before import org.junit.After +import org.junit.Ignore import org.junit.Test import org.mozilla.fenix.R import org.mozilla.fenix.helpers.AndroidAssetDispatcher @@ -88,6 +89,7 @@ class SettingsAddonsTest { } } + @Ignore("Failing intermittently on Firebase: https://github.com/mozilla-mobile/fenix/issues/13829") // Opens the addons settings menu, installs an addon, then uninstalls @Test fun verifyAddonsCanBeUninstalled() { From a6a50d78e039c4a26fa13b2fefe3b884fdfc44c1 Mon Sep 17 00:00:00 2001 From: Mugurell Date: Thu, 10 Sep 2020 15:44:18 +0300 Subject: [PATCH 06/22] For #14903 - Prevent BrowserFragment's view leak --- .../mozilla/fenix/browser/BrowserFragment.kt | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt index 56964573b..11310a2fe 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -54,6 +54,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { private val windowFeature = ViewBoundFeatureWrapper() private var readerModeAvailable = false + private var openInAppOnboardingObserver: OpenInAppOnboardingObserver? = null override fun onCreateView( inflater: LayoutInflater, @@ -159,15 +160,16 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { } session?.register(toolbarSessionObserver, viewLifecycleOwner, autoPause = true) - if (settings.shouldShowOpenInAppBanner) { - session?.register( - OpenInAppOnboardingObserver( - context = context, - navController = findNavController(), - settings = settings, - appLinksUseCases = context.components.useCases.appLinksUseCases, - container = browserToolbarView.view.parent as ViewGroup - ), + if (settings.shouldShowOpenInAppBanner && session != null) { + openInAppOnboardingObserver = OpenInAppOnboardingObserver( + context = context, + navController = findNavController(), + settings = settings, + appLinksUseCases = context.components.useCases.appLinksUseCases, + container = browserToolbarView.view.parent as ViewGroup + ) + session.register( + openInAppOnboardingObserver!!, owner = this, autoPause = true ) @@ -188,6 +190,16 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { subscribeToTabCollections() } + override fun onStop() { + super.onStop() + // This observer initialized in onStart has a reference to fragment's view. + // Prevent it leaking the view after the latter onDestroyView. + if (openInAppOnboardingObserver != null) { + getSessionById()?.unregister(openInAppOnboardingObserver!!) + openInAppOnboardingObserver = null + } + } + private fun subscribeToTabCollections() { Observer> { requireComponents.core.tabCollectionStorage.cachedTabCollections = it From 1a6bb95aec2e3bca7a57e71ad2b5d0fef84ce2a8 Mon Sep 17 00:00:00 2001 From: liuche Date: Thu, 10 Sep 2020 09:01:51 -0700 Subject: [PATCH 07/22] Port l10n uplift script from AC (#14904) * Issue #14855 - Port L10N uplift script from AC, author grisha * Issue #14855 - Additional usage note for l10n-uplift --- l10n-uplift.py | 101 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100755 l10n-uplift.py diff --git a/l10n-uplift.py b/l10n-uplift.py new file mode 100755 index 000000000..8b890d70a --- /dev/null +++ b/l10n-uplift.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 + +# Purpose: uplift (via cherry-picking) any missing commits from an l10n bot +# from 'MAIN_BRANCH' to a specified release branch. +# +# Usage examples: (append --verbose to print out detailed information) +# Dry-run (says what will happen, doesn't do any work): ./l10n-uplift.py releases/48.0 +# Uplift, actually perform the work: ./l10n-uplift.py releases/48.0 --uplift +# Process multiple branches at once: ./l10n-uplift.py releases/48.0 releases/44.0 --uplift --verbose + +# Note: there can often be conflicts between cherry-picks, to catch duplication errors, build after conflict resolution: ./gradlew assembleDebug + +import subprocess +import argparse + +# TODO don't forget to change this once we switch to 'main' or whatever other name. +MAIN_BRANCH="master" +L10N_AUTHOR="release+l10n-automation-bot@mozilla.com" + +def run_cmd_checked(*args, **kwargs): + """Run a command, throwing an exception if it exits with non-zero status.""" + kwargs["check"] = True + kwargs["capture_output"] = True + # beware! only run this script with inputs from a trusted, non-external source + kwargs["shell"] = True + try: + return subprocess.run(*args, **kwargs).stdout.decode() + except subprocess.CalledProcessError as err: + print(err.stderr) + raise err + +def uplift_commits(branch, verbose, uplift): + print(f"\nProcessing l10n commits for '{branch}'...") + # if necessary, this will setup 'branch' to track its upstream equivalent + run_cmd_checked([f"git checkout {branch}"]) + # get l10n commits which happened on MAIN_BRANCH since 'branch' split off + commits_since_split = run_cmd_checked([f"git rev-list {branch}..{MAIN_BRANCH} --author={L10N_AUTHOR}"]).split() + # order commits by oldest-first, e.g. how we'd cherry pick them + commits_since_split.reverse() + print(f"Since '{branch}' split off '{MAIN_BRANCH}', there were {len(commits_since_split)} commit(s) from {L10N_AUTHOR}.") + + if verbose: + print(f"\nHashes of those commits on '{MAIN_BRANCH}' are: {commits_since_split}\n") + + # look for 'cherry picked' commits, and get the original commit hash from the commit message (as left by 'cherry-pick -x') + commits_already_uplifted = run_cmd_checked([f"git rev-list {MAIN_BRANCH}..{branch} --author={L10N_AUTHOR} --grep=\"cherry picked\" --pretty=%b | grep cherry | cut -d' ' -f5 | cut -c 1-40"]).split() + commits_already_uplifted.reverse() + + print(f"Of those, {len(commits_already_uplifted)} commit(s) already uplifted.") + + if verbose: + print(f"Hashes of commits already uplifted to '{branch}': {commits_already_uplifted}\n") + + commits_to_uplift = [commit for commit in commits_since_split if commit not in commits_already_uplifted] + + print(f"Need to uplift {len(commits_to_uplift)} commit(s).") + + if verbose: + print(f"Hashes of commits to uplift from '{MAIN_BRANCH}' to '{branch}': {commits_to_uplift}\n") + + if len(commits_to_uplift) == 0: + print("Nothing to uplift.") + return + + if uplift: + print(f"Uplifting (for real)...") + else: + print(f"Uplifting (dry-run)...") + + run_cmd_checked([f"git checkout {branch}"]) + for commit in commits_to_uplift: + if verbose: + print(f"Cherry picking {commit} from '{MAIN_BRANCH}' to '{branch}'") + if uplift: + run_cmd_checked([f"git cherry-pick {commit} -x"]) + if uplift: + print(f"Uplifted {len(commits_to_uplift)} commits from '{MAIN_BRANCH}' to '{branch}'") + +parser = argparse.ArgumentParser(description=f"Uplift l10n commits from {MAIN_BRANCH} to specified branches") +parser.add_argument( + 'branches', nargs='+', type=str, + help='target branches, e.g. specific release branches') +parser.add_argument( + '--verbose', default=False, action='store_true', + help='print out commit hashes and other detailed information' +) +parser.add_argument( + '--uplift', default=False, action='store_true', + help='uplift l10n commits missing from specified branches (if not specified, dry-run is performed)' +) +args = parser.parse_args() + +# remember the current branch, so that we can return to it once we're done. +current_branch = run_cmd_checked(["git rev-parse --abbrev-ref HEAD"]) + +try: + for branch in args.branches: + uplift_commits(branch, args.verbose, args.uplift) +finally: + # go back to the branch we were on before 'uplift_for_branches' ran + run_cmd_checked([f"git checkout {current_branch}"]) From b0729f655ce5c9b38d86a59e8aa93666204762c3 Mon Sep 17 00:00:00 2001 From: Abhijit Kiran Valluri Date: Thu, 10 Sep 2020 19:53:05 -0400 Subject: [PATCH 08/22] Fix #14950 - Make the "Add new tab" button visible in private mode on light theme (#14951) --- app/src/main/res/layout/component_tabstray.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/layout/component_tabstray.xml b/app/src/main/res/layout/component_tabstray.xml index ca2689b4a..9bd393734 100644 --- a/app/src/main/res/layout/component_tabstray.xml +++ b/app/src/main/res/layout/component_tabstray.xml @@ -139,6 +139,7 @@ app:layout_constraintBottom_toBottomOf="@id/tab_layout" app:layout_constraintEnd_toStartOf="@id/tab_tray_overflow" app:layout_constraintTop_toTopOf="@id/tab_layout" + app:tint="@color/primary_text_normal_theme" app:srcCompat="@drawable/ic_new" /> Date: Thu, 10 Sep 2020 19:09:38 -0500 Subject: [PATCH 09/22] For #14239: Notification for QR scan when permissions have been denied (#14553) * Show dialog when permissions are denied * Add qr permissions dialog to search dialog fragment * Add qr permissions dialog to the pairing screen * Show dialog after permissions have been denied * Reset focus after denying permissions * Show dialog after permissions denied in search frag and par frag * Use shared preferences to store camera permission state * Move dialog creation into the search controller and add tests * Dialog controller implementation and test * Route to intent with correct activity. Set focus when dismissing dialog * Get preferences in old search --- .../mozilla/fenix/search/SearchController.kt | 55 ++++++++++++++ .../mozilla/fenix/search/SearchFragment.kt | 39 ++++++++-- .../mozilla/fenix/search/SearchInteractor.kt | 5 ++ .../searchdialog/SearchDialogController.kt | 52 +++++++++++++ .../searchdialog/SearchDialogFragment.kt | 76 ++++++++++++++++--- .../mozilla/fenix/settings/PairFragment.kt | 64 +++++++++++++++- .../mozilla/fenix/settings/SupportUtils.kt | 3 +- app/src/main/res/values/preference_keys.xml | 2 + app/src/main/res/values/strings.xml | 2 +- .../search/DefaultSearchControllerTest.kt | 17 ++++- .../SearchDialogControllerTest.kt | 14 ++++ 11 files changed, 310 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchController.kt b/app/src/main/java/org/mozilla/fenix/search/SearchController.kt index 8e668c7ea..058bf715b 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchController.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchController.kt @@ -4,7 +4,13 @@ package org.mozilla.fenix.search +import android.content.DialogInterface import android.content.Intent +import android.net.Uri +import android.os.Build +import android.text.SpannableString +import androidx.annotation.VisibleForTesting +import androidx.appcompat.app.AlertDialog import androidx.navigation.NavController import mozilla.components.browser.search.SearchEngine import mozilla.components.browser.session.Session @@ -29,6 +35,7 @@ import org.mozilla.fenix.utils.Settings /** * An interface that handles the view manipulation of the Search, triggered by the Interactor */ +@Suppress("TooManyFunctions") interface SearchController { fun handleUrlCommitted(url: String) fun handleEditingCancelled() @@ -40,6 +47,7 @@ interface SearchController { fun handleExistingSessionSelected(session: Session) fun handleExistingSessionSelected(tabId: String) fun handleSearchShortcutsButtonClicked() + fun handleCameraPermissionsNeeded() } @Suppress("TooManyFunctions", "LongParameterList") @@ -194,4 +202,51 @@ class DefaultSearchController( handleExistingSessionSelected(session) } } + + /** + * Creates and shows an [AlertDialog] when camera permissions are needed. + * + * In versions above M, [AlertDialog.BUTTON_POSITIVE] takes the user to the app settings. This + * intent only exists in M and above. Below M, [AlertDialog.BUTTON_POSITIVE] routes to a SUMO + * help page to find the app settings. + * + * [AlertDialog.BUTTON_NEGATIVE] dismisses the dialog. + */ + override fun handleCameraPermissionsNeeded() { + val dialog = buildDialog() + dialog.show() + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + fun buildDialog(): AlertDialog.Builder { + return AlertDialog.Builder(activity).apply { + val spannableText = SpannableString( + activity.resources.getString(R.string.camera_permissions_needed_message) + ) + setMessage(spannableText) + setNegativeButton(R.string.camera_permissions_needed_negative_button_text) { + dialog: DialogInterface, _ -> + dialog.cancel() + } + setPositiveButton(R.string.camera_permissions_needed_positive_button_text) { + dialog: DialogInterface, _ -> + val intent: Intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + } else { + SupportUtils.createCustomTabIntent( + activity, + SupportUtils.getSumoURLForTopic( + activity, + SupportUtils.SumoTopic.QR_CAMERA_ACCESS + ) + ) + } + val uri = Uri.fromParts("package", activity.packageName, null) + intent.data = uri + dialog.cancel() + activity.startActivity(intent) + } + create() + } + } } diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt index 01877979e..0da68c01d 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt @@ -26,6 +26,7 @@ import androidx.core.widget.NestedScrollView import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs +import androidx.preference.PreferenceManager import kotlinx.android.synthetic.main.fragment_search.* import kotlinx.android.synthetic.main.fragment_search.view.* import kotlinx.android.synthetic.main.search_suggestions_hint.view.* @@ -50,6 +51,7 @@ import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.ext.hideToolbar import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings @@ -219,6 +221,7 @@ class SearchFragment : Fragment(), UserInteractionHandler { setNegativeButton(R.string.qr_scanner_dialog_negative) { dialog: DialogInterface, _ -> requireComponents.analytics.metrics.track(Event.QRScannerNavigationDenied) dialog.cancel() + resetFocus() } setPositiveButton(R.string.qr_scanner_dialog_positive) { dialog: DialogInterface, _ -> requireComponents.analytics.metrics.track(Event.QRScannerNavigationAllowed) @@ -229,6 +232,7 @@ class SearchFragment : Fragment(), UserInteractionHandler { from = BrowserDirection.FromSearch ) dialog.dismiss() + resetFocus() } create() }.show() @@ -241,8 +245,19 @@ class SearchFragment : Fragment(), UserInteractionHandler { view.search_scan_button.setOnClickListener { toolbarView.view.clearFocus() - requireComponents.analytics.metrics.track(Event.QRScannerOpened) - qrFeature.get()?.scan(R.id.container) + + val cameraPermissionsDenied = PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean( + getPreferenceKey(R.string.pref_key_camera_permissions), + false + ) + + if (cameraPermissionsDenied) { + searchInteractor.onCameraPermissionsNeeded() + } else { + requireComponents.analytics.metrics.track(Event.QRScannerOpened) + qrFeature.get()?.scan(R.id.container) + } } view.search_engines_shortcut_button.setOnClickListener { @@ -368,15 +383,19 @@ class SearchFragment : Fragment(), UserInteractionHandler { override fun onBackPressed(): Boolean { return when { qrFeature.onBackPressed() -> { - toolbarView.view.edit.focus() - view?.search_scan_button?.isChecked = false - toolbarView.view.requestFocus() + resetFocus() true } else -> false } } + private fun resetFocus() { + search_scan_button.isChecked = false + toolbarView.view.edit.focus() + toolbarView.view.requestFocus() + } + private fun updateSearchWithLabel(searchState: SearchFragmentState) { search_engine_shortcut.visibility = if (searchState.showSearchShortcuts) View.VISIBLE else View.GONE @@ -408,8 +427,16 @@ class SearchFragment : Fragment(), UserInteractionHandler { context?.let { context: Context -> if (context.isPermissionGranted(Manifest.permission.CAMERA)) { permissionDidUpdate = true + PreferenceManager.getDefaultSharedPreferences(context) + .edit().putBoolean( + getPreferenceKey(R.string.pref_key_camera_permissions), false + ).apply() } else { - view?.search_scan_button?.isChecked = false + PreferenceManager.getDefaultSharedPreferences(context) + .edit().putBoolean( + getPreferenceKey(R.string.pref_key_camera_permissions), true + ).apply() + resetFocus() } } } diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchInteractor.kt b/app/src/main/java/org/mozilla/fenix/search/SearchInteractor.kt index dc4080aa4..823945453 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchInteractor.kt @@ -13,6 +13,7 @@ import org.mozilla.fenix.search.toolbar.ToolbarInteractor * Interactor for the search screen * Provides implementations for the AwesomeBarView and ToolbarView */ +@Suppress("TooManyFunctions") class SearchInteractor( private val searchController: SearchController ) : AwesomeBarInteractor, ToolbarInteractor { @@ -56,4 +57,8 @@ class SearchInteractor( override fun onExistingSessionSelected(tabId: String) { searchController.handleExistingSessionSelected(tabId) } + + fun onCameraPermissionsNeeded() { + searchController.handleCameraPermissionsNeeded() + } } diff --git a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogController.kt b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogController.kt index 8f210124a..476a7d615 100644 --- a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogController.kt +++ b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogController.kt @@ -4,7 +4,13 @@ package org.mozilla.fenix.searchdialog +import android.content.DialogInterface import android.content.Intent +import android.net.Uri +import android.os.Build +import android.text.SpannableString +import androidx.annotation.VisibleForTesting +import androidx.appcompat.app.AlertDialog import androidx.navigation.NavController import mozilla.components.browser.search.SearchEngine import mozilla.components.browser.session.Session @@ -186,4 +192,50 @@ class SearchDialogController( handleExistingSessionSelected(session) } } + + /** + * Creates and shows an [AlertDialog] when camera permissions are needed. + * + * In versions above M, [AlertDialog.BUTTON_POSITIVE] takes the user to the app settings. This + * intent only exists in M and above. Below M, [AlertDialog.BUTTON_POSITIVE] routes to a SUMO + * help page to find the app settings. + * + * [AlertDialog.BUTTON_NEGATIVE] dismisses the dialog. + */ + override fun handleCameraPermissionsNeeded() { + val dialog = buildDialog() + dialog.show() + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + fun buildDialog(): AlertDialog.Builder { + return AlertDialog.Builder(activity).apply { + val spannableText = SpannableString( + activity.resources.getString(R.string.camera_permissions_needed_message) + ) + setMessage(spannableText) + setNegativeButton(R.string.camera_permissions_needed_negative_button_text) { _, _ -> + dismissDialog() + } + setPositiveButton(R.string.camera_permissions_needed_positive_button_text) { + dialog: DialogInterface, _ -> + val intent: Intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + } else { + SupportUtils.createCustomTabIntent( + activity, + SupportUtils.getSumoURLForTopic( + activity, + SupportUtils.SumoTopic.QR_CAMERA_ACCESS + ) + ) + } + val uri = Uri.fromParts("package", activity.packageName, null) + intent.data = uri + dialog.cancel() + activity.startActivity(intent) + } + create() + } + } } diff --git a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt index 9a1d1d0b8..b5c7e9a88 100644 --- a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.searchdialog +import android.Manifest import android.app.Activity import android.app.Dialog import android.content.Context @@ -28,11 +29,8 @@ import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs +import androidx.preference.PreferenceManager import kotlinx.android.synthetic.main.fragment_search_dialog.* -import kotlinx.android.synthetic.main.fragment_search_dialog.fill_link_from_clipboard -import kotlinx.android.synthetic.main.fragment_search_dialog.pill_wrapper -import kotlinx.android.synthetic.main.fragment_search_dialog.qr_scan_button -import kotlinx.android.synthetic.main.fragment_search_dialog.toolbar import kotlinx.android.synthetic.main.fragment_search_dialog.view.* import kotlinx.android.synthetic.main.search_suggestions_hint.view.* import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -44,6 +42,7 @@ import mozilla.components.support.base.feature.UserInteractionHandler import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.support.ktx.android.content.getColorFromAttr import mozilla.components.support.ktx.android.content.hasCamera +import mozilla.components.support.ktx.android.content.isPermissionGranted import mozilla.components.support.ktx.android.content.res.getSpanned import mozilla.components.support.ktx.android.view.hideKeyboard import mozilla.components.ui.autocomplete.InlineAutocompleteEditText @@ -55,6 +54,7 @@ import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings import org.mozilla.fenix.search.SearchFragmentAction @@ -204,8 +204,22 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { if (!requireContext().hasCamera()) { return@setOnClickListener } toolbarView.view.clearFocus() - requireComponents.analytics.metrics.track(Event.QRScannerOpened) - qrFeature.get()?.scan(R.id.search_wrapper) + + val cameraPermissionsDenied = + PreferenceManager.getDefaultSharedPreferences(context).getBoolean( + getPreferenceKey(R.string.pref_key_camera_permissions), + false + ) + + if (cameraPermissionsDenied) { + interactor.onCameraPermissionsNeeded() + resetFocus() + view.hideKeyboard() + toolbarView.view.requestFocus() + } else { + requireComponents.analytics.metrics.track(Event.QRScannerOpened) + qrFeature.get()?.scan(R.id.search_wrapper) + } } fill_link_from_clipboard.setOnClickListener { @@ -280,6 +294,19 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { } } + override fun onResume() { + super.onResume() + resetFocus() + toolbarView.view.edit.focus() + } + + override fun onPause() { + super.onPause() + qr_scan_button.isChecked = false + view?.hideKeyboard() + toolbarView.view.requestFocus() + } + override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { if (requestCode == VoiceSearchActivity.SPEECH_REQUEST_CODE && resultCode == Activity.RESULT_OK) { intent?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.first()?.also { @@ -293,9 +320,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { override fun onBackPressed(): Boolean { return when { qrFeature.onBackPressed() -> { - toolbarView.view.edit.focus() - view?.qr_scan_button?.isChecked = false - toolbarView.view.requestFocus() + resetFocus() true } else -> { @@ -350,6 +375,39 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { }) } + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + when (requestCode) { + REQUEST_CODE_CAMERA_PERMISSIONS -> qrFeature.withFeature { + context?.let { context: Context -> + it.onPermissionsResult(permissions, grantResults) + if (!context.isPermissionGranted(Manifest.permission.CAMERA)) { + PreferenceManager.getDefaultSharedPreferences(context) + .edit().putBoolean( + getPreferenceKey(R.string.pref_key_camera_permissions), true + ).apply() + resetFocus() + } else { + PreferenceManager.getDefaultSharedPreferences(context) + .edit().putBoolean( + getPreferenceKey(R.string.pref_key_camera_permissions), false + ).apply() + } + } + } + else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults) + } + } + + private fun resetFocus() { + qr_scan_button.isChecked = false + toolbarView.view.edit.focus() + toolbarView.view.requestFocus() + } + private fun setupConstraints(view: View) { if (view.context.settings().toolbarPosition == ToolbarPosition.BOTTOM) { ConstraintSet().apply { diff --git a/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt index b1127f2ce..b7a20951c 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt @@ -4,27 +4,35 @@ package org.mozilla.fenix.settings +import android.content.DialogInterface +import android.content.Intent import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.os.VibrationEffect import android.os.Vibrator +import android.provider.Settings +import android.text.SpannableString import android.view.View +import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import androidx.fragment.app.Fragment import androidx.navigation.fragment.NavHostFragment.findNavController import androidx.navigation.fragment.findNavController +import androidx.preference.PreferenceManager import mozilla.components.feature.qr.QrFeature import mozilla.components.support.base.feature.UserInteractionHandler import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import org.mozilla.fenix.R +import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.showToolbar class PairFragment : Fragment(R.layout.fragment_pair), UserInteractionHandler { private val qrFeature = ViewBoundFeatureWrapper() + private val preferences = PreferenceManager.getDefaultSharedPreferences(context) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -63,8 +71,17 @@ class PairFragment : Fragment(R.layout.fragment_pair), UserInteractionHandler { view = view ) + val cameraPermissionsDenied = preferences.getBoolean( + getPreferenceKey(R.string.pref_key_camera_permissions), + false + ) + qrFeature.withFeature { - it.scan(R.id.pair_layout) + if (cameraPermissionsDenied) { + showPermissionsNeededDialog() + } else { + it.scan(R.id.pair_layout) + } } } @@ -99,10 +116,55 @@ class PairFragment : Fragment(R.layout.fragment_pair), UserInteractionHandler { qrFeature.withFeature { it.onPermissionsResult(permissions, grantResults) } + preferences.edit().putBoolean( + getPreferenceKey(R.string.pref_key_camera_permissions), false + ).apply() } else { + preferences.edit().putBoolean( + getPreferenceKey(R.string.pref_key_camera_permissions), true + ).apply() findNavController().popBackStack(R.id.turnOnSyncFragment, false) } } } } + + /** + * Shows an [AlertDialog] when camera permissions are needed. + * + * In versions above M, [AlertDialog.BUTTON_POSITIVE] takes the user to the app settings. This + * intent only exists in M and above. Below M, [AlertDialog.BUTTON_POSITIVE] routes to a SUMO + * help page to find the app settings. + * + * [AlertDialog.BUTTON_NEGATIVE] dismisses the dialog. + */ + private fun showPermissionsNeededDialog() { + AlertDialog.Builder(requireContext()).apply { + val spannableText = SpannableString( + resources.getString(R.string.camera_permissions_needed_message) + ) + setMessage(spannableText) + setNegativeButton(R.string.camera_permissions_needed_negative_button_text) { + dialog: DialogInterface, _ -> + dialog.cancel() + } + setPositiveButton(R.string.camera_permissions_needed_positive_button_text) { + dialog: DialogInterface, _ -> + val intent: Intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + } else { + SupportUtils.createCustomTabIntent( + requireContext(), + SupportUtils.getSumoURLForTopic( + requireContext(), + SupportUtils.SumoTopic.QR_CAMERA_ACCESS + ) + ) + } + dialog.cancel() + startActivity(intent) + } + create() + }.show() + } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt b/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt index 3e1bae04a..07fef7c93 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt @@ -40,7 +40,8 @@ object SupportUtils { SEARCH_SUGGESTION("how-search-firefox-preview"), CUSTOM_SEARCH_ENGINES("custom-search-engines"), UPGRADE_FAQ("firefox-preview-upgrade-faqs"), - SYNC_SETUP("how-set-firefox-sync-firefox-preview") + SYNC_SETUP("how-set-firefox-sync-firefox-preview"), + QR_CAMERA_ACCESS("qr-camera-access") } enum class MozillaPage(internal val path: String) { diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index cb27836a4..98aa99d53 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -222,4 +222,6 @@ pref_key_close_tabs_after_one_day pref_key_close_tabs_after_one_week pref_key_close_tabs_after_one_month + + pref_key_camera_permissions diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 433d1d99f..abd5168b8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1543,7 +1543,7 @@ Remove - Get the most out of %s. diff --git a/app/src/test/java/org/mozilla/fenix/search/DefaultSearchControllerTest.kt b/app/src/test/java/org/mozilla/fenix/search/DefaultSearchControllerTest.kt index d6a223342..52b8dbbf0 100644 --- a/app/src/test/java/org/mozilla/fenix/search/DefaultSearchControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/search/DefaultSearchControllerTest.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.search +import androidx.appcompat.app.AlertDialog import androidx.navigation.NavController import androidx.navigation.NavDirections import io.mockk.MockKAnnotations @@ -13,6 +14,7 @@ import io.mockk.impl.annotations.MockK import io.mockk.just import io.mockk.mockk import io.mockk.mockkObject +import io.mockk.spyk import io.mockk.unmockkObject import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -32,6 +34,8 @@ import org.mozilla.fenix.components.metrics.MetricsUtils import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.utils.Settings +typealias AlertDialogBuilder = AlertDialog.Builder + @ExperimentalCoroutinesApi class DefaultSearchControllerTest { @@ -58,7 +62,6 @@ class DefaultSearchControllerTest { every { id } returns R.id.searchFragment } every { MetricsUtils.createSearchEvent(searchEngine, activity, any()) } returns null - controller = DefaultSearchController( activity = activity, sessionManager = sessionManager, @@ -328,4 +331,16 @@ class DefaultSearchControllerTest { verify { sessionManager.select(any()) } verify { activity.openToBrowser(from = BrowserDirection.FromSearch) } } + + @Test + fun `show camera permissions needed dialog`() { + val dialogBuilder: AlertDialogBuilder = mockk(relaxed = true) + + val spyController = spyk(controller) + every { spyController.buildDialog() } returns dialogBuilder + + spyController.handleCameraPermissionsNeeded() + + verify { dialogBuilder.show() } + } } diff --git a/app/src/test/java/org/mozilla/fenix/searchdialog/SearchDialogControllerTest.kt b/app/src/test/java/org/mozilla/fenix/searchdialog/SearchDialogControllerTest.kt index 0aaed6fe8..d30fde735 100644 --- a/app/src/test/java/org/mozilla/fenix/searchdialog/SearchDialogControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/searchdialog/SearchDialogControllerTest.kt @@ -13,6 +13,7 @@ import io.mockk.impl.annotations.MockK import io.mockk.just import io.mockk.mockk import io.mockk.mockkObject +import io.mockk.spyk import io.mockk.unmockkObject import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -29,6 +30,7 @@ import org.mozilla.fenix.R import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.components.metrics.MetricsUtils +import org.mozilla.fenix.search.AlertDialogBuilder import org.mozilla.fenix.search.SearchFragmentAction import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.utils.Settings @@ -342,4 +344,16 @@ class SearchDialogControllerTest { verify { sessionManager.select(any()) } verify { activity.openToBrowser(from = BrowserDirection.FromSearchDialog) } } + + @Test + fun `show camera permissions needed dialog`() { + val dialogBuilder: AlertDialogBuilder = mockk(relaxed = true) + + val spyController = spyk(controller) + every { spyController.buildDialog() } returns dialogBuilder + + spyController.handleCameraPermissionsNeeded() + + verify { dialogBuilder.show() } + } } From 8e7d10c33c6ffd6b6bc58d02affe660abedb42aa Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Fri, 11 Sep 2020 00:01:06 +0000 Subject: [PATCH 10/22] Import l10n. --- app/src/main/res/values-ast/strings.xml | 11 +++++-- app/src/main/res/values-be/strings.xml | 12 +++++++- app/src/main/res/values-cak/strings.xml | 17 +++++++---- app/src/main/res/values-cs/strings.xml | 18 +++++------- app/src/main/res/values-de/strings.xml | 33 +++++++++++++++++---- app/src/main/res/values-en-rCA/strings.xml | 33 +++++++++++++++++---- app/src/main/res/values-hsb/strings.xml | 34 ++++++++++++++++++---- app/src/main/res/values-hu/strings.xml | 34 ++++++++++++++++++---- app/src/main/res/values-iw/strings.xml | 33 +++++++++++++++++---- app/src/main/res/values-ko/strings.xml | 33 +++++++++++++++++---- app/src/main/res/values-nb-rNO/strings.xml | 24 ++++++++------- app/src/main/res/values-pt-rBR/strings.xml | 33 +++++++++++++++++---- app/src/main/res/values-sv-rSE/strings.xml | 33 +++++++++++++++++---- 13 files changed, 277 insertions(+), 71 deletions(-) diff --git a/app/src/main/res/values-ast/strings.xml b/app/src/main/res/values-ast/strings.xml index 60d218f38..101c25ad1 100644 --- a/app/src/main/res/values-ast/strings.xml +++ b/app/src/main/res/values-ast/strings.xml @@ -263,6 +263,8 @@ Abrir los enllaces nuna llingüeta privada Permitir la fechura de captures nel restolar en privao + + Si s\'activa, les llingüetes privaes tamién van ser visibles cuando s\'abran munches aplicaciones Amestar un atayu pa restolar en privao @@ -281,6 +283,8 @@ Estilu Aniciu + + Xestos Personalización @@ -1228,7 +1232,7 @@ Nome del atayu - Pues amestar fácilmente esti sitiu web a la pantalla d\'aniciu del preséu p\'acceder nel intre y restolalu como si fore una aplicación. + Pues amestar fácilmente esti sitiu web a la pantalla d\'Aniciu del preséu p\'acceder aína a elli como si fore una aplicación nativa. Anicios de sesión y contraseñes @@ -1462,8 +1466,6 @@ Algamóse la llende de sitios destacaos - - P\'amestar sitios nuevos desanicia dalgún. Ten primíu un sitiu y esbilla Desaniciar. Val, entendílo @@ -1478,4 +1480,7 @@ Coleiciona les coses que t\'importen Agrupa busques, sitios y llingüetes asemeyaos p\'acceder aína a ellos dempués. + + Pues amestar fácilmente esti sitiu web a la pantalla d\'aniciu del preséu p\'acceder nel intre y restolalu como si fore una aplicación. + diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index c484bbd62..8f8250f17 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -402,6 +402,8 @@ Доследы + + Дазваляе Mozilla ўсталёўваць эксперыментальныя функцыі і збіраць звесткі для іх Паведамляльнік пра крахі @@ -934,6 +936,9 @@ Аўтаматычны памер шрыфту + + Памер шрыфту будзе адпавядаць наладам Android. Адключыце, каб кіраваць шрыфтам тут. + Выдаліць дадзеныя аглядання @@ -1184,6 +1189,8 @@ Майнеры крыптавалют Збіральнікі лічбавых адбіткаў + + Спыняе збор унікальнай ідэнтыфікацыйнай інфармацыі пра вашу прыладу, якая можа быць выкарыстана для сачэння. Змест з элементамі сачэння @@ -1354,6 +1361,9 @@ Апошняе выкарыстанне + + Меню сартавання лагінаў + Дадаць пашукавік @@ -1474,7 +1484,7 @@ Лагін з такім імем карыстальніка ўжо існуе - + Падключыць іншую прыладу. diff --git a/app/src/main/res/values-cak/strings.xml b/app/src/main/res/values-cak/strings.xml index 1b04f5b19..56f6e63f7 100644 --- a/app/src/main/res/values-cak/strings.xml +++ b/app/src/main/res/values-cak/strings.xml @@ -1081,7 +1081,7 @@ - Xatikirisaj molojri\'ïl pa jun chik rokik\'amaya\'l Firefox achi\'el %s pa re oyonib\'äl re\'. ¿La nawajo\' natikirisaj molojri\'ïl rik\'in re rub\'i\' taqoya\'l re\'? + Xatikirisaj molojri\'ïl pa jun chik rokik\'amaya\'l Firefox achi\'el %s pa re okisab\'äl re\'. ¿La nawajo\' natikirisaj molojri\'ïl rik\'in re rub\'i\' taqoya\'l re\'? Ja\', titikirisäx molojri\'ïl @@ -1333,7 +1333,7 @@ Rub\'i\' ri choj okem - Anin yatikïr naya\' re ajk\'amaya\'l pa ri Rutikirib\'al ruxaq awoyonib\'al richin aninäq nawokisaj chuqa\' aninäq yatok pa k\'amaya\'l, achi\'el ta xa jun chokoy. + Anin yatikïr naya\' re ajk\'amaya\'l pa ri Rutikirib\'al ruxaq okisab\'al richin aninäq nawokisaj chuqa\' aninäq yatok pa k\'amaya\'l, achi\'el ta xa jun chokoy. Kitikirisaxik molojri\'ïl chuqa\' ewan taq tzij @@ -1403,8 +1403,12 @@ Xwachib\'ëx ri ruxaq pa molwuj Tiwachib\'ëx ewan tzij + + Tijosq\'iï ewan tzij Tiwachib\'ëx rub\'i\' winäq + + Tijosq\'ïx rub\'i\' winäq Tiwachib\'ëx ruxaq @@ -1565,7 +1569,7 @@ Achi\'el: \nhttps://www.google.com/search?q=%s K\'o chik jun tikirib\'äl molojri\'ïl rik\'in re b\'i\'aj re\' - + Tokisäx jun chik okisab\'äl Tajuxub\'ej chik awi\'. @@ -1585,8 +1589,6 @@ Achi\'el: \nhttps://www.google.com/search?q=%s Xaq\'i\' ruchi\' ri jutaqil taq ruxaq - - Richin natz\'aqatisaj jun k\'ak\'a\' jutaqil ruxaq, tayuju\' jun. Tapitz\'a\' pa ruwi\' ri ruxaq richin nacha\' tiyuj. ÜTZ, Wetaman Chik @@ -1604,4 +1606,9 @@ Achi\'el: \nhttps://www.google.com/search?q=%s Ke\'amolo\' ri taq wachinäq niqa chawa Ketzob\'ajïx taq kanoxïk, taq ruxaq chuqa\' taq ruwi\' ejunam richin ye\'okisäx na. + + Xatikirisaj molojri\'ïl pa jun chik rokik\'amaya\'l Firefox achi\'el %s pa re oyonib\'äl re\'. ¿La nawajo\' natikirisaj molojri\'ïl rik\'in re rub\'i\' taqoya\'l re\'? + + Anin yatikïr naya\' re ajk\'amaya\'l pa ri Rutikirib\'al ruxaq awoyonib\'al richin aninäq nawokisaj chuqa\' aninäq yatok pa k\'amaya\'l, achi\'el ta xa jun chokoy. + diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 774847bb1..6aba69922 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -517,7 +517,7 @@ - Zavřít panely + Zavírat panely Ručně @@ -1064,10 +1064,6 @@ S účtem Firefoxu můžete synchronizovat záložky, hesla i další svá data. Zjistit více - - V dalším prohlížeči Firefox na tomto telefonu už jste přihlášení jako %s. Chcete se přihlásit tímto účtem? Ano, přihlásit @@ -1309,9 +1305,6 @@ Název zkratky - - Tuto stránku si můžete snadno přidat na domovskou obrazovku vašeho telefonu. Budete k ní mít okamžitý přístup a prohlížení bude rychlejší se zážitkem jako v aplikaci. - Přihlašovací údaje @@ -1540,7 +1533,7 @@ Přihlašovací údaje s tímto uživatelským jménem už existují - + Připojte další zařízení. @@ -1560,8 +1553,6 @@ Dosažen limit počtu top stránek - - Pro přidání další top stránky nejdříve nějakou odeberte. Stačí na ní podržet prst. OK, rozumím @@ -1576,4 +1567,9 @@ Uložte si důležité věci do sbírek Podobná vyhledávání, stránky a panely si můžete seskupit a poté se k nim snadno vracet. + + V dalším prohlížeči Firefox na tomto telefonu už jste přihlášení jako %s. Chcete se přihlásit tímto účtem? + + Tuto stránku si můžete snadno přidat na domovskou obrazovku vašeho telefonu. Budete k ní mít okamžitý přístup a prohlížení bude rychlejší se zážitkem jako v aplikaci. + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index cd67ceeed..8f6f868fc 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -273,6 +273,8 @@ Links in privatem Tab öffnen Bildschirmfotos im privaten Modus zulassen + + Wenn erlaubt, sind auch private Tabs sichtbar, wenn mehrere Apps geöffnet sind Verknüpfung zum privaten Modus hinzufügen @@ -293,6 +295,8 @@ Theme Startseite + + Gesten Anpassen @@ -471,6 +475,16 @@ Geräte-Theme beachten + + + Zum Aktualisieren ziehen + + Zum Ausblenden der Symbolleiste scrollen + + Symbolleiste zur Seite wischen, um Tabs zu wechseln + + Nach oben wischen, ob Tabs zu öffnen + Sitzungen @@ -1086,7 +1100,7 @@ - Sie sind in einem anderen Firefox-Browser auf diesem Handy als %s angemeldet. Möchten Sie sich mit diesem Konto anmelden? + Sie sind in einem anderen Firefox-Browser auf diesem Gerät als %s angemeldet. Möchten Sie sich mit diesem Konto anmelden? Ja, anmelden @@ -1330,7 +1344,7 @@ Name der Verknüpfung - Sie können diese Website einfach zum Startbildschirm Ihres Handys hinzufügen, um unmittelbaren Zugriff darauf zu haben und sie wie eine App zu nutzen. + Sie können diese Website einfach zum Startbildschirm Ihres Geräts hinzufügen, um unmittelbaren Zugriff darauf zu haben und sie wie eine App zu nutzen. Zugangsdaten und Passwörter @@ -1400,8 +1414,12 @@ Website in Zwischenablage kopiert Passwort kopieren + + Passwort löschen Benutzernamen kopieren + + Benutzername löschen Website kopieren @@ -1557,7 +1575,7 @@ Es existieren bereits Zugangsdaten mit diesem Benutzernamen - + Weiteres Gerät verbinden. Bitte erneut authentifizieren. @@ -1577,7 +1595,7 @@ Obergrenze für wichtige Seiten erreicht - Entfernen Sie eine der wichtigen Seiten, um eine neue hinzuzufügen. Tippen Sie lange auf die Seite und wählen Sie „Entfernen“. + Entfernen Sie eine der wichtigen Seiten, um eine neue hinzuzufügen. Tippen und halten Sie die Seite und wählen Sie „Entfernen“. Ok, verstanden @@ -1595,4 +1613,9 @@ Sammeln Sie die Dinge, die Ihnen wichtig sind Gruppieren Sie ähnliche Suchanfragen, Websites und Tabs, um später schnell darauf zugreifen zu können. - + + Sie sind in einem anderen Firefox-Browser auf diesem Handy als %s angemeldet. Möchten Sie sich mit diesem Konto anmelden? + + Sie können diese Website einfach zum Startbildschirm Ihres Handys hinzufügen, um unmittelbaren Zugriff darauf zu haben und sie wie eine App zu nutzen. + + diff --git a/app/src/main/res/values-en-rCA/strings.xml b/app/src/main/res/values-en-rCA/strings.xml index f35760ed7..a7bc236b9 100644 --- a/app/src/main/res/values-en-rCA/strings.xml +++ b/app/src/main/res/values-en-rCA/strings.xml @@ -266,6 +266,8 @@ Open links in a private tab Allow screenshots in private browsing + + If allowed, private tabs will also be visible when multiple apps are open Add private browsing shortcut @@ -286,6 +288,8 @@ Theme Home + + Gestures Customize @@ -458,6 +462,16 @@ Follow device theme + + + Pull to refresh + + Scroll to hide toolbar + + Swipe toolbar sideways to switch tabs + + Swipe toolbar up to open tabs + Sessions @@ -1044,7 +1058,7 @@ - You are signed in as %s on another Firefox browser on this phone. Would you like to sign in with this account? + You are signed in as %s on another Firefox browser on this device. Would you like to sign in with this account? Yes, sign me in @@ -1287,7 +1301,7 @@ Shortcut name - You can easily add this website to your phone’s Home screen to have instant access and browse faster with an app-like experience. + You can easily add this website to your device’s Home screen to have instant access and browse faster with an app-like experience. Logins and passwords @@ -1357,8 +1371,12 @@ Site copied to clipboard Copy password + + Clear password Copy username + + Clear username Copy site @@ -1513,7 +1531,7 @@ A login with that username already exists - + Connect another device. Please re-authenticate. @@ -1533,7 +1551,7 @@ Top site limit reached - To add a new top site, remove one. Long press the site and select remove. + To add a new top site, remove one. Touch and hold the site and select remove. OK, Got It @@ -1551,4 +1569,9 @@ Collect the things that matter to you Group together similar searches, sites, and tabs for quick access later. - + + You are signed in as %s on another Firefox browser on this phone. Would you like to sign in with this account? + + You can easily add this website to your phone’s Home screen to have instant access and browse faster with an app-like experience. + + diff --git a/app/src/main/res/values-hsb/strings.xml b/app/src/main/res/values-hsb/strings.xml index 6c0cdbad2..888017b67 100644 --- a/app/src/main/res/values-hsb/strings.xml +++ b/app/src/main/res/values-hsb/strings.xml @@ -268,6 +268,8 @@ Wotkazy w priwatnym rajtarku wočinić Fota wobrazowki w priwatnym modusu dowolić + + Jeli dowolene, budu priwatne rajtarki tež widźomne, hdyž wjacore nałoženja su wočinjene Skrótšenku za priwatny modus přidać @@ -288,6 +290,8 @@ Drasta Startowa strona + + Gesty Přiměrić @@ -462,6 +466,17 @@ Na gratowu drastu dźiwać + + + Ćehńće, zo byšće aktualizował + + Kulće, zo byšće symbolowu lajstu schował + + + Trějće symbolowu lajstu nabok, zo byšće rajtarki přepinał + + Trějće symbolowu lajstu horje, zo byšće rajtarki wočinił + Posedźenja @@ -1053,7 +1068,7 @@ - Sće so jako %s w druhim wobhladowaku Firefox na tutym telefonje přizjewił. Chceće so z tutym kontom přizjewić? + Sće so jako %s w druhim wobhladowaku Firefox na tutym graće přizjewił. Chceće so z tutym kontom přizjewić? Haj, přizjewić @@ -1298,7 +1313,7 @@ Mjeno skrótšenki - Móžeće startowej wobrazowce swojeho telefona tute websydło lochko přidać, zo byšće direktny přistup měł a spěšnišo z dožiwjenjom nałoženja přehladował. + Móžeće startowej wobrazowce swojeho grata tute websydło lochko přidać, zo byšće direktny přistup měł a spěšnišo z dožiwjenjom nałoženja přehladował. Přizjewjenja a hesła @@ -1368,8 +1383,12 @@ Sydło je so do mjezyskłada kopěrowało Hesło kopěrować + + Hesło zhašeć Wužiwarske mjeno kopěrować + + Wužiwarske mjeno zhašeć Sydło kopěrować @@ -1527,7 +1546,7 @@ Přizjewjenje z tym wužiwarskim mjenom hižo eksistuje. - + Z druhim gratom zwjazać. Prošu awtentifikujće znowa. @@ -1547,7 +1566,7 @@ Limit za wažne sydła docpěty - Zo byšće nowe wažne sydło přidał, wotstrońće jedne z nich. Tłóčće dołho na sydło a wubjerće „Wotstronić“. + Zo byšće nowe wažne sydło přidał, wotstrońće jedne z nich. Dótkńće so sydła, dźeržće jo a wubjerće „Wotstronić“. W porjadku, sym zrozumił @@ -1565,4 +1584,9 @@ Zběrajće wěcy, kotrež su wam wažne Zeskupće podobne pytanja, sydła a rajtarki za pozdźiši spěšny přistup. - + + Sće so jako %s w druhim wobhladowaku Firefox na tutym telefonje přizjewił. Chceće so z tutym kontom přizjewić? + + Móžeće startowej wobrazowce swojeho telefona tute websydło lochko přidać, zo byšće direktny přistup měł a spěšnišo z dožiwjenjom nałoženja přehladował. + + diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 26e564cbe..4ab7ef165 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -269,6 +269,8 @@ Hivatkozások megnyitása privát lapon Képernyőképek engedélyezése privát böngészésben + + Ha engedélyezett, a privát lapok akkor is láthatóak lesznek, ha több alkalmazás van nyitva Privát böngészési parancsikon hozzáadása @@ -289,6 +291,8 @@ Téma Kezdőlap + + Kézmozdulatok Testreszabás @@ -465,6 +469,17 @@ Az eszköz témájának követése + + + Húzza a frissítéshez + + Görgessen az eszköztár elrejtéséhez + + + Seperje oldalra az eszköztárat a lapok közti váltáshoz + + Seperje felfelé az eszköztárat a lapok megnyitásához + Munkamenetek @@ -1061,7 +1076,7 @@ - A következőként van bejelentkezve egy másik Firefox böngészőben ezen a telefonon: %s. Szeretne bejelentkezni ezzel a fiókkal? + A következőként van bejelentkezve egy másik Firefox böngészőben ezen az eszközön: %s. Szeretne bejelentkezni ezzel a fiókkal? Igen, jelentkeztessen be @@ -1308,7 +1323,7 @@ Parancsikon neve - Könnyedén hozzáadhatja ezt a weboldalt a telefonja Kezdőképernyőhöz, és azonnal elérheti azt, így gyorsabban böngészve, miközben alkalmazásszerű élményt kap. + Könnyedén hozzáadhatja ezt a weboldalt az eszköze Kezdőképernyőhöz, és azonnal elérheti azt, így gyorsabban böngészve, miközben alkalmazásszerű élményt kap. Bejelentkezések és jelszavak @@ -1378,8 +1393,12 @@ Az oldal vágólapra másolva Jelszó másolása + + Jelszó törlése Felhasználónév másolása + + Felhasználónév törlése Oldal másolása @@ -1535,7 +1554,7 @@ Már létezik bejelentkezés ezzel a felhasználónévvel. - + Másik eszköz csatlakoztatása. Hitelesítsen újra. @@ -1556,7 +1575,7 @@ Kedvenc oldalak korlátja elérve - Új kedvenc oldal hozzáadásához távolítson el egyet. Nyomja hosszan az oldalt, és válassza az eltávolítást. + Új kedvenc oldal hozzáadásához távolítson el egyet. Érintse meg és tartsa az ujját az oldalon, és válassza az eltávolítást. Rendben, értem @@ -1574,4 +1593,9 @@ Gyűjtse össze az Önnek fontos dolgokat Csoportosítsa a hasonló kereséseket, webhelyeket és lapokat a későbbi gyors elérés érdekében. - + + A következőként van bejelentkezve egy másik Firefox böngészőben ezen a telefonon: %s. Szeretne bejelentkezni ezzel a fiókkal? + + Könnyedén hozzáadhatja ezt a weboldalt a telefonja Kezdőképernyőhöz, és azonnal elérheti azt, így gyorsabban böngészve, miközben alkalmazásszerű élményt kap. + + diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index c8d213fe1..09e84d9e7 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -265,6 +265,8 @@ פתיחת קישורים בלשונית פרטית לאפשר צילומי מסך בגלישה פרטית + + אם אפשרות זו מופעלת, לשוניות פרטיות תהיינה מוצגות כאשר מספר יישומונים פתוחים הוספת קיצור דרך לגלישה פרטית @@ -283,6 +285,8 @@ ערכת נושא בית + + מחוות התאמה אישית @@ -455,6 +459,16 @@ שימוש בערכת הנושא של המכשיר + + + משיכה לרענון הדף + + גלילה להסתרת סרגל הכלים + + החלקה על סרגל הכלים כלפי הצדדים למעבר בין לשוניות + + החלקה על סרגל הכלים כלפי מעלה לפתיחת לשוניות + הפעלות @@ -1046,7 +1060,7 @@ - דפדפן Firefox נוסף בטלפון זה מחובר כ־%s. האם ברצונך להתחבר עם חשבון זה? + דפדפן Firefox נוסף במכשיר זה מחובר כ־%s. האם ברצונך להתחבר עם חשבון זה? כן, תחברו אותי @@ -1288,7 +1302,7 @@ שם קיצור הדרך - באפשרותך להוסיף בקלות אתר זה למסך הבית של הטלפון שלך כדי לקבל גישה מיידית ולגלוש מהר יותר עם חוויה שמדמה שימוש ביישומון. + באפשרותך להוסיף בקלות אתר זה למסך הבית של המכשיר שלך כדי לקבל גישה מיידית ולגלוש מהר יותר עם חוויה שמדמה שימוש ביישומון. כניסות וססמאות @@ -1350,8 +1364,12 @@ האתר הועתק ללוח העתקת ססמה + + ניקוי ססמה העתקת שם משתמש + + ניקוי שם משתמש העתקת אתר @@ -1503,7 +1521,7 @@ כבר קיימת כניסה עם שם משתמש זה - + נא לחבר מכשיר נוסף. נא להפעיל סנכרון לשוניות. @@ -1522,7 +1540,7 @@ הגעת למכסת האתרים המובילים - כדי להוסיף אתר מוביל חדש, יש להסיר אחד אחר. יש ללחוץ לחיצה ארוכה על האתר ולבחור ב״הסרה״. + כדי להוסיף אתר מוביל חדש, יש להסיר אחד אחר. יש ללחוץ לחיצה ארוכה על האתר ולבחור ב״הסרה״. בסדר, הבנתי @@ -1540,4 +1558,9 @@ לאסוף את הדברים החשובים לך ניתן לקבץ חיפושים, אתרים ולשוניות דומים יחד כדי לגשת אליהם מהר יותר בהמשך. - + + דפדפן Firefox נוסף בטלפון זה מחובר כ־%s. האם ברצונך להתחבר עם חשבון זה? + + באפשרותך להוסיף בקלות אתר זה למסך הבית של הטלפון שלך כדי לקבל גישה מיידית ולגלוש מהר יותר עם חוויה שמדמה שימוש ביישומון. + + diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index e9d6d1b74..551ce30cb 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -278,6 +278,8 @@ 사생활 보호 탭에 링크 열기 사생활 보호 모드에서 스크린샷 허용 + + 허용되는 경우, 여러 앱이 열려있을 때 사생활 보호 탭도 표시됨 사생활 보호 모드 바로 가기 추가 @@ -298,6 +300,8 @@ 테마 + + 제스처 사용자 지정 @@ -476,6 +480,16 @@ 기기 테마 따르기 + + + 당겨서 새로 고침 + + 스크롤해서 툴바 숨기기 + + 도구 모음을 옆으로 밀어서 탭 전환 + + 도구 모음을 위로 밀어서 탭 열기 + 세션 @@ -1097,7 +1111,7 @@ - 이 휴대폰의 다른 Firefox 브라우저에서 %s로 로그인했습니다. 이 계정으로 로그인하시겠습니까? + 이 기기의 다른 Firefox 브라우저에서 %s(으)로 로그인했습니다. 이 계정으로 로그인하시겠습니까? 예, 로그인함 @@ -1345,7 +1359,7 @@ 바로 가기 이름 - 이 웹 사이트를 휴대폰의 홈 화면에 쉽게 추가하여 앱과 같은 경험을 통해 즉시 액세스하고 더 빠르게 탐색 할 수 있습니다. + 이 웹 사이트를 기기의 홈 화면에 쉽게 추가하여 앱과 같은 경험을 통해 즉시 액세스하고 더 빠르게 탐색 할 수 있습니다. 로그인과 비밀번호 @@ -1415,8 +1429,12 @@ 사이트가 클립보드에 복사됨 비밀번호 복사 + + 비밀번호 지우기 사용자 이름 복사 + + 사용자 이름 지우기 사이트 복사 @@ -1574,7 +1592,7 @@ 해당 사용자 이름을 가진 로그인이 이미 존재합니다 - + 다른 기기를 연결하세요. 다시 인증하세요. @@ -1595,7 +1613,7 @@ 상위 사이트 제한에 도달 - 새로운 상위 사이트를 추가하려면 삭제하세요. 사이트를 길게 누르고 삭제를 선택하세요. + 새 상위 사이트를 추가하려면 하나를 삭제하세요. 사이트를 길게 터치하고 삭제를 선택하세요. 확인 @@ -1613,4 +1631,9 @@ 중요한 것들 수집하기 나중에 빠르게 접근할 수 있도록 유사한 검색, 사이트 및 탭을 모아 보세요. - + + 이 휴대폰의 다른 Firefox 브라우저에서 %s(으)로 로그인했습니다. 이 계정으로 로그인하시겠습니까? + + 이 웹 사이트를 휴대폰의 홈 화면에 쉽게 추가하여 앱과 같은 경험을 통해 즉시 액세스하고 더 빠르게 탐색 할 수 있습니다. + + diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index fb8f208e4..0d14a409d 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -290,6 +290,8 @@ Tema Hjem + + Bevegelser Tilpass @@ -466,6 +468,10 @@ Følg enhetens tema + + + Trekk for å oppdatere + Økter @@ -1059,10 +1065,6 @@ Begynn å synkronisere bokmerker, passord og mer med Firefox-kontoen din. Les mer - - Du er logget inn som %s på en annen Firefox-nettleser på denne telefonen. Vil du logge inn med denne kontoen? Ja, logg meg inn @@ -1313,9 +1315,6 @@ Navn på snarvei - - Du kan enkelt legge til dette nettstedet på telefonens startskjermen for å få øyeblikkelig tilgang og surfe raskere med en app-lignende opplevelse. - Innlogginger og passord @@ -1547,7 +1546,7 @@ En innlogging med det brukernavnet eksisterer allerede - + Koble til en annen enhet. Autentiser på nytt. @@ -1566,8 +1565,6 @@ Grense for populære nettsteder nådd - - For å legge til et nytt populært nettsted må du fjern en annen. Trykk lenge på nettstedet og velg fjern. OK, jeg skjønner @@ -1585,4 +1582,9 @@ Samle tingene som betyr noe for deg Grupper sammen lignende søk, nettsteder og faner for rask tilgang senere. - + + Du er logget inn som %s på en annen Firefox-nettleser på denne telefonen. Vil du logge inn med denne kontoen? + + Du kan enkelt legge til dette nettstedet på telefonens startskjermen for å få øyeblikkelig tilgang og surfe raskere med en app-lignende opplevelse. + + diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 268a258cb..33bd656bd 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -268,6 +268,8 @@ Abrir links em abas privativas Permitir capturas de tela na navegação privativa + + Se permitido, as abas privativas também ficam visíveis quando vários aplicativos estão abertos Adicionar atalho para navegação privativa @@ -288,6 +290,8 @@ Tema Tela inicial + + Gestos Personalizar @@ -462,6 +466,16 @@ Acompanhar tema do dispositivo + + + Puxe para atualizar + + Deslize para ocultar a barra de ferramentas + + Deslize a barra de ferramentas para o lado para mudar de aba + + Deslize a barra de ferramentas para cima para abrir abas + Sessões @@ -1052,7 +1066,7 @@ - Você está conectado como %s em outro navegador Firefox neste celular. Quer entrar com esta conta? + Você está conectado como %s em outro navegador Firefox neste dispositivo. Quer entrar com esta conta? Sim, entrar @@ -1299,7 +1313,7 @@ Nome do atalho - Você pode facilmente adicionar este site à tela inicial do celular para ter acesso imediato e navegar mais rápido com uma experiência semelhante a um aplicativo. + Você pode facilmente adicionar este site à tela inicial do dispositivo para ter acesso imediato e navegar mais rápido com uma experiência semelhante a um aplicativo. Contas e senhas @@ -1369,8 +1383,12 @@ Site copiado para a área de transferência Copiar senha + + Limpar senha Copiar nome de usuário + + Limpar nome de usuário Copiar site @@ -1526,7 +1544,7 @@ Já existe uma conta com este nome de usuário - + Conecte outro dispositivo. Autentique novamente. @@ -1547,7 +1565,7 @@ Atingiu o limite de sites preferidos - Para adicionar mais um site preferido, remova outro. Mantenha o dedo sobre o site e selecione remover. + Para adicionar mais um site preferido, remova outro. Mantenha o dedo sobre o site e selecione remover. OK, entendi @@ -1565,4 +1583,9 @@ Reúna o que é importante para você Agrupe pesquisas, sites e abas semelhantes para acesso rápido mais tarde. - + + Você está conectado como %s em outro navegador Firefox neste celular. Quer entrar com esta conta? + + Você pode facilmente adicionar este site à tela inicial do celular para ter acesso imediato e navegar mais rápido com uma experiência semelhante a um aplicativo. + + diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index affae7f02..9bd59bbed 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -272,6 +272,8 @@ Öppna länkar i en privat flik Tillåt skärmdumpar i privat surfning + + Om tillåtet kommer privata flikar också att visas när flera appar är öppna Lägg till genväg för privat surfning @@ -292,6 +294,8 @@ Tema Hem + + Gester Anpassa @@ -470,6 +474,16 @@ Följ enhetens tema + + + Dra för att uppdatera + + Bläddra för att dölja verktygsfältet + + Svep verktygsfältet i sidled för att byta flik + + Svep verktygsfältet uppåt för att öppna flikar + Sessioner @@ -1068,7 +1082,7 @@ - Du är inloggad som %s på en annan Firefox-webbläsare på den här telefonen. Vill du logga in med det här kontot? + Du är inloggad som %s i en annan Firefox-webbläsare på den här enheten. Vill du logga in med det här kontot? Ja, logga in mig @@ -1315,7 +1329,7 @@ Genvägens namn - Du kan enkelt lägga till den här webbplatsen på telefonens startsida för att få direktåtkomst och surfa snabbare med en appliknande upplevelse. + Du kan enkelt lägga till den här webbplatsen på enhetens startskärm för att få direktåtkomst och surfa snabbare med en appliknande upplevelse. Inloggningar och lösenord @@ -1385,8 +1399,12 @@ Webbplats kopierad till urklipp Kopiera lösenord + + Rensa lösenord Kopiera användarnamn + + Rensa användarnamn Kopiera webbplats @@ -1542,7 +1560,7 @@ En inloggning med det användarnamnet finns redan - + Anslut en annan enhet. Bekräfta igen. @@ -1562,7 +1580,7 @@ Övre gräns för mest besökta nådd - För att lägga till en ny mest besökt sida, ta bort en. Tryck länge på webbplatsen och välj ta bort. + För att lägga till en ny mest besökt sida, ta bort en. Tryck länge på webbplatsen och välj ta bort. Ok, jag förstår @@ -1580,4 +1598,9 @@ Samla de saker som är viktiga för dig Gruppera liknande sökningar, webbplatser och flikar för snabb åtkomst senare. - + + Du är inloggad som %s på en annan Firefox-webbläsare på den här telefonen. Vill du logga in med det här kontot? + + Du kan enkelt lägga till den här webbplatsen på telefonens startsida för att få direktåtkomst och surfa snabbare med en appliknande upplevelse. + + From f9b2d6599328623cc8f1f87f90f8fbd2a5fca8cc Mon Sep 17 00:00:00 2001 From: Jeff Boek Date: Wed, 9 Sep 2020 20:45:44 -0700 Subject: [PATCH 11/22] For #5745 - Create preference to disable domain autocompletion --- .../mozilla/fenix/search/toolbar/ToolbarView.kt | 16 ++++++++++------ .../settings/search/SearchEngineFragment.kt | 6 ++++++ .../java/org/mozilla/fenix/utils/Settings.kt | 5 +++++ app/src/main/res/values/preference_keys.xml | 1 + app/src/main/res/values/strings.xml | 2 ++ app/src/main/res/xml/search_preferences.xml | 4 ++++ 6 files changed, 28 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt b/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt index 79f11acf1..037c08708 100644 --- a/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt +++ b/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt @@ -19,6 +19,7 @@ import mozilla.components.support.ktx.android.content.getColorFromAttr import mozilla.components.support.ktx.android.content.res.resolveAttribute import mozilla.components.support.ktx.android.view.hideKeyboard import org.mozilla.fenix.R +import org.mozilla.fenix.ext.settings import org.mozilla.fenix.search.SearchFragmentState /** @@ -110,12 +111,15 @@ class ToolbarView( } val engineForSpeculativeConnects = if (!isPrivate) engine else null - ToolbarAutocompleteFeature( - view, - engineForSpeculativeConnects - ).apply { - addDomainProvider(ShippedDomainsProvider().also { it.initialize(view.context) }) - historyStorage?.also(::addHistoryStorageProvider) + + if (context.settings().shouldAutocompleteInAwesomebar) { + ToolbarAutocompleteFeature( + view, + engineForSpeculativeConnects + ).apply { + addDomainProvider(ShippedDomainsProvider().also { it.initialize(view.context) }) + historyStorage?.also(::addHistoryStorageProvider) + } } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineFragment.kt index 800bf05bc..1c69aba76 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineFragment.kt @@ -32,6 +32,11 @@ class SearchEngineFragment : PreferenceFragmentCompat() { isChecked = context.settings().shouldShowSearchSuggestions } + val autocompleteURLsPreference = + requirePreference(R.string.pref_key_enable_autocomplete_urls).apply { + isChecked = context.settings().shouldAutocompleteInAwesomebar + } + val searchSuggestionsInPrivatePreference = requirePreference(R.string.pref_key_show_search_suggestions_in_private).apply { isChecked = context.settings().shouldShowSearchSuggestionsInPrivate @@ -73,6 +78,7 @@ class SearchEngineFragment : PreferenceFragmentCompat() { showClipboardSuggestions.onPreferenceChangeListener = SharedPreferenceUpdater() searchSuggestionsInPrivatePreference.onPreferenceChangeListener = SharedPreferenceUpdater() showVoiceSearchPreference.onPreferenceChangeListener = SharedPreferenceUpdater() + autocompleteURLsPreference.onPreferenceChangeListener = SharedPreferenceUpdater() searchSuggestionsPreference.setOnPreferenceClickListener { if (!searchSuggestionsPreference.isChecked) { diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index 4c2e0874b..d1bade330 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -615,6 +615,11 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = true ) + val shouldAutocompleteInAwesomebar by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_enable_autocomplete_urls), + default = true + ) + var defaultTopSitesAdded by booleanPreference( appContext.getPreferenceKey(R.string.default_top_sites_added), default = false diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index 98aa99d53..8e6b1dcc6 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -97,6 +97,7 @@ pref_key_show_search_suggestions_in_private pref_key_show_search_suggestions_in_privateonboarding pref_key_show_voice_search + pref_key_enable_domain_autocomplete pref_key_optimize diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index abd5168b8..3be52909f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -322,6 +322,8 @@ Search bookmarks Account settings + + Autocomplete URLs Open links in apps diff --git a/app/src/main/res/xml/search_preferences.xml b/app/src/main/res/xml/search_preferences.xml index d892037e0..9d5cc495d 100644 --- a/app/src/main/res/xml/search_preferences.xml +++ b/app/src/main/res/xml/search_preferences.xml @@ -28,6 +28,10 @@ android:defaultValue="true" android:key="@string/pref_key_show_search_engine_shortcuts" android:title="@string/preferences_show_search_engines" /> + Date: Fri, 11 Sep 2020 04:06:38 +0300 Subject: [PATCH 12/22] For #14902 - Disabling bottom toolbar animation now works (#14927) --- .../fenix/components/toolbar/BrowserToolbarView.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt index 57b522296..5d55bea76 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt @@ -227,8 +227,12 @@ class BrowserToolbarView( fun setScrollFlags(shouldDisableScroll: Boolean = false) { when (settings.toolbarPosition) { ToolbarPosition.BOTTOM -> { - (view.layoutParams as? CoordinatorLayout.LayoutParams)?.apply { - behavior = BrowserToolbarBottomBehavior(view.context, null) + if (settings.isDynamicToolbarEnabled) { + (view.layoutParams as? CoordinatorLayout.LayoutParams)?.apply { + behavior = BrowserToolbarBottomBehavior(view.context, null) + } + } else { + expand() } } ToolbarPosition.TOP -> { From b9867300317a0c805a873b4e9fdc89b305146ee3 Mon Sep 17 00:00:00 2001 From: ekager Date: Thu, 10 Sep 2020 15:14:11 -0700 Subject: [PATCH 13/22] No issue - Updates AC to 58.0.20200910190642 and fixes imports --- app/src/main/java/org/mozilla/fenix/components/Core.kt | 2 +- .../mozilla/fenix/components/metrics/BreadcrumbsRecorder.kt | 2 +- app/src/main/java/org/mozilla/fenix/ext/Activity.kt | 2 +- app/src/main/java/org/mozilla/fenix/ext/Fragment.kt | 2 +- .../main/java/org/mozilla/fenix/tabtray/TabTrayController.kt | 4 ++-- app/src/test/java/org/mozilla/fenix/components/TestCore.kt | 2 +- .../fenix/components/metrics/BreadcrumbRecorderTest.kt | 2 +- .../org/mozilla/fenix/tabtray/DefaultTabTrayControllerTest.kt | 2 +- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) 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 79fedd99f..12a556bfc 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -27,6 +27,7 @@ import mozilla.components.browser.storage.sync.PlacesHistoryStorage import mozilla.components.browser.storage.sync.RemoteTabsStorage import mozilla.components.browser.thumbnails.ThumbnailsMiddleware import mozilla.components.browser.thumbnails.storage.ThumbnailStorage +import mozilla.components.concept.base.crash.CrashReporting import mozilla.components.concept.engine.DefaultSettings import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.mediaquery.PreferredColorScheme @@ -51,7 +52,6 @@ import mozilla.components.service.digitalassetlinks.RelationChecker import mozilla.components.service.digitalassetlinks.local.StatementApi import mozilla.components.service.digitalassetlinks.local.StatementRelationChecker import mozilla.components.service.sync.logins.SyncableLoginsStorage -import mozilla.components.support.base.crash.CrashReporting import mozilla.components.support.locale.LocaleManager import org.mozilla.fenix.AppRequestInterceptor import org.mozilla.fenix.Config diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/BreadcrumbsRecorder.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/BreadcrumbsRecorder.kt index 14399697b..6db1182b3 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/BreadcrumbsRecorder.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/BreadcrumbsRecorder.kt @@ -10,8 +10,8 @@ import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.OnLifecycleEvent import androidx.navigation.NavController import androidx.navigation.NavDestination +import mozilla.components.concept.base.crash.Breadcrumb import mozilla.components.lib.crash.CrashReporter -import mozilla.components.support.base.crash.Breadcrumb /** * Records breadcrumbs when the fragment changes. diff --git a/app/src/main/java/org/mozilla/fenix/ext/Activity.kt b/app/src/main/java/org/mozilla/fenix/ext/Activity.kt index e65ad9f35..0ab6efec0 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/Activity.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/Activity.kt @@ -7,7 +7,7 @@ package org.mozilla.fenix.ext import android.app.Activity import android.view.View import android.view.WindowManager -import mozilla.components.support.base.crash.Breadcrumb +import mozilla.components.concept.base.crash.Breadcrumb /** * Attempts to call immersive mode using the View to hide the status bar and navigation buttons. diff --git a/app/src/main/java/org/mozilla/fenix/ext/Fragment.kt b/app/src/main/java/org/mozilla/fenix/ext/Fragment.kt index 39c3f7bb4..1ceb0c2e6 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/Fragment.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/Fragment.kt @@ -12,7 +12,7 @@ import androidx.navigation.NavDirections import androidx.navigation.NavOptions import androidx.navigation.fragment.NavHostFragment.findNavController import androidx.navigation.fragment.findNavController -import mozilla.components.support.base.crash.Breadcrumb +import mozilla.components.concept.base.crash.Breadcrumb import org.mozilla.fenix.NavHostActivity import org.mozilla.fenix.R import org.mozilla.fenix.components.Components diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt index c3b4df91a..ea1a4abf0 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt @@ -9,8 +9,7 @@ import androidx.navigation.NavController import kotlinx.coroutines.ExperimentalCoroutinesApi import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager -import mozilla.components.browser.storage.sync.Tab as SyncTab -import mozilla.components.concept.engine.profiler.Profiler +import mozilla.components.concept.base.profiler.Profiler import mozilla.components.concept.engine.prompt.ShareData import mozilla.components.concept.tabstray.Tab import mozilla.components.feature.tabs.TabsUseCases @@ -21,6 +20,7 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager import org.mozilla.fenix.components.TabCollectionStorage import org.mozilla.fenix.ext.sessionsOfType import org.mozilla.fenix.home.HomeFragment +import mozilla.components.browser.storage.sync.Tab as SyncTab /** * [TabTrayDialogFragment] controller. diff --git a/app/src/test/java/org/mozilla/fenix/components/TestCore.kt b/app/src/test/java/org/mozilla/fenix/components/TestCore.kt index 31bc4234c..fad4bd4b9 100644 --- a/app/src/test/java/org/mozilla/fenix/components/TestCore.kt +++ b/app/src/test/java/org/mozilla/fenix/components/TestCore.kt @@ -10,12 +10,12 @@ import io.mockk.mockk import mozilla.components.browser.session.SessionManager import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.thumbnails.storage.ThumbnailStorage +import mozilla.components.concept.base.crash.CrashReporting import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.Settings import mozilla.components.concept.fetch.Client import mozilla.components.feature.pwa.WebAppShortcutManager import mozilla.components.feature.top.sites.DefaultTopSitesStorage -import mozilla.components.support.base.crash.CrashReporting class TestCore(context: Context, crashReporter: CrashReporting) : Core(context, crashReporter) { diff --git a/app/src/test/java/org/mozilla/fenix/components/metrics/BreadcrumbRecorderTest.kt b/app/src/test/java/org/mozilla/fenix/components/metrics/BreadcrumbRecorderTest.kt index 454cd8881..51def937d 100644 --- a/app/src/test/java/org/mozilla/fenix/components/metrics/BreadcrumbRecorderTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/metrics/BreadcrumbRecorderTest.kt @@ -12,10 +12,10 @@ import io.mockk.Called import io.mockk.mockk import io.mockk.spyk import io.mockk.verify +import mozilla.components.concept.base.crash.Breadcrumb import mozilla.components.lib.crash.Crash import mozilla.components.lib.crash.CrashReporter import mozilla.components.lib.crash.service.CrashReporterService -import mozilla.components.support.base.crash.Breadcrumb import org.junit.Assert.assertEquals import org.junit.Test diff --git a/app/src/test/java/org/mozilla/fenix/tabtray/DefaultTabTrayControllerTest.kt b/app/src/test/java/org/mozilla/fenix/tabtray/DefaultTabTrayControllerTest.kt index 318b761df..0e478c3ba 100644 --- a/app/src/test/java/org/mozilla/fenix/tabtray/DefaultTabTrayControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/tabtray/DefaultTabTrayControllerTest.kt @@ -18,7 +18,7 @@ import io.mockk.verifyOrder import kotlinx.coroutines.ExperimentalCoroutinesApi import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager -import mozilla.components.concept.engine.profiler.Profiler +import mozilla.components.concept.base.profiler.Profiler import mozilla.components.concept.tabstray.Tab import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.tabs.TabsUseCases diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 6cf6bff2e..e1570e7db 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 = "58.0.20200908130811" + const val VERSION = "58.0.20200910190642" } From e6ee13dcbb5feb71d4a0b35b12b4e568cba12125 Mon Sep 17 00:00:00 2001 From: Jeff Klukas Date: Thu, 10 Sep 2020 11:14:39 -0400 Subject: [PATCH 14/22] Update documentation on the epoch date used for version codes Addresses https://github.com/mozilla-mobile/fenix/issues/14031 --- buildSrc/src/main/java/Config.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index b3f6ff907..236689878 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -45,8 +45,8 @@ object Config { } private val fennecBaseVersionCode by lazy { - val format = SimpleDateFormat("YYYYMMDDHHMMSS", Locale.US) - val cutoff = format.parse("20150801000000") + val format = SimpleDateFormat("yyyyMMddHHmmss", Locale.US) + val cutoff = format.parse("20141228000000") val build = Date() Math.floor((build.time - cutoff.time) / (1000.0 * 60.0 * 60.0)).toInt() @@ -56,6 +56,12 @@ object Config { * Generates a versionCode that follows the same rules like legacy Fennec builds. * Adapted from: * https://searchfox.org/mozilla-central/rev/34cb8d0a2a324043bcfc2c56f37b31abe7fb23a8/python/mozbuild/mozbuild/android_version_code.py + * + * There is a discrepancy between the epoch date used here (20141228) + * and the epoch used in Fennec (20150801) for historical reasons. We keep + * this discrepancy to avoid having Fenix version codes decrease. + * Note that the original Fennec implementation also had an inconsistency in + * the documented epoch date (20150901) and the effective epoch date (20150801). */ @JvmStatic fun generateFennecVersionCode(abi: String): Int { @@ -69,7 +75,7 @@ object Config { // 0111 1000 0010 tttt tttt tttt tttt txpg // // The 17 bits labelled 't' represent the number of hours since midnight on - // September 1, 2015. (2015090100 in YYYYMMMDDHH format.) This yields a + // December 28, 2014. (2014122800 in yyyyMMddHH format.) This yields a // little under 15 years worth of hourly build identifiers, since 2**17 / (366 // * 24) =~ 14.92. // From e602720b879b40513bf194b348786844d93a3603 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Fri, 11 Sep 2020 14:06:44 +0000 Subject: [PATCH 15/22] Update Android Components version to 59.0.20200911130559. --- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index e1570e7db..7f74fa0c3 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 = "58.0.20200910190642" + const val VERSION = "59.0.20200911130559" } From cce58e7d51a51e4f9c3600bbf12650e3377c9a97 Mon Sep 17 00:00:00 2001 From: "codrut.topliceanu" Date: Fri, 11 Sep 2020 14:42:39 +0300 Subject: [PATCH 16/22] For #14725 - Resize search engine radio button --- app/src/main/res/layout/search_engine_radio_button.xml | 6 +++--- app/src/main/res/values/dimens.xml | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/layout/search_engine_radio_button.xml b/app/src/main/res/layout/search_engine_radio_button.xml index a2d1026c1..d352591af 100644 --- a/app/src/main/res/layout/search_engine_radio_button.xml +++ b/app/src/main/res/layout/search_engine_radio_button.xml @@ -14,8 +14,8 @@ android:focusable="true"> @@ -24,7 +24,7 @@ android:importantForAccessibility="no" android:layout_width="@dimen/search_engine_engine_icon_height" android:layout_height="@dimen/search_engine_engine_icon_height" - android:layout_marginStart="@dimen/search_bar_search_icon_margin" + android:layout_marginStart="@dimen/search_engine_engine_icon_margin" android:layout_gravity="center" /> 24dp 12dp 28dp + 12dp 48dp 24dp 32dp From 09fbb43f80d85111e314d46c0edf7c9dca8fc0fd Mon Sep 17 00:00:00 2001 From: ekager Date: Sat, 29 Aug 2020 15:24:45 -0400 Subject: [PATCH 17/22] For #2486 - Adds Recently Closed Tabs --- app/build.gradle | 1 + .../org/mozilla/fenix/BrowserDirection.kt | 3 +- .../java/org/mozilla/fenix/HomeActivity.kt | 3 + .../fenix/browser/BaseBrowserFragment.kt | 5 +- .../java/org/mozilla/fenix/components/Core.kt | 10 +- .../org/mozilla/fenix/home/HomeFragment.kt | 4 +- .../fenix/library/history/HistoryAdapter.kt | 6 +- .../library/history/HistoryController.kt | 11 ++ .../fenix/library/history/HistoryFragment.kt | 19 +- .../library/history/HistoryInteractor.kt | 4 + .../fenix/library/history/HistoryView.kt | 5 + .../viewholders/HistoryListItemViewHolder.kt | 23 ++- .../recentlyclosed/RecentlyClosedAdapter.kt | 36 ++++ .../RecentlyClosedController.kt | 86 +++++++++ .../recentlyclosed/RecentlyClosedFragment.kt | 135 ++++++++++++++ .../RecentlyClosedFragmentInteractor.kt | 44 +++++ .../RecentlyClosedFragmentStore.kt | 45 +++++ .../RecentlyClosedFragmentView.kt | 110 +++++++++++ .../RecentlyClosedItemViewHolder.kt | 68 +++++++ .../deletebrowsingdata/DeleteAndQuit.kt | 1 + .../DeleteBrowsingDataController.kt | 4 + .../DeleteBrowsingDataFragment.kt | 12 +- .../fenix/tabtray/TabTrayController.kt | 6 + .../tabtray/TabTrayFragmentInteractor.kt | 9 + .../org/mozilla/fenix/tabtray/TabTrayView.kt | 9 + .../res/layout/component_recently_closed.xml | 38 ++++ .../layout/fragment_recently_closed_tabs.xml | 9 + app/src/main/res/layout/history_list_item.xml | 91 ++++++++-- app/src/main/res/navigation/nav_graph.xml | 9 + .../mozilla/fenix/ext/MockKMatcherScope.kt | 15 ++ .../DefaultRecentlyClosedControllerTest.kt | 171 ++++++++++++++++++ .../RecentlyClosedFragmentInteractorTest.kt | 96 ++++++++++ ...DefaultDeleteBrowsingDataControllerTest.kt | 5 + buildSrc/src/main/java/Dependencies.kt | 1 + 34 files changed, 1053 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedAdapter.kt create mode 100644 app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedController.kt create mode 100644 app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragment.kt create mode 100644 app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentInteractor.kt create mode 100644 app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentStore.kt create mode 100644 app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentView.kt create mode 100644 app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedItemViewHolder.kt create mode 100644 app/src/main/res/layout/component_recently_closed.xml create mode 100644 app/src/main/res/layout/fragment_recently_closed_tabs.xml create mode 100644 app/src/test/java/org/mozilla/fenix/library/recentlyclosed/DefaultRecentlyClosedControllerTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentInteractorTest.kt diff --git a/app/build.gradle b/app/build.gradle index 3cf6ad855..dfcc663d9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -385,6 +385,7 @@ dependencies { implementation Deps.mozilla_feature_site_permissions implementation Deps.mozilla_feature_readerview implementation Deps.mozilla_feature_tab_collections + implementation Deps.mozilla_feature_recentlyclosed implementation Deps.mozilla_feature_top_sites implementation Deps.mozilla_feature_share implementation Deps.mozilla_feature_accounts_push diff --git a/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt b/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt index 841305b36..84da2f843 100644 --- a/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt +++ b/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt @@ -32,5 +32,6 @@ enum class BrowserDirection(@IdRes val fragmentId: Int) { FromAddonDetailsFragment(R.id.addonDetailsFragment), FromAddonPermissionsDetailsFragment(R.id.addonPermissionsDetailFragment), FromLoginDetailFragment(R.id.loginDetailFragment), - FromTabTray(R.id.tabTrayDialogFragment) + FromTabTray(R.id.tabTrayDialogFragment), + FromRecentlyClosed(R.id.recentlyClosedFragment) } diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index aaa99af95..d18c56462 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -83,6 +83,7 @@ import org.mozilla.fenix.home.intent.SpeechProcessingIntentProcessor import org.mozilla.fenix.home.intent.StartSearchIntentProcessor import org.mozilla.fenix.library.bookmarks.BookmarkFragmentDirections import org.mozilla.fenix.library.history.HistoryFragmentDirections +import org.mozilla.fenix.library.recentlyclosed.RecentlyClosedFragmentDirections import org.mozilla.fenix.perf.Performance import org.mozilla.fenix.perf.StartupTimeline import org.mozilla.fenix.search.SearchFragmentDirections @@ -703,6 +704,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { LoginDetailFragmentDirections.actionGlobalBrowser(customTabSessionId) BrowserDirection.FromTabTray -> TabTrayDialogFragmentDirections.actionGlobalBrowser(customTabSessionId) + BrowserDirection.FromRecentlyClosed -> + RecentlyClosedFragmentDirections.actionGlobalBrowser(customTabSessionId) } /** 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 5b208af83..315f20704 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -915,14 +915,13 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session */ protected open fun removeSessionIfNeeded(): Boolean { getSessionById()?.let { session -> - val sessionManager = requireComponents.core.sessionManager return if (session.source == SessionState.Source.ACTION_VIEW) { activity?.finish() - sessionManager.remove(session) + requireComponents.useCases.tabsUseCases.removeTab(session) true } else { if (session.hasParentSession) { - sessionManager.remove(session, true) + requireComponents.useCases.tabsUseCases.removeTab(session) } // We want to return to home if this session didn't have a parent session to select. val goToOverview = !session.hasParentSession diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt index 12a556bfc..fbec09a5d 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -20,6 +20,7 @@ import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.engine.EngineMiddleware import mozilla.components.browser.session.storage.SessionStorage +import mozilla.components.browser.state.action.RecentlyClosedAction import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.storage.sync.PlacesBookmarksStorage @@ -40,6 +41,7 @@ import mozilla.components.feature.media.middleware.MediaMiddleware import mozilla.components.feature.pwa.ManifestStorage import mozilla.components.feature.pwa.WebAppShortcutManager import mozilla.components.feature.readerview.ReaderViewMiddleware +import mozilla.components.feature.recentlyclosed.RecentlyClosedMiddleware import mozilla.components.feature.session.HistoryDelegate import mozilla.components.feature.top.sites.DefaultTopSitesStorage import mozilla.components.feature.top.sites.PinnedSiteStorage @@ -140,12 +142,15 @@ class Core(private val context: Context, private val crashReporter: CrashReporti val store by lazy { BrowserStore( middleware = listOf( + RecentlyClosedMiddleware(context, RECENTLY_CLOSED_MAX, engine), MediaMiddleware(context, MediaService::class.java), DownloadMiddleware(context, DownloadService::class.java), ReaderViewMiddleware(), ThumbnailsMiddleware(thumbnailStorage) ) + EngineMiddleware.create(engine, ::findSessionById) - ) + ).also { + it.dispatch(RecentlyClosedAction.InitializeRecentlyClosedState) + } } private fun findSessionById(tabId: String): Session? { @@ -344,7 +349,7 @@ class Core(private val context: Context, private val crashReporter: CrashReporti fun getPreferredColorScheme(): PreferredColorScheme { val inDark = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == - Configuration.UI_MODE_NIGHT_YES + Configuration.UI_MODE_NIGHT_YES return when { context.settings().shouldUseDarkTheme -> PreferredColorScheme.Dark context.settings().shouldUseLightTheme -> PreferredColorScheme.Light @@ -357,5 +362,6 @@ class Core(private val context: Context, private val crashReporter: CrashReporti private const val KEY_STRENGTH = 256 private const val KEY_STORAGE_NAME = "core_prefs" private const val PASSWORDS_KEY = "passwords" + private const val RECENTLY_CLOSED_MAX = 5 } } diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 5ddd53818..bc392ac14 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -477,7 +477,7 @@ class HomeFragment : Fragment() { .let { SessionManager.Snapshot(it, selectedIndex) } tabs.forEach { - sessionManager.remove(it) + requireComponents.useCases.tabsUseCases.removeTab(it) } val snackbarMessage = if (sessionCode == ALL_PRIVATE_TABS) { @@ -505,7 +505,7 @@ class HomeFragment : Fragment() { val isSelected = session.id == requireComponents.core.store.state.selectedTabId ?: false - sessionManager.remove(session) + requireComponents.useCases.tabsUseCases.removeTab(sessionId) val snackbarMessage = if (snapshot.session.private) { requireContext().getString(R.string.snackbar_private_tab_closed) diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt index 234082b74..7a0b33b1a 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt @@ -27,9 +27,9 @@ enum class HistoryItemTimeGroup { } } -class HistoryAdapter( - private val historyInteractor: HistoryInteractor -) : PagedListAdapter(historyDiffCallback), SelectionHolder { +class HistoryAdapter(private val historyInteractor: HistoryInteractor) : + PagedListAdapter(historyDiffCallback), + SelectionHolder { private var mode: HistoryFragmentState.Mode = HistoryFragmentState.Mode.Normal override val selectedItems get() = mode.selectedItems diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryController.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryController.kt index 46a99952e..069afb1df 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryController.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryController.kt @@ -8,6 +8,7 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.res.Resources import androidx.navigation.NavController +import androidx.navigation.NavOptions import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import mozilla.components.concept.engine.prompt.ShareData @@ -15,6 +16,7 @@ import org.mozilla.fenix.R import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.components.FenixSnackbar +@Suppress("TooManyFunctions") interface HistoryController { fun handleOpen(item: HistoryItem, mode: BrowsingMode? = null) fun handleSelect(item: HistoryItem) @@ -26,8 +28,10 @@ interface HistoryController { fun handleCopyUrl(item: HistoryItem) fun handleShare(item: HistoryItem) fun handleRequestSync() + fun handleEnterRecentlyClosed() } +@Suppress("TooManyFunctions") class DefaultHistoryController( private val store: HistoryFragmentStore, private val navController: NavController, @@ -101,4 +105,11 @@ class DefaultHistoryController( store.dispatch(HistoryFragmentAction.FinishSync) } } + + override fun handleEnterRecentlyClosed() { + navController.navigate( + HistoryFragmentDirections.actionGlobalRecentlyClosed(), + NavOptions.Builder().setPopUpTo(R.id.recentlyClosedFragment, true).build() + ) + } } diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt index 3b5f6d7d8..b34f22bb4 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt @@ -25,6 +25,7 @@ import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch +import mozilla.components.browser.state.action.RecentlyClosedAction import mozilla.components.concept.engine.prompt.ShareData import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.service.fxa.sync.SyncReason @@ -49,12 +50,15 @@ import org.mozilla.fenix.utils.allowUndo @SuppressWarnings("TooManyFunctions", "LargeClass") class HistoryFragment : LibraryPageFragment(), UserInteractionHandler { private lateinit var historyStore: HistoryFragmentStore - private lateinit var historyView: HistoryView private lateinit var historyInteractor: HistoryInteractor private lateinit var viewModel: HistoryViewModel private var undoScope: CoroutineScope? = null private var pendingHistoryDeletionJob: (suspend () -> Unit)? = null + private var _historyView: HistoryView? = null + protected val historyView: HistoryView + get() = _historyView!! + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -91,7 +95,10 @@ class HistoryFragment : LibraryPageFragment(), UserInteractionHandl historyInteractor = HistoryInteractor( historyController ) - historyView = HistoryView(view.historyLayout, historyInteractor) + _historyView = HistoryView( + view.historyLayout, + historyInteractor + ) return view } @@ -234,6 +241,11 @@ class HistoryFragment : LibraryPageFragment(), UserInteractionHandl return historyView.onBackPressed() } + override fun onDestroyView() { + super.onDestroyView() + _historyView = null + } + private fun openItem(item: HistoryItem, mode: BrowsingMode? = null) { requireComponents.analytics.metrics.track(Event.HistoryItemOpened) @@ -255,8 +267,9 @@ class HistoryFragment : LibraryPageFragment(), UserInteractionHandl } setPositiveButton(R.string.delete_browsing_data_prompt_allow) { dialog: DialogInterface, _ -> historyStore.dispatch(HistoryFragmentAction.EnterDeletionMode) - viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.lifecycleScope.launch(IO) { requireComponents.analytics.metrics.track(Event.HistoryAllItemsRemoved) + requireComponents.core.store.dispatch(RecentlyClosedAction.RemoveAllClosedTabAction) requireComponents.core.historyStorage.deleteEverything() launch(Main) { viewModel.invalidate() diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryInteractor.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryInteractor.kt index 4ddf147a7..2c8b9056e 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryInteractor.kt @@ -61,4 +61,8 @@ class HistoryInteractor( override fun onRequestSync() { historyController.handleRequestSync() } + + override fun onRecentlyClosedClicked() { + historyController.handleEnterRecentlyClosed() + } } diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryView.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryView.kt index 09be8a3d7..c309a4fb9 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryView.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryView.kt @@ -77,6 +77,11 @@ interface HistoryViewInteractor : SelectionInteractor { * Called when the user requests a sync of the history */ fun onRequestSync() + + /** + * Called when the user clicks on recently closed tab button. + */ + fun onRecentlyClosedClicked() } /** diff --git a/app/src/main/java/org/mozilla/fenix/library/history/viewholders/HistoryListItemViewHolder.kt b/app/src/main/java/org/mozilla/fenix/library/history/viewholders/HistoryListItemViewHolder.kt index f833507e9..03016716e 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/viewholders/HistoryListItemViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/viewholders/HistoryListItemViewHolder.kt @@ -5,10 +5,12 @@ package org.mozilla.fenix.library.history.viewholders import android.view.View +import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.history_list_item.view.* import kotlinx.android.synthetic.main.library_site_item.view.* import org.mozilla.fenix.R +import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.hideAndDisable import org.mozilla.fenix.ext.showAndEnable import org.mozilla.fenix.library.SelectionHolder @@ -38,6 +40,10 @@ class HistoryListItemViewHolder( historyInteractor.onDeleteSome(selected) } } + + itemView.recently_closed.setOnClickListener { + historyInteractor.onRecentlyClosedClicked() + } } fun bind( @@ -56,7 +62,7 @@ class HistoryListItemViewHolder( itemView.history_layout.titleView.text = item.title itemView.history_layout.urlView.text = item.url - toggleDeleteButton(showDeleteButton, mode === HistoryFragmentState.Mode.Normal) + toggleTopContent(showDeleteButton, mode === HistoryFragmentState.Mode.Normal) val headerText = timeGroup?.humanReadable(itemView.context) toggleHeader(headerText) @@ -86,11 +92,11 @@ class HistoryListItemViewHolder( } } - private fun toggleDeleteButton( - showDeleteButton: Boolean, + private fun toggleTopContent( + showTopContent: Boolean, isNormalMode: Boolean ) { - if (showDeleteButton) { + if (showTopContent) { itemView.delete_button.run { visibility = View.VISIBLE @@ -102,7 +108,16 @@ class HistoryListItemViewHolder( alpha = DELETE_BUTTON_DISABLED_ALPHA } } + val numRecentTabs = itemView.context.components.core.store.state.closedTabs.size + itemView.recently_closed_tabs_description.text = String.format( + itemView.context.getString( + if (numRecentTabs == 1) + R.string.recently_closed_tab else R.string.recently_closed_tabs + ), numRecentTabs + ) + itemView.recently_closed.isVisible = true } else { + itemView.recently_closed.visibility = View.GONE itemView.delete_button.visibility = View.GONE } } diff --git a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedAdapter.kt b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedAdapter.kt new file mode 100644 index 000000000..db6121c1e --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedAdapter.kt @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.library.recentlyclosed + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import mozilla.components.browser.state.state.ClosedTab + +class RecentlyClosedAdapter( + private val interactor: RecentlyClosedFragmentInteractor +) : ListAdapter(DiffCallback) { + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): RecentlyClosedItemViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(RecentlyClosedItemViewHolder.LAYOUT_ID, parent, false) + return RecentlyClosedItemViewHolder(view, interactor) + } + + override fun onBindViewHolder(holder: RecentlyClosedItemViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + private object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: ClosedTab, newItem: ClosedTab) = + oldItem.id == newItem.id || oldItem.title == newItem.title || oldItem.url == newItem.url + + override fun areContentsTheSame(oldItem: ClosedTab, newItem: ClosedTab) = + oldItem.id == newItem.id || oldItem.title == newItem.title || oldItem.url == newItem.url + } +} diff --git a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedController.kt b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedController.kt new file mode 100644 index 000000000..0f71bf023 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedController.kt @@ -0,0 +1,86 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.library.recentlyclosed + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.res.Resources +import androidx.navigation.NavController +import androidx.navigation.NavOptions +import mozilla.components.browser.session.SessionManager +import mozilla.components.browser.state.action.RecentlyClosedAction +import mozilla.components.browser.state.state.ClosedTab +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.prompt.ShareData +import mozilla.components.feature.recentlyclosed.ext.restoreTab +import org.mozilla.fenix.BrowserDirection +import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.R +import org.mozilla.fenix.browser.browsingmode.BrowsingMode +import org.mozilla.fenix.components.FenixSnackbar + +interface RecentlyClosedController { + fun handleOpen(item: ClosedTab, mode: BrowsingMode? = null) + fun handleDeleteOne(tab: ClosedTab) + fun handleCopyUrl(item: ClosedTab) + fun handleShare(item: ClosedTab) + fun handleNavigateToHistory() + fun handleRestore(item: ClosedTab) +} + +class DefaultRecentlyClosedController( + private val navController: NavController, + private val store: BrowserStore, + private val sessionManager: SessionManager, + private val resources: Resources, + private val snackbar: FenixSnackbar, + private val clipboardManager: ClipboardManager, + private val activity: HomeActivity, + private val openToBrowser: (item: ClosedTab, mode: BrowsingMode?) -> Unit +) : RecentlyClosedController { + override fun handleOpen(item: ClosedTab, mode: BrowsingMode?) { + openToBrowser(item, mode) + } + + override fun handleDeleteOne(tab: ClosedTab) { + store.dispatch(RecentlyClosedAction.RemoveClosedTabAction(tab)) + } + + override fun handleNavigateToHistory() { + navController.navigate( + RecentlyClosedFragmentDirections.actionGlobalHistoryFragment(), + NavOptions.Builder().setPopUpTo(R.id.historyFragment, true).build() + ) + } + + override fun handleCopyUrl(item: ClosedTab) { + val urlClipData = ClipData.newPlainText(item.url, item.url) + clipboardManager.setPrimaryClip(urlClipData) + with(snackbar) { + setText(resources.getString(R.string.url_copied)) + show() + } + } + + override fun handleShare(item: ClosedTab) { + navController.navigate( + RecentlyClosedFragmentDirections.actionGlobalShareFragment( + data = arrayOf(ShareData(url = item.url, title = item.title)) + ) + ) + } + + override fun handleRestore(item: ClosedTab) { + item.restoreTab( + store, + sessionManager, + onTabRestored = { + activity.openToBrowser( + from = BrowserDirection.FromRecentlyClosed + ) + } + ) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragment.kt b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragment.kt new file mode 100644 index 000000000..743e33c18 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragment.kt @@ -0,0 +1,135 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.library.recentlyclosed + +import android.content.ClipboardManager +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import androidx.navigation.fragment.findNavController +import kotlinx.android.synthetic.main.fragment_recently_closed_tabs.view.* +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map +import mozilla.components.browser.state.state.ClosedTab +import mozilla.components.lib.state.ext.consumeFrom +import mozilla.components.lib.state.ext.flowScoped +import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged +import org.mozilla.fenix.BrowserDirection +import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.R +import org.mozilla.fenix.browser.browsingmode.BrowsingMode +import org.mozilla.fenix.components.FenixSnackbar +import org.mozilla.fenix.components.StoreProvider +import org.mozilla.fenix.ext.getRootView +import org.mozilla.fenix.ext.requireComponents +import org.mozilla.fenix.ext.showToolbar +import org.mozilla.fenix.library.LibraryPageFragment + +@Suppress("TooManyFunctions") +class RecentlyClosedFragment : LibraryPageFragment() { + private lateinit var recentlyClosedFragmentStore: RecentlyClosedFragmentStore + private var _recentlyClosedFragmentView: RecentlyClosedFragmentView? = null + protected val recentlyClosedFragmentView: RecentlyClosedFragmentView + get() = _recentlyClosedFragmentView!! + + private lateinit var recentlyClosedInteractor: RecentlyClosedFragmentInteractor + + override fun onResume() { + super.onResume() + showToolbar(getString(R.string.library_recently_closed_tabs)) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.library_menu, menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { + R.id.close_history -> { + close() + true + } + else -> super.onOptionsItemSelected(item) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val view = inflater.inflate(R.layout.fragment_recently_closed_tabs, container, false) + recentlyClosedFragmentStore = StoreProvider.get(this) { + RecentlyClosedFragmentStore( + RecentlyClosedFragmentState( + items = listOf() + ) + ) + } + recentlyClosedInteractor = RecentlyClosedFragmentInteractor( + recentlyClosedController = DefaultRecentlyClosedController( + navController = findNavController(), + store = requireComponents.core.store, + activity = activity as HomeActivity, + sessionManager = requireComponents.core.sessionManager, + resources = requireContext().resources, + snackbar = FenixSnackbar.make( + view = requireActivity().getRootView()!!, + isDisplayedWithBrowserToolbar = true + ), + clipboardManager = activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager, + openToBrowser = ::openItem + ) + ) + _recentlyClosedFragmentView = RecentlyClosedFragmentView( + view.recentlyClosedLayout, + recentlyClosedInteractor + ) + return view + } + + override fun onDestroyView() { + super.onDestroyView() + _recentlyClosedFragmentView = null + } + + private fun openItem(tab: ClosedTab, mode: BrowsingMode? = null) { + mode?.let { (activity as HomeActivity).browsingModeManager.mode = it } + + (activity as HomeActivity).openToBrowserAndLoad( + searchTermOrURL = tab.url, + newTab = true, + from = BrowserDirection.FromRecentlyClosed + ) + } + + @ExperimentalCoroutinesApi + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + consumeFrom(recentlyClosedFragmentStore) { + recentlyClosedFragmentView.update(it.items) + } + + requireComponents.core.store.flowScoped(viewLifecycleOwner) { flow -> + flow.map { state -> state.closedTabs } + .ifChanged() + .collect { tabs -> + recentlyClosedFragmentStore.dispatch( + RecentlyClosedFragmentAction.Change(tabs) + ) + } + } + } + + override val selectedItems: Set = setOf() +} diff --git a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentInteractor.kt b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentInteractor.kt new file mode 100644 index 000000000..b62b430b2 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentInteractor.kt @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.library.recentlyclosed + +import mozilla.components.browser.state.state.ClosedTab +import org.mozilla.fenix.browser.browsingmode.BrowsingMode + +/** + * Interactor for the recently closed screen + * Provides implementations for the RecentlyClosedInteractor + */ +class RecentlyClosedFragmentInteractor( + private val recentlyClosedController: RecentlyClosedController +) : RecentlyClosedInteractor { + override fun restore(item: ClosedTab) { + recentlyClosedController.handleRestore(item) + } + + override fun onCopyPressed(item: ClosedTab) { + recentlyClosedController.handleCopyUrl(item) + } + + override fun onSharePressed(item: ClosedTab) { + recentlyClosedController.handleShare(item) + } + + override fun onOpenInNormalTab(item: ClosedTab) { + recentlyClosedController.handleOpen(item, BrowsingMode.Normal) + } + + override fun onOpenInPrivateTab(item: ClosedTab) { + recentlyClosedController.handleOpen(item, BrowsingMode.Private) + } + + override fun onDeleteOne(tab: ClosedTab) { + recentlyClosedController.handleDeleteOne(tab) + } + + override fun onNavigateToHistory() { + recentlyClosedController.handleNavigateToHistory() + } +} diff --git a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentStore.kt b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentStore.kt new file mode 100644 index 000000000..cb75dabca --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentStore.kt @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.library.recentlyclosed + +import mozilla.components.browser.state.state.ClosedTab +import mozilla.components.lib.state.Action +import mozilla.components.lib.state.State +import mozilla.components.lib.state.Store + +/** + * The [Store] for holding the [RecentlyClosedFragmentState] and applying [RecentlyClosedFragmentAction]s. + */ +class RecentlyClosedFragmentStore(initialState: RecentlyClosedFragmentState) : + Store( + initialState, + ::recentlyClosedStateReducer + ) + +/** + * Actions to dispatch through the `RecentlyClosedFragmentStore` to modify + * `RecentlyClosedFragmentState` through the reducer. + */ +sealed class RecentlyClosedFragmentAction : Action { + data class Change(val list: List) : RecentlyClosedFragmentAction() +} + +/** + * The state for the Recently Closed Screen + * @property items List of recently closed tabs to display + */ +data class RecentlyClosedFragmentState(val items: List = emptyList()) : State + +/** + * The RecentlyClosedFragmentState Reducer. + */ +private fun recentlyClosedStateReducer( + state: RecentlyClosedFragmentState, + action: RecentlyClosedFragmentAction +): RecentlyClosedFragmentState { + return when (action) { + is RecentlyClosedFragmentAction.Change -> state.copy(items = action.list) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentView.kt b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentView.kt new file mode 100644 index 000000000..0ec06e48e --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentView.kt @@ -0,0 +1,110 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.library.recentlyclosed + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.component_recently_closed.* +import mozilla.components.browser.state.state.ClosedTab +import org.mozilla.fenix.R + +interface RecentlyClosedInteractor { + /** + * Called when an item is tapped to restore it. + * + * @param item the tapped item to restore. + */ + fun restore(item: ClosedTab) + + /** + * Called when the view more history option is tapped. + */ + fun onNavigateToHistory() + + /** + * Copies the URL of a recently closed tab item to the copy-paste buffer. + * + * @param item the recently closed tab item to copy the URL from + */ + fun onCopyPressed(item: ClosedTab) + + /** + * Opens the share sheet for a recently closed tab item. + * + * @param item the recently closed tab item to share + */ + fun onSharePressed(item: ClosedTab) + + /** + * Opens a recently closed tab item in a new tab. + * + * @param item the recently closed tab item to open in a new tab + */ + fun onOpenInNormalTab(item: ClosedTab) + + /** + * Opens a recently closed tab item in a private tab. + * + * @param item the recently closed tab item to open in a private tab + */ + fun onOpenInPrivateTab(item: ClosedTab) + + /** + * Deletes one recently closed tab item. + * + * @param item the recently closed tab item to delete. + */ + fun onDeleteOne(tab: ClosedTab) +} + +/** + * View that contains and configures the Recently Closed List + */ +class RecentlyClosedFragmentView( + container: ViewGroup, + private val interactor: RecentlyClosedFragmentInteractor +) : LayoutContainer { + + override val containerView: ConstraintLayout = LayoutInflater.from(container.context) + .inflate(R.layout.component_recently_closed, container, true) + .findViewById(R.id.recently_closed_wrapper) + + private val recentlyClosedAdapter: RecentlyClosedAdapter = RecentlyClosedAdapter(interactor) + + init { + recently_closed_list.apply { + layoutManager = LinearLayoutManager(containerView.context) + adapter = recentlyClosedAdapter + } + + view_more_history.apply { + titleView.text = + containerView.context.getString(R.string.recently_closed_show_full_history) + urlView.isVisible = false + overflowView.isVisible = false + iconView.background = null + iconView.setImageDrawable( + ContextCompat.getDrawable( + containerView.context, + R.drawable.ic_history + ) + ) + setOnClickListener { + interactor.onNavigateToHistory() + } + } + } + + fun update(items: List) { + recently_closed_empty_view.isVisible = items.isEmpty() + recently_closed_list.isVisible = items.isNotEmpty() + recentlyClosedAdapter.submitList(items) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedItemViewHolder.kt b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedItemViewHolder.kt new file mode 100644 index 000000000..e60cc34ea --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedItemViewHolder.kt @@ -0,0 +1,68 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.library.recentlyclosed + +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.synthetic.main.history_list_item.view.* +import mozilla.components.browser.state.state.ClosedTab +import org.mozilla.fenix.R +import org.mozilla.fenix.library.history.HistoryItemMenu +import org.mozilla.fenix.utils.Do + +class RecentlyClosedItemViewHolder( + view: View, + private val recentlyClosedFragmentInteractor: RecentlyClosedFragmentInteractor +) : RecyclerView.ViewHolder(view) { + + private var item: ClosedTab? = null + + init { + setupMenu() + } + + fun bind( + item: ClosedTab + ) { + itemView.history_layout.titleView.text = + if (item.title.isNotEmpty()) item.title else item.url + itemView.history_layout.urlView.text = item.url + + if (this.item?.url != item.url) { + itemView.history_layout.loadFavicon(item.url) + } + + itemView.setOnClickListener { + recentlyClosedFragmentInteractor.restore(item) + } + + this.item = item + } + + private fun setupMenu() { + val historyMenu = HistoryItemMenu(itemView.context) { + val item = this.item ?: return@HistoryItemMenu + Do exhaustive when (it) { + HistoryItemMenu.Item.Copy -> recentlyClosedFragmentInteractor.onCopyPressed(item) + HistoryItemMenu.Item.Share -> recentlyClosedFragmentInteractor.onSharePressed(item) + HistoryItemMenu.Item.OpenInNewTab -> recentlyClosedFragmentInteractor.onOpenInNormalTab( + item + ) + HistoryItemMenu.Item.OpenInPrivateTab -> recentlyClosedFragmentInteractor.onOpenInPrivateTab( + item + ) + HistoryItemMenu.Item.Delete -> recentlyClosedFragmentInteractor.onDeleteOne( + item + ) + } + } + + itemView.history_layout.attachMenu(historyMenu.menuController) + } + + companion object { + const val LAYOUT_ID = R.layout.history_list_item + } +} diff --git a/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuit.kt b/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuit.kt index 6f87de29d..8550cd650 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuit.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuit.kt @@ -26,6 +26,7 @@ fun deleteAndQuit(activity: Activity, coroutineScope: CoroutineScope, snackbar: activity.components.useCases.tabsUseCases.removeAllTabs, activity.components.core.historyStorage, activity.components.core.permissionStorage, + activity.components.core.store, activity.components.core.icons, activity.components.core.engine, coroutineContext diff --git a/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataController.kt b/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataController.kt index 575e8e969..7b02cd526 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataController.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataController.kt @@ -7,6 +7,8 @@ package org.mozilla.fenix.settings.deletebrowsingdata import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import mozilla.components.browser.icons.BrowserIcons +import mozilla.components.browser.state.action.RecentlyClosedAction +import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.Engine import mozilla.components.concept.storage.HistoryStorage import mozilla.components.feature.tabs.TabsUseCases @@ -25,6 +27,7 @@ class DefaultDeleteBrowsingDataController( private val removeAllTabs: TabsUseCases.RemoveAllTabsUseCase, private val historyStorage: HistoryStorage, private val permissionStorage: PermissionStorage, + private val store: BrowserStore, private val iconsStorage: BrowserIcons, private val engine: Engine, private val coroutineContext: CoroutineContext = Dispatchers.Main @@ -41,6 +44,7 @@ class DefaultDeleteBrowsingDataController( engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.DOM_STORAGES)) historyStorage.deleteEverything() iconsStorage.clear() + store.dispatch(RecentlyClosedAction.RemoveAllClosedTabAction) } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt index 733ae3ba2..77f867494 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt @@ -28,7 +28,6 @@ import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged import org.mozilla.fenix.R import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar @@ -45,11 +44,12 @@ class DeleteBrowsingDataFragment : Fragment(R.layout.fragment_delete_browsing_da super.onViewCreated(view, savedInstanceState) controller = DefaultDeleteBrowsingDataController( - requireContext().components.useCases.tabsUseCases.removeAllTabs, - requireContext().components.core.historyStorage, - requireContext().components.core.permissionStorage, - requireContext().components.core.icons, - requireContext().components.core.engine + requireComponents.useCases.tabsUseCases.removeAllTabs, + requireComponents.core.historyStorage, + requireComponents.core.permissionStorage, + requireComponents.core.store, + requireComponents.core.icons, + requireComponents.core.engine ) settings = requireContext().settings() diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt index ea1a4abf0..431bfe898 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt @@ -42,6 +42,7 @@ interface TabTrayController { fun handleRemoveSelectedTab(tab: Tab) fun handleOpenTab(tab: Tab) fun handleEnterMultiselect() + fun handleRecentlyClosedClicked() } /** @@ -178,4 +179,9 @@ class DefaultTabTrayController( override fun handleEnterMultiselect() { tabTrayDialogFragmentStore.dispatch(TabTrayDialogFragmentAction.EnterMultiSelectMode) } + + override fun handleRecentlyClosedClicked() { + val directions = TabTrayDialogFragmentDirections.actionGlobalRecentlyClosed() + navController.navigate(directions) + } } diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractor.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractor.kt index 2ec18d928..3cf89a386 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractor.kt @@ -73,6 +73,11 @@ interface TabTrayInteractor { * Called when multiselect mode should be entered with no tabs selected. */ fun onEnterMultiselect() + + /** + * Called when user clicks the recently closed tabs menu button. + */ + fun onOpenRecentlyClosedClicked() } /** @@ -92,6 +97,10 @@ class TabTrayFragmentInteractor(private val controller: TabTrayController) : Tab controller.handleTabSettingsClicked() } + override fun onOpenRecentlyClosedClicked() { + controller.handleRecentlyClosedClicked() + } + override fun onShareTabsClicked(private: Boolean) { controller.onShareTabsClicked(private) } diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt index 3a237e22a..92e809bd7 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt @@ -221,6 +221,7 @@ class TabTrayView( is TabTrayItemMenu.Item.CloseAllTabs -> interactor.onCloseAllTabsClicked( isPrivateModeSelected ) + is TabTrayItemMenu.Item.OpenRecentlyClosed -> interactor.onOpenRecentlyClosedClicked() } } @@ -596,6 +597,7 @@ class TabTrayItemMenu( object OpenTabSettings : Item() object SaveToCollection : Item() object CloseAllTabs : Item() + object OpenRecentlyClosed : Item() } val menuBuilder by lazy { BrowserMenuBuilder(menuItems) } @@ -625,6 +627,13 @@ class TabTrayItemMenu( onItemTapped.invoke(Item.OpenTabSettings) }, + SimpleBrowserMenuItem( + context.getString(R.string.tab_tray_menu_recently_closed), + textColorResource = R.color.primary_text_normal_theme + ) { + onItemTapped.invoke(Item.OpenRecentlyClosed) + }, + SimpleBrowserMenuItem( context.getString(R.string.tab_tray_menu_item_close), textColorResource = R.color.primary_text_normal_theme diff --git a/app/src/main/res/layout/component_recently_closed.xml b/app/src/main/res/layout/component_recently_closed.xml new file mode 100644 index 000000000..e0b70db2c --- /dev/null +++ b/app/src/main/res/layout/component_recently_closed.xml @@ -0,0 +1,38 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_recently_closed_tabs.xml b/app/src/main/res/layout/fragment_recently_closed_tabs.xml new file mode 100644 index 000000000..bfeca1ab4 --- /dev/null +++ b/app/src/main/res/layout/fragment_recently_closed_tabs.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/history_list_item.xml b/app/src/main/res/layout/history_list_item.xml index a40877e72..147d44820 100644 --- a/app/src/main/res/layout/history_list_item.xml +++ b/app/src/main/res/layout/history_list_item.xml @@ -2,12 +2,12 @@ - + - + + + + + android:textSize="18sp" + app:layout_constraintBottom_toTopOf="@+id/recently_closed_tabs_description" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/icon" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" + app:layout_goneMarginEnd="@dimen/library_item_icon_margin_horizontal" /> + + + + + - diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 866cdf46b..a91a57421 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -31,6 +31,10 @@ android:id="@+id/action_global_search_dialog" app:destination="@id/searchDialogFragment" /> + + @@ -177,6 +181,11 @@ app:argType="org.mozilla.fenix.components.metrics.Event$PerformedSearch$SearchAccessPoint" /> + + { + + override fun match(arg: NavOptions?): Boolean = + value.popUpTo == arg?.popUpTo && value.isPopUpToInclusive == arg.isPopUpToInclusive + + override fun substitute(map: Map) = + copy(value = value.internalSubstitute(map)) +} + private data class EqIntentFilterMatcher(private val value: Intent) : Matcher { override fun match(arg: Intent?): Boolean = value.filterEquals(arg) diff --git a/app/src/test/java/org/mozilla/fenix/library/recentlyclosed/DefaultRecentlyClosedControllerTest.kt b/app/src/test/java/org/mozilla/fenix/library/recentlyclosed/DefaultRecentlyClosedControllerTest.kt new file mode 100644 index 000000000..1d2a767a1 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/library/recentlyclosed/DefaultRecentlyClosedControllerTest.kt @@ -0,0 +1,171 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.library.recentlyclosed + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.res.Resources +import androidx.navigation.NavController +import androidx.navigation.NavOptions +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.slot +import io.mockk.unmockkStatic +import io.mockk.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineDispatcher +import mozilla.components.browser.session.SessionManager +import mozilla.components.browser.state.action.RecentlyClosedAction +import mozilla.components.browser.state.state.ClosedTab +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.prompt.ShareData +import mozilla.components.feature.recentlyclosed.ext.restoreTab +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.R +import org.mozilla.fenix.browser.browsingmode.BrowsingMode +import org.mozilla.fenix.components.FenixSnackbar +import org.mozilla.fenix.ext.directionsEq +import org.mozilla.fenix.ext.optionsEq +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +// Robolectric needed for `onShareItem()` +@ExperimentalCoroutinesApi +@RunWith(FenixRobolectricTestRunner::class) +class DefaultRecentlyClosedControllerTest { + private val dispatcher = TestCoroutineDispatcher() + private val navController: NavController = mockk(relaxed = true) + private val resources: Resources = mockk(relaxed = true) + private val snackbar: FenixSnackbar = mockk(relaxed = true) + private val clipboardManager: ClipboardManager = mockk(relaxed = true) + private val openToBrowser: (ClosedTab, BrowsingMode?) -> Unit = mockk(relaxed = true) + private val sessionManager: SessionManager = mockk(relaxed = true) + private val activity: HomeActivity = mockk(relaxed = true) + private val store: BrowserStore = mockk(relaxed = true) + val mockedTab: ClosedTab = mockk(relaxed = true) + + private val controller = DefaultRecentlyClosedController( + navController, + store, + sessionManager, + resources, + snackbar, + clipboardManager, + activity, + openToBrowser + ) + + @Before + fun setUp() { + mockkStatic("mozilla.components.feature.recentlyclosed.ext.ClosedTabKt") + every { mockedTab.restoreTab(any(), any(), any()) } just Runs + } + + @After + fun tearDown() { + dispatcher.cleanupTestCoroutines() + unmockkStatic("mozilla.components.feature.recentlyclosed.ext.ClosedTabKt") + } + + @Test + fun handleOpen() { + val item: ClosedTab = mockk(relaxed = true) + + controller.handleOpen(item, BrowsingMode.Private) + + verify { + openToBrowser(item, BrowsingMode.Private) + } + + controller.handleOpen(item, BrowsingMode.Normal) + + verify { + openToBrowser(item, BrowsingMode.Normal) + } + } + + @Test + fun handleDeleteOne() { + val item: ClosedTab = mockk(relaxed = true) + + controller.handleDeleteOne(item) + + verify { + store.dispatch(RecentlyClosedAction.RemoveClosedTabAction(item)) + } + } + + @Test + fun handleNavigateToHistory() { + controller.handleNavigateToHistory() + + verify { + navController.navigate( + directionsEq( + RecentlyClosedFragmentDirections.actionGlobalHistoryFragment() + ), + optionsEq(NavOptions.Builder().setPopUpTo(R.id.historyFragment, true).build()) + ) + } + } + + @Test + fun handleCopyUrl() { + val item = ClosedTab(id = "tab-id", title = "Mozilla", url = "mozilla.org", createdAt = 1L) + + val clipdata = slot() + + controller.handleCopyUrl(item) + + verify { + clipboardManager.setPrimaryClip(capture(clipdata)) + snackbar.show() + } + + assertEquals(1, clipdata.captured.itemCount) + assertEquals("mozilla.org", clipdata.captured.description.label) + assertEquals("mozilla.org", clipdata.captured.getItemAt(0).text) + } + + @Test + @Suppress("UNCHECKED_CAST") + fun handleShare() { + val item = ClosedTab(id = "tab-id", title = "Mozilla", url = "mozilla.org", createdAt = 1L) + + controller.handleShare(item) + + verify { + navController.navigate( + directionsEq( + RecentlyClosedFragmentDirections.actionGlobalShareFragment( + data = arrayOf(ShareData(url = item.url, title = item.title)) + ) + ) + ) + } + } + + @Test + fun handleRestore() { + controller.handleRestore(mockedTab) + + dispatcher.advanceUntilIdle() + + verify { + mockedTab.restoreTab( + store, + sessionManager, + onTabRestored = any() + ) + } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentInteractorTest.kt new file mode 100644 index 000000000..c4242bc03 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentInteractorTest.kt @@ -0,0 +1,96 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.library.recentlyclosed + +import io.mockk.mockk +import io.mockk.verify +import mozilla.components.browser.state.state.ClosedTab +import org.junit.Before +import org.junit.Test +import org.mozilla.fenix.browser.browsingmode.BrowsingMode + +class RecentlyClosedFragmentInteractorTest { + + lateinit var interactor: RecentlyClosedFragmentInteractor + private val defaultRecentlyClosedController: DefaultRecentlyClosedController = + mockk(relaxed = true) + + @Before + fun setup() { + interactor = + RecentlyClosedFragmentInteractor( + recentlyClosedController = defaultRecentlyClosedController + ) + } + + @Test + fun open() { + val tab = ClosedTab(id = "tab-id", title = "Mozilla", url = "mozilla.org", createdAt = 1L) + interactor.restore(tab) + + verify { + defaultRecentlyClosedController.handleRestore(tab) + } + } + + @Test + fun onCopyPressed() { + val tab = ClosedTab(id = "tab-id", title = "Mozilla", url = "mozilla.org", createdAt = 1L) + interactor.onCopyPressed(tab) + + verify { + defaultRecentlyClosedController.handleCopyUrl(tab) + } + } + + @Test + fun onSharePressed() { + val tab = ClosedTab(id = "tab-id", title = "Mozilla", url = "mozilla.org", createdAt = 1L) + interactor.onSharePressed(tab) + + verify { + defaultRecentlyClosedController.handleShare(tab) + } + } + + @Test + fun onOpenInNormalTab() { + val tab = ClosedTab(id = "tab-id", title = "Mozilla", url = "mozilla.org", createdAt = 1L) + interactor.onOpenInNormalTab(tab) + + verify { + defaultRecentlyClosedController.handleOpen(tab, mode = BrowsingMode.Normal) + } + } + + @Test + fun onOpenInPrivateTab() { + val tab = ClosedTab(id = "tab-id", title = "Mozilla", url = "mozilla.org", createdAt = 1L) + interactor.onOpenInPrivateTab(tab) + + verify { + defaultRecentlyClosedController.handleOpen(tab, mode = BrowsingMode.Private) + } + } + + @Test + fun onDeleteOne() { + val tab = ClosedTab(id = "tab-id", title = "Mozilla", url = "mozilla.org", createdAt = 1L) + interactor.onDeleteOne(tab) + + verify { + defaultRecentlyClosedController.handleDeleteOne(tab) + } + } + + @Test + fun onNavigateToHistory() { + interactor.onNavigateToHistory() + + verify { + defaultRecentlyClosedController.handleNavigateToHistory() + } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/settings/deletebrowsingdata/DefaultDeleteBrowsingDataControllerTest.kt b/app/src/test/java/org/mozilla/fenix/settings/deletebrowsingdata/DefaultDeleteBrowsingDataControllerTest.kt index 0d300350b..605a81492 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/deletebrowsingdata/DefaultDeleteBrowsingDataControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/deletebrowsingdata/DefaultDeleteBrowsingDataControllerTest.kt @@ -13,6 +13,8 @@ import kotlinx.coroutines.GlobalScope.coroutineContext import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.runBlockingTest import mozilla.components.browser.icons.BrowserIcons +import mozilla.components.browser.state.action.RecentlyClosedAction +import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.Engine import mozilla.components.concept.storage.HistoryStorage import mozilla.components.feature.tabs.TabsUseCases @@ -31,6 +33,7 @@ class DefaultDeleteBrowsingDataControllerTest { private var removeAllTabs: TabsUseCases.RemoveAllTabsUseCase = mockk(relaxed = true) private var historyStorage: HistoryStorage = mockk(relaxed = true) private var permissionStorage: PermissionStorage = mockk(relaxed = true) + private var store: BrowserStore = mockk(relaxed = true) private var iconsStorage: BrowserIcons = mockk(relaxed = true) private val engine: Engine = mockk(relaxed = true) private lateinit var controller: DefaultDeleteBrowsingDataController @@ -40,6 +43,7 @@ class DefaultDeleteBrowsingDataControllerTest { controller = DefaultDeleteBrowsingDataController( removeAllTabs = removeAllTabs, historyStorage = historyStorage, + store = store, permissionStorage = permissionStorage, iconsStorage = iconsStorage, engine = engine, @@ -65,6 +69,7 @@ class DefaultDeleteBrowsingDataControllerTest { coVerify { engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.DOM_STORAGES)) historyStorage.deleteEverything() + store.dispatch(RecentlyClosedAction.RemoveAllClosedTabAction) iconsStorage.clear() } } diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index c10b9775e..ff7344e09 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -111,6 +111,7 @@ object Deps { const val mozilla_feature_site_permissions = "org.mozilla.components:feature-sitepermissions:${Versions.mozilla_android_components}" const val mozilla_feature_readerview = "org.mozilla.components:feature-readerview:${Versions.mozilla_android_components}" const val mozilla_feature_tab_collections = "org.mozilla.components:feature-tab-collections:${Versions.mozilla_android_components}" + const val mozilla_feature_recentlyclosed = "org.mozilla.components:feature-recentlyclosed:${Versions.mozilla_android_components}" const val mozilla_feature_accounts_push = "org.mozilla.components:feature-accounts-push:${Versions.mozilla_android_components}" const val mozilla_feature_top_sites = "org.mozilla.components:feature-top-sites:${Versions.mozilla_android_components}" const val mozilla_feature_share = "org.mozilla.components:feature-share:${Versions.mozilla_android_components}" From 79180250eae38cdf00d842bff4578c0cfe9e04f1 Mon Sep 17 00:00:00 2001 From: Jeff Boek Date: Fri, 11 Sep 2020 11:55:59 -0700 Subject: [PATCH 18/22] For #14283 - Fixes overlapping search suggestions and hint --- .../fenix/searchdialog/SearchDialogFragment.kt | 5 +++-- app/src/main/res/layout/fragment_search_dialog.xml | 13 ++++++++++--- app/src/main/res/layout/search_suggestions_hint.xml | 1 + 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt index b5c7e9a88..695df19f2 100644 --- a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt @@ -416,11 +416,12 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { clear(toolbar.id, TOP) connect(toolbar.id, BOTTOM, PARENT_ID, BOTTOM) - clear(awesome_bar.id, TOP) clear(pill_wrapper.id, BOTTOM) - connect(awesome_bar.id, TOP, PARENT_ID, TOP) connect(pill_wrapper.id, BOTTOM, toolbar.id, TOP) + clear(search_suggestions_hint.id, TOP) + connect(search_suggestions_hint.id, TOP, PARENT_ID, TOP) + clear(fill_link_from_clipboard.id, TOP) connect(fill_link_from_clipboard.id, BOTTOM, pill_wrapper.id, TOP) diff --git a/app/src/main/res/layout/fragment_search_dialog.xml b/app/src/main/res/layout/fragment_search_dialog.xml index ddef5fdec..fec3110b8 100644 --- a/app/src/main/res/layout/fragment_search_dialog.xml +++ b/app/src/main/res/layout/fragment_search_dialog.xml @@ -42,8 +42,7 @@ app:layout_constraintBottom_toTopOf="@+id/pill_wrapper" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/toolbar" - mozac:awesomeBarChipBackgroundColor="@color/photonGreen50" + app:layout_constraintTop_toBottomOf="@id/top_barrier" mozac:awesomeBarDescriptionTextColor="?secondaryText" mozac:awesomeBarTitleTextColor="?primaryText" /> @@ -55,7 +54,15 @@ android:layout="@layout/search_suggestions_hint" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toBottomOf="@id/toolbar" /> + + + Date: Fri, 11 Sep 2020 14:56:03 -0500 Subject: [PATCH 19/22] Access shared prefs in fxa pairing (#14997) --- .../mozilla/fenix/settings/PairFragment.kt | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt index b7a20951c..224efa237 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt @@ -32,7 +32,6 @@ import org.mozilla.fenix.ext.showToolbar class PairFragment : Fragment(R.layout.fragment_pair), UserInteractionHandler { private val qrFeature = ViewBoundFeatureWrapper() - private val preferences = PreferenceManager.getDefaultSharedPreferences(context) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -71,10 +70,11 @@ class PairFragment : Fragment(R.layout.fragment_pair), UserInteractionHandler { view = view ) - val cameraPermissionsDenied = preferences.getBoolean( - getPreferenceKey(R.string.pref_key_camera_permissions), - false - ) + val cameraPermissionsDenied = PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean( + getPreferenceKey(R.string.pref_key_camera_permissions), + false + ) qrFeature.withFeature { if (cameraPermissionsDenied) { @@ -116,13 +116,15 @@ class PairFragment : Fragment(R.layout.fragment_pair), UserInteractionHandler { qrFeature.withFeature { it.onPermissionsResult(permissions, grantResults) } - preferences.edit().putBoolean( - getPreferenceKey(R.string.pref_key_camera_permissions), false - ).apply() + PreferenceManager.getDefaultSharedPreferences(context) + .edit().putBoolean( + getPreferenceKey(R.string.pref_key_camera_permissions), false + ).apply() } else { - preferences.edit().putBoolean( - getPreferenceKey(R.string.pref_key_camera_permissions), true - ).apply() + PreferenceManager.getDefaultSharedPreferences(context) + .edit().putBoolean( + getPreferenceKey(R.string.pref_key_camera_permissions), true + ).apply() findNavController().popBackStack(R.id.turnOnSyncFragment, false) } } From b5cf484360daf2ae39bebeef17b384641a8fd46b Mon Sep 17 00:00:00 2001 From: "codrut.topliceanu" Date: Wed, 9 Sep 2020 17:30:13 +0300 Subject: [PATCH 20/22] For #14559 - Persist searchFragment keyboard status --- .../searchdialog/SearchDialogFragment.kt | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt index 695df19f2..ff56f62f2 100644 --- a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt @@ -55,6 +55,7 @@ import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.getPreferenceKey +import org.mozilla.fenix.ext.isKeyboardVisible import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings import org.mozilla.fenix.search.SearchFragmentAction @@ -82,12 +83,17 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { private val qrFeature = ViewBoundFeatureWrapper() private val speechIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH) + private var keyboardVisible: Boolean = false + override fun onStart() { super.onStart() // https://github.com/mozilla-mobile/fenix/issues/14279 // To prevent GeckoView from resizing we're going to change the softInputMode to not adjust // the size of the window. requireActivity().window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING) + if (keyboardVisible) { + toolbarView.view.edit.focus() + } } override fun onStop() { @@ -95,6 +101,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { // https://github.com/mozilla-mobile/fenix/issues/14279 // Let's reset back to the default behavior after we're done searching requireActivity().window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) + keyboardVisible = toolbarView.view.isKeyboardVisible() } override fun onCreate(savedInstanceState: Bundle?) { @@ -142,7 +149,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { metrics = requireComponents.analytics.metrics, dismissDialog = { dismissAllowingStateLoss() }, clearToolbarFocus = { - toolbarView.view.hideKeyboard() + toolbarView.view.hideKeyboardAndSave() toolbarView.view.clearFocus() } ) @@ -167,7 +174,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { setShortcutsChangedListener(FenixSearchEngineProvider.PREF_FILE_SEARCH_ENGINES) view.awesome_bar.setOnTouchListener { _, _ -> - view.hideKeyboard() + view.hideKeyboardAndSave() false } @@ -190,7 +197,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { setupConstraints(view) search_wrapper.setOnClickListener { - it.hideKeyboard() + it.hideKeyboardAndSave() dismissAllowingStateLoss() } @@ -324,7 +331,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { true } else -> { - view?.hideKeyboard() + view?.hideKeyboardAndSave() dismissAllowingStateLoss() true } @@ -453,6 +460,15 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { ) } + /** + * Used to save keyboard status on stop/sleep, to be restored later. + * See #14559 + * */ + private fun View.hideKeyboardAndSave() { + keyboardVisible = false + this.hideKeyboard() + } + private fun launchVoiceSearch() { // Note if a user disables speech while the app is on the search fragment // the voice button will still be available and *will* cause a crash if tapped, From 75aa2d413adc44fa5d247fdf90f03530a2141683 Mon Sep 17 00:00:00 2001 From: Mugurell Date: Fri, 11 Sep 2020 18:13:02 +0300 Subject: [PATCH 21/22] For #14974 - Ensure website bottom elements stay at bottom engineView.setDynamicToolbarMaxHeight(0) vs engineView.setDynamicToolbarMaxHeight(toolbarHeight) ensures webpage's bottom elements are aligned to the bottom of the browser. We also need to make sure that when the toolbar is static it does not cover the bottom of the page - something desired when the toolbar was dynamic. For this the engineView will have a toolbarHeight bottom margin. --- .../fenix/browser/BaseBrowserFragment.kt | 41 +++++++++++++------ .../toolbar/BrowserToolbarController.kt | 5 ++- .../DefaultBrowserToolbarControllerTest.kt | 14 ++++++- 3 files changed, 45 insertions(+), 15 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 315f20704..c05a5bf8a 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -770,20 +770,35 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session } private fun initializeEngineView(toolbarHeight: Int) { - engineView.setDynamicToolbarMaxHeight(toolbarHeight) - val context = requireContext() - val behavior = when (context.settings().toolbarPosition) { - ToolbarPosition.BOTTOM -> EngineViewBottomBehavior(context, null) - ToolbarPosition.TOP -> SwipeRefreshScrollingViewBehavior( - context, - null, - engineView, - browserToolbarView - ) - } - (swipeRefresh.layoutParams as CoordinatorLayout.LayoutParams).behavior = behavior + if (context.settings().isDynamicToolbarEnabled) { + engineView.setDynamicToolbarMaxHeight(toolbarHeight) + + val behavior = when (context.settings().toolbarPosition) { + // Set engineView dynamic vertical clipping depending on the toolbar position. + ToolbarPosition.BOTTOM -> EngineViewBottomBehavior(context, null) + // Set scroll flags depending on if if the browser or the website is doing the scroll. + ToolbarPosition.TOP -> SwipeRefreshScrollingViewBehavior( + context, + null, + engineView, + browserToolbarView + ) + } + + (swipeRefresh.layoutParams as CoordinatorLayout.LayoutParams).behavior = behavior + } else { + // Ensure webpage's bottom elements are aligned to the very bottom of the engineView. + engineView.setDynamicToolbarMaxHeight(0) + + // Effectively place the engineView on top of the toolbar if that is not dynamic. + if (context.settings().shouldUseBottomToolbar) { + val browserEngine = swipeRefresh.layoutParams as CoordinatorLayout.LayoutParams + browserEngine.bottomMargin = + requireContext().resources.getDimensionPixelSize(R.dimen.browser_toolbar_height) + } + } } /** @@ -1097,7 +1112,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session if (webAppToolbarShouldBeVisible) { browserToolbarView.view.isVisible = true val toolbarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height) - engineView.setDynamicToolbarMaxHeight(toolbarHeight) + initializeEngineView(toolbarHeight) } } } 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 86f32927c..5b70eae28 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 @@ -22,6 +22,7 @@ import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.sessionsOfType +import org.mozilla.fenix.ext.settings /** * An interface that handles the view manipulation of the BrowserToolbar, triggered by the Interactor @@ -156,7 +157,9 @@ class DefaultBrowserToolbarController( } override fun handleScroll(offset: Int) { - engineView.setVerticalClipping(offset) + if (activity.settings().isDynamicToolbarEnabled) { + engineView.setVerticalClipping(offset) + } } companion object { 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 a25367093..124cf312a 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 @@ -37,6 +37,7 @@ import org.mozilla.fenix.browser.readermode.ReaderModeController import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.settings import org.mozilla.fenix.helpers.FenixRobolectricTestRunner @RunWith(FenixRobolectricTestRunner::class) @@ -241,12 +242,23 @@ class DefaultBrowserToolbarControllerTest { } @Test - fun handleScroll() { + fun `handleScroll for dynamic toolbars`() { val controller = createController() + every { activity.settings().isDynamicToolbarEnabled } returns true + controller.handleScroll(10) verify { engineView.setVerticalClipping(10) } } + @Test + fun `handleScroll for static toolbars`() { + val controller = createController() + every { activity.settings().isDynamicToolbarEnabled } returns false + + controller.handleScroll(10) + verify(exactly = 0) { engineView.setVerticalClipping(10) } + } + private fun createController( activity: HomeActivity = this.activity, customTabSession: Session? = null, From 05857ba55db5e173477f017cdc6757654d4486cc Mon Sep 17 00:00:00 2001 From: Gabriel Luong Date: Thu, 10 Sep 2020 16:25:40 -0400 Subject: [PATCH 22/22] For #14145 - Add pinned badge for pinned sites --- .../topsites/TopSiteItemViewHolder.kt | 11 ++++- app/src/main/res/drawable/ic_pin.xml | 13 ++++++ app/src/main/res/layout/top_site_item.xml | 43 +++++++++++++++---- app/src/main/res/values/dimens.xml | 3 +- 4 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 app/src/main/res/drawable/ic_pin.xml diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolder.kt index af5bb1de5..ff7df347f 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolder.kt @@ -16,6 +16,7 @@ import mozilla.components.browser.menu.item.SimpleBrowserMenuItem import mozilla.components.feature.top.sites.TopSite import mozilla.components.feature.top.sites.TopSite.Type.DEFAULT import mozilla.components.feature.top.sites.TopSite.Type.FRECENT +import mozilla.components.feature.top.sites.TopSite.Type.PINNED import org.mozilla.fenix.R import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.loadIntoView @@ -54,8 +55,14 @@ class TopSiteItemViewHolder( } fun bind(topSite: TopSite) { - this.topSite = topSite top_site_title.text = topSite.title + + pin_indicator.visibility = if (topSite.type == PINNED) { + View.VISIBLE + } else { + View.GONE + } + when (topSite.url) { SupportUtils.POCKET_TRENDING_URL -> { favicon_image.setImageDrawable(getDrawable(itemView.context, R.drawable.ic_pocket)) @@ -64,6 +71,8 @@ class TopSiteItemViewHolder( itemView.context.components.core.icons.loadIntoView(favicon_image, topSite.url) } } + + this.topSite = topSite } private fun onTouchEvent( diff --git a/app/src/main/res/drawable/ic_pin.xml b/app/src/main/res/drawable/ic_pin.xml new file mode 100644 index 000000000..6d28ef432 --- /dev/null +++ b/app/src/main/res/drawable/ic_pin.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/layout/top_site_item.xml b/app/src/main/res/layout/top_site_item.xml index 84c926480..17be2c75d 100644 --- a/app/src/main/res/layout/top_site_item.xml +++ b/app/src/main/res/layout/top_site_item.xml @@ -2,30 +2,57 @@ - - + android:importantForAccessibility="no" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@id/favicon_image" + tools:ignore="SmallSp" /> + + + + + - + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 20c024b77..207f1dfee 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -174,7 +174,8 @@ 40dp 4dp 64dp - 12dp + 8dp + 12dp 8dp