For #23966 - Migrate MessageCardViewHolder to Compose

This commit is contained in:
sarah541 2022-06-01 13:51:32 -04:00 committed by mergify[bot]
parent 282ba75652
commit 8791a25522
5 changed files with 272 additions and 101 deletions

View File

@ -63,7 +63,7 @@ private val EXPECTED_RUNBLOCKING_RANGE = 0..1 // CI has +1 counts compared to lo
* If the view hierarchy uses Jetpack Compose, switching to that is also an option.
*/
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
/**
* The number of layouts we inflate during this start up scenario. Incrementing the expected value
@ -76,7 +76,7 @@ private val EXPECTED_RECYCLER_VIEW_CONSTRAINT_LAYOUT_CHILDREN =
* such that there is one inflation that includes all of the views needed on start up.
*/
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("StrictMode suppression")
private val failureMsgRunBlocking = getErrorMessage("runBlockingIncrement")

View File

@ -0,0 +1,219 @@
/* 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.material.Text
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
)
}
}
Text(
text = message.data.text,
modifier = Modifier.fillMaxWidth(),
fontSize = 14.sp
)
} else {
Row(
modifier = Modifier.fillMaxWidth(),
) {
Text(
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 = {}
)
}
}
}

View File

@ -222,6 +222,11 @@ class SessionControlAdapter(
viewLifecycleOwner = viewLifecycleOwner,
interactor = interactor
)
MessageCardViewHolder.LAYOUT_ID -> return MessageCardViewHolder(
composeView = ComposeView(parent.context),
viewLifecycleOwner = viewLifecycleOwner,
interactor = interactor
)
PrivateBrowsingDescriptionViewHolder.LAYOUT_ID -> return PrivateBrowsingDescriptionViewHolder(
composeView = ComposeView(parent.context),
viewLifecycleOwner = viewLifecycleOwner,
@ -319,7 +324,6 @@ class SessionControlAdapter(
OnboardingToolbarPositionPickerViewHolder.LAYOUT_ID -> OnboardingToolbarPositionPickerViewHolder(
view
)
MessageCardViewHolder.LAYOUT_ID -> MessageCardViewHolder(view, interactor)
BottomSpacerViewHolder.LAYOUT_ID -> BottomSpacerViewHolder(view)
else -> throw IllegalStateException()
}
@ -350,6 +354,11 @@ class SessionControlAdapter(
// This ViewHolder can be removed / re-added and we need it to show a fresh new composition.
holder.composeView.disposeComposition()
}
is MessageCardViewHolder -> {
// Dispose the underlying composition immediately.
// This ViewHolder can be removed / re-added and we need it to show a fresh new composition.
holder.composeView.disposeComposition()
}
is TabInCollectionViewHolder -> {
// Dispose the underlying composition immediately.
// This ViewHolder can be removed / re-added and we need it to show a fresh new composition.

View File

@ -5,52 +5,53 @@
package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding
import android.view.View
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.LifecycleOwner
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.NimbusMessageCardBinding
import org.mozilla.fenix.ext.increaseTapArea
import org.mozilla.fenix.compose.ComposeViewHolder
import org.mozilla.fenix.compose.MessageCard
import org.mozilla.fenix.gleanplumb.Message
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(
view: View,
private val interactor: SessionControlInteractor
) : RecyclerView.ViewHolder(view) {
fun bind(message: Message) {
val binding = NimbusMessageCardBinding.bind(itemView)
if (message.data.title.isNullOrBlank()) {
binding.titleText.isVisible = false
} else {
binding.titleText.text = message.data.title
}
binding.descriptionText.text = message.data.text
if (message.data.buttonLabel.isNullOrBlank()) {
binding.messageButton.isVisible = false
binding.experimentCard.setOnClickListener {
interactor.onMessageClicked(message)
}
} else {
binding.messageButton.text = message.data.buttonLabel
binding.messageButton.setOnClickListener {
interactor.onMessageClicked(message)
}
}
binding.close.apply {
increaseTapArea(CLOSE_BUTTON_EXTRA_DPS)
setOnClickListener {
interactor.onMessageClosedClicked(message)
}
}
}
composeView: ComposeView,
viewLifecycleOwner: LifecycleOwner,
private val interactor: SessionControlInteractor,
) : ComposeViewHolder(composeView, viewLifecycleOwner) {
private lateinit var messageGlobal: Message
companion object {
internal const val LAYOUT_ID = R.layout.nimbus_message_card
private const val CLOSE_BUTTON_EXTRA_DPS = 38
internal val LAYOUT_ID = View.generateViewId()
}
init {
val horizontalPadding =
composeView.resources.getDimensionPixelSize(R.dimen.home_item_horizontal_margin)
composeView.setPadding(horizontalPadding, 0, horizontalPadding, 0)
}
fun bind(message: Message) {
messageGlobal = message
}
@Composable
override fun Content() {
val message by remember { mutableStateOf(messageGlobal) }
MessageCard(
message = message,
onClick = { interactor.onMessageClicked(message) },
onCloseButtonClick = { interactor.onMessageClosedClicked(message) }
)
}
}

View File

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