[fenix] For https://github.com/mozilla-mobile/fenix/issues/26777 - Part 2: Refactor the Jump Back In onboarding CFR to use the CFRPopup

pull/600/head
Gabriel Luong 2 years ago committed by mergify[bot]
parent 13e8bf7c45
commit 4a46d19da0

@ -23,7 +23,6 @@ import org.mozilla.fenix.home.Mode
import org.mozilla.fenix.home.OnboardingState import org.mozilla.fenix.home.OnboardingState
import org.mozilla.fenix.home.recentbookmarks.RecentBookmark import org.mozilla.fenix.home.recentbookmarks.RecentBookmark
import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem
import org.mozilla.fenix.onboarding.JumpBackInCFRDialog
import org.mozilla.fenix.onboarding.HomeCFRPresenter import org.mozilla.fenix.onboarding.HomeCFRPresenter
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
@ -213,17 +212,15 @@ class SessionControlView(
override fun onLayoutCompleted(state: RecyclerView.State?) { override fun onLayoutCompleted(state: RecyclerView.State?) {
super.onLayoutCompleted(state) super.onLayoutCompleted(state)
if (!context.settings().showHomeOnboardingDialog) { if (!context.settings().showHomeOnboardingDialog && (
if (context.settings().showSyncCFR) { context.settings().showSyncCFR ||
HomeCFRPresenter( context.settings().shouldShowJumpBackInCFR
context = context, )
recyclerView = view, ) {
).showSyncCFR() HomeCFRPresenter(
} context = context,
recyclerView = view,
if (context.settings().shouldShowJumpBackInCFR) { ).show()
JumpBackInCFRDialog(view).showIfNeeded()
}
} }
// We want some parts of the home screen UI to be rendered first if they are // We want some parts of the home screen UI to be rendered first if they are

@ -10,11 +10,13 @@ import androidx.compose.ui.unit.dp
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import mozilla.components.service.glean.private.NoExtras import mozilla.components.service.glean.private.NoExtras
import org.mozilla.fenix.GleanMetrics.Onboarding import org.mozilla.fenix.GleanMetrics.Onboarding
import org.mozilla.fenix.GleanMetrics.RecentTabs
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.compose.cfr.CFRPopup import org.mozilla.fenix.compose.cfr.CFRPopup
import org.mozilla.fenix.compose.cfr.CFRPopupProperties import org.mozilla.fenix.compose.cfr.CFRPopupProperties
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.home.recentsyncedtabs.view.RecentSyncedTabViewHolder 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. * 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 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 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( class HomeCFRPresenter(
private val context: Context, private val context: Context,
private val recyclerView: RecyclerView, 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() { fun show() {
findSyncTabsView()?.let { when (val result = getCFRToShow()) {
CFRPopup( is Result.SyncedTab -> showSyncedTabCFR(view = result.view)
text = context.getString(R.string.sync_cfr_message), is Result.JumpBackIn -> showJumpBackInCFR(view = result.view)
anchor = it, else -> {
properties = CFRPopupProperties( // no-op
indicatorDirection = CFRPopup.IndicatorDirection.DOWN, }
popupVerticalOffset = CFR_TO_ANCHOR_VERTICAL_PADDING.dp, }
), }
onDismiss = {
when (it) { private fun showSyncedTabCFR(view: View) {
true -> Onboarding.syncCfrExplicitDismissal.record(NoExtras()) CFRPopup(
false -> Onboarding.syncCfrImplicitDismissal.record(NoExtras()) 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())
} }
).apply {
syncCFR = this
show()
} }
).show()
context.settings().showSyncCFR = false // Turn off both the recent tab and synced tab CFR after the recent synced tab CFR is shown.
context.settings().shouldShowJumpBackInCFR = false context.settings().showSyncCFR = false
context.settings().shouldShowJumpBackInCFR = false
Onboarding.synCfrShown.record(NoExtras()) Onboarding.synCfrShown.record(NoExtras())
}
} }
private fun findSyncTabsView(): View? { @Suppress("MagicNumber")
val count = recyclerView.adapter?.itemCount ?: return null 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) { for (index in count downTo 0) {
val viewHolder = recyclerView.findViewHolderForAdapterPosition(index) 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()
} }
} }

@ -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
}
}

@ -1,63 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/banner_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/onboarding_popup_shape">
<TextView
android:id="@+id/banner_info_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="16dp"
android:maxWidth="279dp"
android:text="@string/onboarding_home_screen_jump_back_contextual_hint_2"
android:textAppearance="@style/Body16TextStyle"
android:textColor="@color/fx_mobile_private_text_color_primary"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/close_info_banner"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<ImageButton
android:id="@+id/close_info_banner"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/content_description_close_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/mozac_ic_close_20"
app:tint="@color/fx_mobile_private_text_color_primary" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:layout_width="@dimen/cfr_triangle_width"
android:layout_height="@dimen/cfr_triangle_height"
android:layout_gravity="start"
android:layout_marginStart="15dp"
android:layout_marginEnd="@dimen/cfr_triangle_margin_edge"
android:importantForAccessibility="no"
app:srcCompat="@drawable/ic_cfr_triangle"
android:rotation="180"
app:tint="@color/fx_mobile_layer_color_gradient_start" />
</LinearLayout>
Loading…
Cancel
Save