diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt index 568212c96..661345f70 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt @@ -23,7 +23,6 @@ import org.mozilla.fenix.home.Mode import org.mozilla.fenix.home.OnboardingState import org.mozilla.fenix.home.recentbookmarks.RecentBookmark import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem -import org.mozilla.fenix.onboarding.JumpBackInCFRDialog import org.mozilla.fenix.onboarding.HomeCFRPresenter import org.mozilla.fenix.utils.Settings @@ -213,17 +212,15 @@ class SessionControlView( override fun onLayoutCompleted(state: RecyclerView.State?) { super.onLayoutCompleted(state) - if (!context.settings().showHomeOnboardingDialog) { - if (context.settings().showSyncCFR) { - HomeCFRPresenter( - context = context, - recyclerView = view, - ).showSyncCFR() - } - - if (context.settings().shouldShowJumpBackInCFR) { - JumpBackInCFRDialog(view).showIfNeeded() - } + if (!context.settings().showHomeOnboardingDialog && ( + context.settings().showSyncCFR || + context.settings().shouldShowJumpBackInCFR + ) + ) { + HomeCFRPresenter( + context = context, + recyclerView = view, + ).show() } // We want some parts of the home screen UI to be rendered first if they are diff --git a/app/src/main/java/org/mozilla/fenix/onboarding/HomeCFRPresenter.kt b/app/src/main/java/org/mozilla/fenix/onboarding/HomeCFRPresenter.kt index 76e647a33..32e474170 100644 --- a/app/src/main/java/org/mozilla/fenix/onboarding/HomeCFRPresenter.kt +++ b/app/src/main/java/org/mozilla/fenix/onboarding/HomeCFRPresenter.kt @@ -10,11 +10,13 @@ import androidx.compose.ui.unit.dp import androidx.recyclerview.widget.RecyclerView import mozilla.components.service.glean.private.NoExtras import org.mozilla.fenix.GleanMetrics.Onboarding +import org.mozilla.fenix.GleanMetrics.RecentTabs import org.mozilla.fenix.R import org.mozilla.fenix.compose.cfr.CFRPopup import org.mozilla.fenix.compose.cfr.CFRPopupProperties import org.mozilla.fenix.ext.settings import org.mozilla.fenix.home.recentsyncedtabs.view.RecentSyncedTabViewHolder +import org.mozilla.fenix.home.recenttabs.view.RecentTabsHeaderViewHolder /** * Vertical padding needed to improve the visual alignment of the popup and respect the UX design. @@ -22,58 +24,119 @@ import org.mozilla.fenix.home.recentsyncedtabs.view.RecentSyncedTabViewHolder private const val CFR_TO_ANCHOR_VERTICAL_PADDING = -16 /** - * Delegate for handling synced tab onboarding CFR. + * Delegate for handling the Home Onboarding CFR. * * @param context [Context] used for various Android interactions. - * @param recyclerView [RecyclerView] will serve as anchor for the sync CFR. + * @param recyclerView [RecyclerView] will serve as anchor for the CFR. */ class HomeCFRPresenter( private val context: Context, private val recyclerView: RecyclerView, ) { - private var syncCFR: CFRPopup? = null - /** - * Find the synced view and if it is available, then show the synced tab CFR. + * Determine the CFR to be shown on the Home screen and show a CFR for the resultant view + * if any. */ - fun showSyncCFR() { - findSyncTabsView()?.let { - CFRPopup( - text = context.getString(R.string.sync_cfr_message), - anchor = it, - properties = CFRPopupProperties( - indicatorDirection = CFRPopup.IndicatorDirection.DOWN, - popupVerticalOffset = CFR_TO_ANCHOR_VERTICAL_PADDING.dp, - ), - onDismiss = { - when (it) { - true -> Onboarding.syncCfrExplicitDismissal.record(NoExtras()) - false -> Onboarding.syncCfrImplicitDismissal.record(NoExtras()) - } - } - ).apply { - syncCFR = this - show() + fun show() { + when (val result = getCFRToShow()) { + is Result.SyncedTab -> showSyncedTabCFR(view = result.view) + is Result.JumpBackIn -> showJumpBackInCFR(view = result.view) + else -> { + // no-op } - - context.settings().showSyncCFR = false - context.settings().shouldShowJumpBackInCFR = false - - Onboarding.synCfrShown.record(NoExtras()) } } - private fun findSyncTabsView(): View? { - val count = recyclerView.adapter?.itemCount ?: return null + private fun showSyncedTabCFR(view: View) { + CFRPopup( + text = context.getString(R.string.sync_cfr_message), + anchor = view, + properties = CFRPopupProperties( + indicatorDirection = CFRPopup.IndicatorDirection.DOWN, + popupVerticalOffset = CFR_TO_ANCHOR_VERTICAL_PADDING.dp, + ), + onDismiss = { + when (it) { + true -> Onboarding.syncCfrExplicitDismissal.record(NoExtras()) + false -> Onboarding.syncCfrImplicitDismissal.record(NoExtras()) + } + } + ).show() + + // Turn off both the recent tab and synced tab CFR after the recent synced tab CFR is shown. + context.settings().showSyncCFR = false + context.settings().shouldShowJumpBackInCFR = false + + Onboarding.synCfrShown.record(NoExtras()) + } + + @Suppress("MagicNumber") + private fun showJumpBackInCFR(view: View) { + CFRPopup( + text = context.getString(R.string.onboarding_home_screen_jump_back_contextual_hint_2), + anchor = view, + properties = CFRPopupProperties( + indicatorDirection = CFRPopup.IndicatorDirection.DOWN, + popupVerticalOffset = (-40).dp, // Offset the top spacer in the recent tabs header. + ), + onDismiss = { + when (it) { + true -> RecentTabs.jumpBackInCfrDismissed.record(NoExtras()) + false -> RecentTabs.jumpBackInCfrCancelled.record(NoExtras()) + } + } + ).show() + + // Users can still see the recent synced tab CFR after the recent tab CFR is shown in + // subsequent navigation to the Home screen. + context.settings().shouldShowJumpBackInCFR = false + + RecentTabs.jumpBackInCfrShown.record(NoExtras()) + } + + /** + * Returns a [Result] that indicates the CFR that should be shown on the Home screen if any + * based on the views available and the preferences. + */ + private fun getCFRToShow(): Result { + var result: Result = Result.None + val count = recyclerView.adapter?.itemCount ?: return result for (index in count downTo 0) { val viewHolder = recyclerView.findViewHolderForAdapterPosition(index) - if (viewHolder is RecentSyncedTabViewHolder) { - return viewHolder.composeView + + if (context.settings().showSyncCFR && viewHolder is RecentSyncedTabViewHolder) { + result = Result.SyncedTab(view = viewHolder.composeView) + break + } else if (context.settings().shouldShowJumpBackInCFR && + viewHolder is RecentTabsHeaderViewHolder + ) { + result = Result.JumpBackIn(view = viewHolder.composeView) } } - return null + return result + } + + /** + * The result of determining which CFR to show on the Home screen. + */ + sealed class Result { + /** + * Indicates no CFR should be shown on the Home screen. + */ + object None : Result() + + /** + * Indicates a CFR should be shown for a Synced Tab and the associated [view] to anchor + * the CFR. + */ + data class SyncedTab(val view: View) : Result() + + /** + * Indicates a CFR should be for Jump Back In and the associated [view] to anchor the CFR. + */ + data class JumpBackIn(val view: View) : Result() } } diff --git a/app/src/main/java/org/mozilla/fenix/onboarding/JumpBackInCFRDialog.kt b/app/src/main/java/org/mozilla/fenix/onboarding/JumpBackInCFRDialog.kt deleted file mode 100644 index 330845014..000000000 --- a/app/src/main/java/org/mozilla/fenix/onboarding/JumpBackInCFRDialog.kt +++ /dev/null @@ -1,118 +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.onboarding - -import android.app.Dialog -import android.content.Context -import android.graphics.Color -import android.graphics.drawable.ColorDrawable -import android.view.Gravity -import android.view.LayoutInflater -import android.view.View -import androidx.recyclerview.widget.RecyclerView -import mozilla.telemetry.glean.private.NoExtras -import org.mozilla.fenix.GleanMetrics.RecentTabs -import org.mozilla.fenix.databinding.OnboardingJumpBackInCfrBinding -import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.home.recentsyncedtabs.view.RecentSyncedTabViewHolder -import org.mozilla.fenix.home.recenttabs.view.RecentTabsHeaderViewHolder - -/** - * Dialog displayed once when the jump back in section is shown in the home screen. - */ -class JumpBackInCFRDialog(val recyclerView: RecyclerView) { - - /** - * Try to show the crf dialog if it hasn't been shown before. - */ - fun showIfNeeded() { - val jumpBackInView = findJumpBackInView() - jumpBackInView?.let { - val crfDialog = createJumpCRF(anchor = jumpBackInView) - crfDialog?.let { - RecentTabs.jumpBackInCfrShown.record(NoExtras()) - - val context = jumpBackInView.context - context.settings().shouldShowJumpBackInCFR = false - it.show() - } - } - } - - private fun findJumpBackInView(): View? { - val count = recyclerView.adapter?.itemCount ?: return null - - for (index in 0..count) { - val viewHolder = recyclerView.findViewHolderForAdapterPosition(index) - if (viewHolder is RecentTabsHeaderViewHolder) { - return viewHolder.composeView - } - } - return null - } - - private fun hasSyncTabsView(): Boolean { - val count = recyclerView.adapter?.itemCount ?: return false - - for (index in count downTo 0) { - val viewHolder = recyclerView.findViewHolderForAdapterPosition(index) - if (viewHolder is RecentSyncedTabViewHolder) { - return true - } - } - - return false - } - - private fun createJumpCRF(anchor: View): Dialog? { - val context: Context = recyclerView.context - - if (context.settings().showSyncCFR && hasSyncTabsView()) { - context.settings().shouldShowJumpBackInCFR = false - } - - if (!context.settings().shouldShowJumpBackInCFR) { - return null - } - - val anchorPosition = IntArray(2) - val popupBinding = OnboardingJumpBackInCfrBinding.inflate(LayoutInflater.from(context)) - val popup = Dialog(context) - - popup.apply { - setContentView(popupBinding.root) - setCanceledOnTouchOutside(true) - setOnCancelListener { - RecentTabs.jumpBackInCfrCancelled.record(NoExtras()) - } - // removing title or setting it as an empty string does not prevent a11y services from assigning one - setTitle(" ") - } - popupBinding.closeInfoBanner.setOnClickListener { - RecentTabs.jumpBackInCfrDismissed.record(NoExtras()) - popup.dismiss() - } - - anchor.getLocationOnScreen(anchorPosition) - val (x, y) = anchorPosition - - if (x == 0 && y == 0) { - return null - } - - popupBinding.root.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED) - - popup.window?.apply { - val attr = attributes - setGravity(Gravity.START or Gravity.TOP) - attr.x = x - attr.y = y - popupBinding.root.measuredHeight - attributes = attr - setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) - setDimAmount(0f) - } - return popup - } -} diff --git a/app/src/main/res/layout/onboarding_jump_back_in_cfr.xml b/app/src/main/res/layout/onboarding_jump_back_in_cfr.xml deleted file mode 100644 index 9ad07cb18..000000000 --- a/app/src/main/res/layout/onboarding_jump_back_in_cfr.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - -