2
0
mirror of https://github.com/fork-maintainers/iceraven-browser synced 2024-11-17 15:26:23 +00:00

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

This commit is contained in:
sarah541 2022-06-01 13:51:32 -04:00 committed by mergify[bot]
parent 562c6319ae
commit 0e5c90103c
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. * If the view hierarchy uses Jetpack Compose, switching to that is also an option.
*/ */
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
/** /**
* The number of layouts we inflate during this start up scenario. Incrementing the expected value * 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. * such that there is one inflation that includes all of the views needed on start up.
*/ */
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("StrictMode suppression") private val failureMsgStrictMode = getErrorMessage("StrictMode suppression")
private val failureMsgRunBlocking = getErrorMessage("runBlockingIncrement") 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, viewLifecycleOwner = viewLifecycleOwner,
interactor = interactor interactor = interactor
) )
MessageCardViewHolder.LAYOUT_ID -> return MessageCardViewHolder(
composeView = ComposeView(parent.context),
viewLifecycleOwner = viewLifecycleOwner,
interactor = interactor
)
PrivateBrowsingDescriptionViewHolder.LAYOUT_ID -> return PrivateBrowsingDescriptionViewHolder( PrivateBrowsingDescriptionViewHolder.LAYOUT_ID -> return PrivateBrowsingDescriptionViewHolder(
composeView = ComposeView(parent.context), composeView = ComposeView(parent.context),
viewLifecycleOwner = viewLifecycleOwner, viewLifecycleOwner = viewLifecycleOwner,
@ -319,7 +324,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()
} }
@ -350,6 +354,11 @@ class SessionControlAdapter(
// This ViewHolder can be removed / re-added and we need it to show a fresh new composition. // This ViewHolder can be removed / re-added and we need it to show a fresh new composition.
holder.composeView.disposeComposition() 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 -> { is TabInCollectionViewHolder -> {
// Dispose the underlying composition immediately. // Dispose the underlying composition immediately.
// This ViewHolder can be removed / re-added and we need it to show a fresh new composition. // 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 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.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.R
import org.mozilla.fenix.databinding.NimbusMessageCardBinding import org.mozilla.fenix.compose.ComposeViewHolder
import org.mozilla.fenix.ext.increaseTapArea import org.mozilla.fenix.compose.MessageCard
import org.mozilla.fenix.gleanplumb.Message import org.mozilla.fenix.gleanplumb.Message
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) { private lateinit var messageGlobal: 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)
}
}
}
companion object { companion object {
internal const val LAYOUT_ID = R.layout.nimbus_message_card internal val LAYOUT_ID = View.generateViewId()
private const val CLOSE_BUTTON_EXTRA_DPS = 38 }
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>