From bc90e193a29ffbdcf2fa9d134feadb52dbe2bd9f Mon Sep 17 00:00:00 2001 From: Roger Yang Date: Thu, 8 Apr 2021 17:55:38 -0400 Subject: [PATCH] Closes #18522: Re-add call to action in tabs tray --- .../fenix/browser/infobanner/InfoBanner.kt | 6 +- .../fenix/tabstray/TabsTrayFragment.kt | 16 +- .../tabstray/TabsTrayInfoBannerBinding.kt | 124 ++++++++++ .../main/res/layout/component_tabstray2.xml | 2 +- .../fenix/tabstray/TabsTrayInfoBannerTest.kt | 221 ++++++++++++++++++ 5 files changed, 365 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInfoBannerBinding.kt create mode 100644 app/src/test/java/org/mozilla/fenix/tabstray/TabsTrayInfoBannerTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/browser/infobanner/InfoBanner.kt b/app/src/main/java/org/mozilla/fenix/browser/infobanner/InfoBanner.kt index 1d1ba4041..700803141 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/infobanner/InfoBanner.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/infobanner/InfoBanner.kt @@ -33,8 +33,10 @@ open class InfoBanner( private val dismissText: String, private val actionText: String? = null, private val dismissByHiding: Boolean = false, - private val dismissAction: (() -> Unit)? = null, - private val actionToPerform: (() -> Unit)? = null + @VisibleForTesting + internal val dismissAction: (() -> Unit)? = null, + @VisibleForTesting + internal val actionToPerform: (() -> Unit)? = null ) { @SuppressLint("InflateParams") @VisibleForTesting diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt index cced95358..ecc3efc70 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt @@ -43,7 +43,7 @@ import org.mozilla.fenix.tabstray.browser.SelectionBannerBinding.VisibilityModif import org.mozilla.fenix.tabstray.ext.showWithTheme import org.mozilla.fenix.tabstray.syncedtabs.SyncedTabsInteractor -@Suppress("TooManyFunctions") +@Suppress("TooManyFunctions", "LargeClass") class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor { private var fabView: View? = null @@ -57,6 +57,7 @@ class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor { private val floatingActionButtonBinding = ViewBoundFeatureWrapper() private val selectionBannerBinding = ViewBoundFeatureWrapper() private val selectionHandleBinding = ViewBoundFeatureWrapper() + private val tabsTrayCtaBinding = ViewBoundFeatureWrapper() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -137,6 +138,19 @@ class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor { syncedTabsTrayInteractor ) + tabsTrayCtaBinding.set( + feature = TabsTrayInfoBannerBinding( + context = view.context, + store = requireComponents.core.store, + infoBannerView = view.info_banner, + settings = requireComponents.settings, + navigationInteractor = navigationInteractor, + metrics = requireComponents.analytics.metrics + ), + owner = this, + view = view + ) + tabLayoutMediator.set( feature = TabLayoutMediator( tabLayout = tab_layout, diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInfoBannerBinding.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInfoBannerBinding.kt new file mode 100644 index 000000000..fd7c843ee --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInfoBannerBinding.kt @@ -0,0 +1,124 @@ +/* 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.tabstray + +import android.content.Context +import android.view.View.VISIBLE +import android.view.ViewGroup +import androidx.annotation.VisibleForTesting +import kotlin.math.max +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map +import mozilla.components.browser.state.selector.normalTabs +import mozilla.components.browser.state.selector.privateTabs +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.lib.state.ext.flowScoped +import mozilla.components.support.base.feature.LifecycleAwareFeature +import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged +import org.mozilla.fenix.R +import org.mozilla.fenix.browser.infobanner.InfoBanner +import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.components.metrics.MetricController +import org.mozilla.fenix.utils.Settings + +class TabsTrayInfoBannerBinding( + private val context: Context, + private val store: BrowserStore, + private val infoBannerView: ViewGroup, + private val settings: Settings, + private val navigationInteractor: NavigationInteractor, + private val metrics: MetricController? +) : LifecycleAwareFeature { + private var scope: CoroutineScope? = null + + @VisibleForTesting + internal var banner: InfoBanner? = null + + @ExperimentalCoroutinesApi + override fun start() { + scope = store.flowScoped { flow -> + flow.map { state -> max(state.normalTabs.size, state.privateTabs.size) } + .ifChanged() + .collect { tabCount -> + if (tabCount >= TAB_COUNT_SHOW_CFR) { + displayInfoBannerIfNeeded(settings) + } + } + } + } + + override fun stop() { + scope?.cancel() + } + + private fun displayInfoBannerIfNeeded(settings: Settings) { + banner = displayGridViewBannerIfNeeded(settings) + ?: displayAutoCloseTabsBannerIfNeeded(settings) + + banner?.apply { + infoBannerView.visibility = VISIBLE + showBanner() + } + } + + private fun displayGridViewBannerIfNeeded(settings: Settings): InfoBanner? { + return if ( + settings.shouldShowGridViewBanner && + settings.canShowCfr && + settings.listTabView + ) { + InfoBanner( + context = context, + message = context.getString(R.string.tab_tray_grid_view_banner_message), + dismissText = context.getString(R.string.tab_tray_grid_view_banner_negative_button_text), + actionText = context.getString(R.string.tab_tray_grid_view_banner_positive_button_text), + container = infoBannerView, + dismissByHiding = true, + dismissAction = { + metrics?.track(Event.TabsTrayCfrDismissed) + settings.shouldShowGridViewBanner = false + } + ) { + navigationInteractor.onTabSettingsClicked() + settings.shouldShowGridViewBanner = false + } + } else { + null + } + } + + private fun displayAutoCloseTabsBannerIfNeeded(settings: Settings): InfoBanner? { + return if ( + settings.shouldShowAutoCloseTabsBanner && + settings.canShowCfr + ) { + InfoBanner( + context = context, + message = context.getString(R.string.tab_tray_close_tabs_banner_message), + dismissText = context.getString(R.string.tab_tray_close_tabs_banner_negative_button_text), + actionText = context.getString(R.string.tab_tray_close_tabs_banner_positive_button_text), + container = infoBannerView, + dismissByHiding = true, + dismissAction = { + metrics?.track(Event.TabsTrayCfrDismissed) + settings.shouldShowAutoCloseTabsBanner = false + } + ) { + navigationInteractor.onTabSettingsClicked() + settings.shouldShowAutoCloseTabsBanner = false + } + } else { + null + } + } + + companion object { + @VisibleForTesting + internal const val TAB_COUNT_SHOW_CFR = 6 + } +} diff --git a/app/src/main/res/layout/component_tabstray2.xml b/app/src/main/res/layout/component_tabstray2.xml index 275707fd9..3a359f4d4 100644 --- a/app/src/main/res/layout/component_tabstray2.xml +++ b/app/src/main/res/layout/component_tabstray2.xml @@ -25,7 +25,7 @@ app:layout_constraintWidth_percent="0.1" />