For #26777 - Part 2: Refactor the Jump Back In onboarding CFR to use the CFRPopup
This commit is contained in:
parent
86b62af556
commit
812f073e39
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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…
Reference in New Issue