[fenix] For https://github.com/mozilla-mobile/fenix/issues/23966 - Migrate MessageCardViewHolder to Compose

pull/600/head
sarah541 2 years ago committed by mergify[bot]
parent f707f47355
commit 633377e903

@ -25,9 +25,9 @@ private const val EXPECTED_SUPPRESSION_COUNT = 19
@Suppress("TopLevelPropertyNaming") // it's silly this would have a different naming convention b/c no const @Suppress("TopLevelPropertyNaming") // it's silly this would have a different naming convention b/c no const
private val EXPECTED_RUNBLOCKING_RANGE = 0..1 // CI has +1 counts compared to local runs: increment these together private val EXPECTED_RUNBLOCKING_RANGE = 0..1 // CI has +1 counts compared to local runs: increment these together
private val EXPECTED_RECYCLER_VIEW_CONSTRAINT_LAYOUT_CHILDREN = private val EXPECTED_RECYCLER_VIEW_CONSTRAINT_LAYOUT_CHILDREN =
4..5 // The messaging framework is not deterministic and could add a +1 to the count 3..4 // The messaging framework is not deterministic and could add a +1 to the count
private val EXPECTED_NUMBER_OF_INFLATION = private val EXPECTED_NUMBER_OF_INFLATION =
14..15 // The messaging framework is not deterministic and could add a +1 to the count 13..14 // The messaging framework is not deterministic and could add a +1 to the count
private val failureMsgStrictMode = getErrorMessage( private val failureMsgStrictMode = getErrorMessage(
shortName = "StrictMode suppression", shortName = "StrictMode suppression",

@ -72,7 +72,7 @@ fun CollectionsPlaceholder(
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
) { ) {
Icon( Icon(
painter = painterResource(R.drawable.ic_close), painter = painterResource(R.drawable.mozac_ic_close_20),
contentDescription = stringResource( contentDescription = stringResource(
R.string.remove_home_collection_placeholder_content_description R.string.remove_home_collection_placeholder_content_description
), ),

@ -0,0 +1,218 @@
/* 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.compose
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.mozilla.experiments.nimbus.StringHolder
import org.mozilla.fenix.R
import org.mozilla.fenix.compose.button.PrimaryButton
import org.mozilla.fenix.gleanplumb.Message
import org.mozilla.fenix.nimbus.MessageData
import org.mozilla.fenix.nimbus.StyleData
import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.theme.Theme
/**
* Message Card.
*
* @param message [Message] that holds a representation of GleanPlum message from Nimbus.
* @param onClick Invoked when user clicks on the message card.
* @param onCloseButtonClick Invoked when user clicks on close button to remove message.
*/
@Suppress("LongMethod")
@Composable
fun MessageCard(
message: Message,
onClick: () -> Unit,
onCloseButtonClick: () -> Unit,
) {
Card(
modifier = Modifier
.padding(vertical = 16.dp)
.then(
if (message.data.buttonLabel.isNullOrBlank()) {
Modifier.clickable(onClick = onClick)
} else {
Modifier
}
),
shape = RoundedCornerShape(8.dp),
backgroundColor = FirefoxTheme.colors.layer2,
) {
Column(
Modifier
.padding(all = 16.dp)
.fillMaxWidth()
) {
val title = message.data.title
if (!title.isNullOrBlank()) {
Row(
modifier = Modifier.fillMaxWidth(),
) {
SectionHeader(
text = title,
modifier = Modifier
.weight(1f)
)
IconButton(
modifier = Modifier.size(20.dp),
onClick = onCloseButtonClick
) {
Icon(
painter = painterResource(R.drawable.mozac_ic_close_20),
contentDescription = stringResource(
R.string.content_description_close_button
),
tint = FirefoxTheme.colors.iconPrimary
)
}
}
PrimaryText(
text = message.data.text,
modifier = Modifier.fillMaxWidth(),
fontSize = 14.sp
)
} else {
Row(
modifier = Modifier.fillMaxWidth(),
) {
PrimaryText(
text = message.data.text,
modifier = Modifier.weight(1f),
fontSize = 14.sp
)
IconButton(
modifier = Modifier.size(20.dp),
onClick = onCloseButtonClick
) {
Icon(
painter = painterResource(R.drawable.mozac_ic_close_20),
contentDescription = stringResource(
R.string.content_description_close_button
),
tint = FirefoxTheme.colors.iconPrimary
)
}
}
}
if (!message.data.buttonLabel.isNullOrBlank()) {
Spacer(modifier = Modifier.height(16.dp))
PrimaryButton(
text = stringResource(R.string.preferences_set_as_default_browser),
onClick = onClick
)
}
}
}
}
@Composable
@Preview
private fun MessageCardPreview() {
FirefoxTheme(theme = Theme.getTheme(isPrivate = false)) {
Box(Modifier.background(FirefoxTheme.colors.layer1)) {
MessageCard(
message = Message(
id = "end-",
data = MessageData(
title = StringHolder(
R.string.bookmark_empty_title_error,
"Title"
),
text = StringHolder(
R.string.default_browser_experiment_card_text, "description"
)
),
action = "action",
style = StyleData(),
triggers = listOf("trigger"),
metadata = Message.Metadata("end-")
),
onClick = {},
onCloseButtonClick = {}
)
}
}
}
@Composable
@Preview
private fun MessageCardWithoutTitlePreview() {
FirefoxTheme(theme = Theme.getTheme(isPrivate = false)) {
Box(Modifier.background(FirefoxTheme.colors.layer1)) {
MessageCard(
message = Message(
id = "end-",
data = MessageData(
text = StringHolder(
R.string.default_browser_experiment_card_text, "description"
)
),
action = "action",
style = StyleData(),
triggers = listOf("trigger"),
metadata = Message.Metadata("end-")
),
onClick = {},
onCloseButtonClick = {}
)
}
}
}
@Composable
@Preview
private fun MessageCardWithButtonLabelPreview() {
FirefoxTheme(theme = Theme.getTheme(isPrivate = false)) {
Box(Modifier.background(FirefoxTheme.colors.layer1)) {
MessageCard(
message = Message(
id = "end-",
data = MessageData(
buttonLabel = StringHolder(R.string.preferences_set_as_default_browser, ""),
title = StringHolder(
R.string.bookmark_empty_title_error,
"Title"
),
text = StringHolder(
R.string.default_browser_experiment_card_text, "description"
)
),
action = "action",
style = StyleData(),
triggers = listOf("trigger"),
metadata = Message.Metadata("end-")
),
onClick = {},
onCloseButtonClick = {}
)
}
}
}

@ -293,6 +293,11 @@ class SessionControlAdapter(
viewLifecycleOwner = viewLifecycleOwner, viewLifecycleOwner = viewLifecycleOwner,
interactor = interactor, interactor = interactor,
) )
MessageCardViewHolder.LAYOUT_ID -> return MessageCardViewHolder(
composeView = ComposeView(parent.context),
viewLifecycleOwner = viewLifecycleOwner,
interactor = interactor
)
} }
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false) val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
@ -318,7 +323,6 @@ class SessionControlAdapter(
OnboardingToolbarPositionPickerViewHolder.LAYOUT_ID -> OnboardingToolbarPositionPickerViewHolder( OnboardingToolbarPositionPickerViewHolder.LAYOUT_ID -> OnboardingToolbarPositionPickerViewHolder(
view view
) )
MessageCardViewHolder.LAYOUT_ID -> MessageCardViewHolder(view, interactor)
BottomSpacerViewHolder.LAYOUT_ID -> BottomSpacerViewHolder(view) BottomSpacerViewHolder.LAYOUT_ID -> BottomSpacerViewHolder(view)
else -> throw IllegalStateException() else -> throw IllegalStateException()
} }
@ -328,6 +332,7 @@ class SessionControlAdapter(
when (holder) { when (holder) {
is CollectionHeaderViewHolder, is CollectionHeaderViewHolder,
is CustomizeHomeButtonViewHolder, is CustomizeHomeButtonViewHolder,
is MessageCardViewHolder,
is NoCollectionsMessageViewHolder, is NoCollectionsMessageViewHolder,
is RecentlyVisitedViewHolder, is RecentlyVisitedViewHolder,
is RecentVisitsHeaderViewHolder, is RecentVisitsHeaderViewHolder,
@ -390,9 +395,6 @@ class SessionControlAdapter(
is TopSitePagerViewHolder -> { is TopSitePagerViewHolder -> {
holder.bind((item as AdapterItem.TopSitePager).topSites) holder.bind((item as AdapterItem.TopSitePager).topSites)
} }
is MessageCardViewHolder -> {
holder.bind((item as AdapterItem.NimbusMessageCard).message)
}
is CollectionViewHolder -> { is CollectionViewHolder -> {
val (collection, expanded) = item as AdapterItem.CollectionItem val (collection, expanded) = item as AdapterItem.CollectionItem
holder.bindSession(collection, expanded) holder.bindSession(collection, expanded)

@ -5,53 +5,53 @@
package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.compose.runtime.Composable
import androidx.recyclerview.widget.RecyclerView import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.LifecycleOwner
import mozilla.components.lib.state.ext.observeAsComposableState
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.NimbusMessageCardBinding import org.mozilla.fenix.components.components
import org.mozilla.fenix.ext.increaseTapArea import org.mozilla.fenix.compose.ComposeViewHolder
import org.mozilla.fenix.gleanplumb.Message import org.mozilla.fenix.compose.MessageCard
import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor
/**
* View holder for the Nimbus Message Card.
*
* @property interactor [SessionControlInteractor] which will have delegated to all user
* interactions.
*/
class MessageCardViewHolder( class MessageCardViewHolder(
view: View, composeView: ComposeView,
private val interactor: SessionControlInteractor viewLifecycleOwner: LifecycleOwner,
) : RecyclerView.ViewHolder(view) { private val interactor: SessionControlInteractor,
) : ComposeViewHolder(composeView, viewLifecycleOwner) {
fun bind(message: Message) { companion object {
val binding = NimbusMessageCardBinding.bind(itemView) internal val LAYOUT_ID = View.generateViewId()
}
if (message.data.title.isNullOrBlank()) { init {
binding.titleText.isVisible = false val horizontalPadding =
} else { composeView.resources.getDimensionPixelSize(R.dimen.home_item_horizontal_margin)
binding.titleText.text = message.data.title composeView.setPadding(horizontalPadding, 0, horizontalPadding, 0)
} }
binding.descriptionText.text = message.data.text @Composable
override fun Content() {
if (message.data.buttonLabel.isNullOrBlank()) { val messaging = components.appStore.observeAsComposableState { state -> state.messaging }
binding.messageButton.isVisible = false val message = messaging.value?.messageToShow
binding.experimentCard.setOnClickListener {
interactor.onMessageClicked(message) message?.let {
} MessageCard(
} else { message = it,
binding.messageButton.text = message.data.buttonLabel onClick = { interactor.onMessageClicked(message) },
binding.messageButton.setOnClickListener { onCloseButtonClick = { interactor.onMessageClosedClicked(message) }
interactor.onMessageClicked(message) )
}
} }
binding.close.apply { if (message != null) {
increaseTapArea(CLOSE_BUTTON_EXTRA_DPS) interactor.onMessageDisplayed(message)
setOnClickListener {
interactor.onMessageClosedClicked(message)
}
} }
interactor.onMessageDisplayed(message)
}
companion object {
internal const val LAYOUT_ID = R.layout.nimbus_message_card
private const val CLOSE_BUTTON_EXTRA_DPS = 38
} }
} }

@ -1,58 +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/. -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/experiment_card"
style="@style/OnboardingCardLightWithPadding"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/home_item_horizontal_margin">
<TextView
android:id="@+id/title_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
tools:text="Title"
android:layout_marginBottom="10dp"
android:textAppearance="@style/Header16TextStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/close"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/description_text" />
<ImageButton
android:id="@+id/close"
android:layout_width="10dp"
android:layout_height="10dp"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/content_description_close_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/description_text"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_close"
tools:srcCompat="@drawable/ic_close" />
<TextView
android:id="@+id/description_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/default_browser_experiment_card_text"
android:textAppearance="@style/Body14TextStyle"
app:layout_constraintBottom_toTopOf="@id/message_button"
app:layout_constraintEnd_toStartOf="@id/close"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/title_text" />
<Button
android:id="@+id/message_button"
style="@style/PositiveButton"
android:layout_height="36dp"
android:background="@drawable/rounded_button_background"
android:layout_marginTop="16dp"
android:text="@string/preferences_set_as_default_browser"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/description_text" />
</androidx.constraintlayout.widget.ConstraintLayout>
Loading…
Cancel
Save