diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eba2dd5a4..0bc497fbc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,6 +11,8 @@ Don't hesitate to ask any questions and share your ideas We would love to accept your Pull Requests but please, before starting your development, [create an issue](https://github.com/Telefonica/mistica-android/issues/new/choose). +Include someone from **Telefonica/mistica-design** team as reviewer. + ## Bug reports If something is broken or not working as expected, let us know! diff --git a/README.md b/README.md index e52f44e90..ab961f300 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ Just set your App or any specific activity to use any of the following: * [Load Error Feedback](library/src/main/java/com/telefonica/mistica/feedback/error) * [Media Cards](library/src/main/java/com/telefonica/mistica/card/mediacard) * [Pop Overs](library/src/main/java/com/telefonica/mistica/feedback/popover) +* [Poster Card](library/src/main/java/com/telefonica/mistica/compose/card/postercard) * [Screen Feedbacks](library/src/main/java/com/telefonica/mistica/feedback/screen) * [Scroll Content Indicator](library/src/main/java/com/telefonica/mistica/contentindicator) * [Sheet](library/src/main/java/com/telefonica/mistica/sheet) diff --git a/catalog/src/main/java/com/telefonica/mistica/catalog/ui/CatalogMainActivity.kt b/catalog/src/main/java/com/telefonica/mistica/catalog/ui/CatalogMainActivity.kt index fef4e1147..dcb2e5cf8 100644 --- a/catalog/src/main/java/com/telefonica/mistica/catalog/ui/CatalogMainActivity.kt +++ b/catalog/src/main/java/com/telefonica/mistica/catalog/ui/CatalogMainActivity.kt @@ -100,6 +100,7 @@ class CatalogMainActivity : AppCompatActivity() { SectionItem("Lists", R.drawable.ic_lists, Section.LISTS), SectionItem("Load Error Feedback", R.drawable.ic_load_feedback_error, Section.LOAD_ERROR_FEEDBACK), SectionItem("Media Card", R.drawable.ic_cards, Section.MEDIA_CARDS), + SectionItem("Poster Card", R.drawable.ic_cards, Section.POSTER_CARD), SectionItem("Filters", R.drawable.ic_filters, Section.FILTERS), SectionItem("PopOvers", R.drawable.ic_popovers, Section.POPOVERS), SectionItem("Scroll Content indicator", R.drawable.ic_feedbacks, Section.SCROLL_CONTENT_INDICATOR), diff --git a/catalog/src/main/java/com/telefonica/mistica/catalog/ui/ComponentCatalogActivity.kt b/catalog/src/main/java/com/telefonica/mistica/catalog/ui/ComponentCatalogActivity.kt index 95805a8c8..d8891ab19 100644 --- a/catalog/src/main/java/com/telefonica/mistica/catalog/ui/ComponentCatalogActivity.kt +++ b/catalog/src/main/java/com/telefonica/mistica/catalog/ui/ComponentCatalogActivity.kt @@ -64,6 +64,7 @@ import com.telefonica.mistica.catalog.ui.compose.components.Lists import com.telefonica.mistica.catalog.ui.compose.components.LoadErrorFeedbacks import com.telefonica.mistica.catalog.ui.compose.components.MediaCards import com.telefonica.mistica.catalog.ui.compose.components.PopOvers +import com.telefonica.mistica.catalog.ui.compose.components.PosterCards import com.telefonica.mistica.catalog.ui.compose.components.Skeletons import com.telefonica.mistica.catalog.ui.compose.components.Steppers import com.telefonica.mistica.catalog.ui.compose.components.TabsCatalog @@ -128,7 +129,8 @@ class ComponentCatalogActivity : AppCompatActivity() { Section.CALLOUTS to ::setCalloutsCatalogFragment, Section.SHEET to ::setSheetCatalogFragment, Section.CAROUSEL to ::setCarouselFragment, - Section.SKELETON to ::setSkeletonFragment + Section.SKELETON to ::setSkeletonFragment, + Section.POSTER_CARD to ::setPosterCardFragment ) val section = intent.getSerializableExtra(EXTRA_SECTION) as? Section @@ -210,6 +212,12 @@ class ComponentCatalogActivity : AppCompatActivity() { composeComponent = { DataCards() }) } + private fun setPosterCardFragment() { + setPageAdapterWithTabs( + classicComponent = null, + composeComponent = { PosterCards() }) + } + private fun setMediaCardsFragment() { setPageAdapterWithTabs( classicComponent = MediaCardsFragment(), @@ -398,5 +406,6 @@ enum class Section { SHEET, CAROUSEL, SKELETON, + POSTER_CARD } diff --git a/catalog/src/main/java/com/telefonica/mistica/catalog/ui/compose/components/PosterCards.kt b/catalog/src/main/java/com/telefonica/mistica/catalog/ui/compose/components/PosterCards.kt new file mode 100644 index 000000000..2faeef6ca --- /dev/null +++ b/catalog/src/main/java/com/telefonica/mistica/catalog/ui/compose/components/PosterCards.kt @@ -0,0 +1,237 @@ +package com.telefonica.mistica.catalog.ui.compose.components + +import androidx.annotation.AttrRes +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Checkbox +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.unit.dp +import com.telefonica.mistica.catalog.R +import com.telefonica.mistica.compose.card.postercard.PosterCard +import com.telefonica.mistica.compose.card.postercard.PosterCardAspectRatio +import com.telefonica.mistica.compose.card.postercard.PosterCardBackgroundType +import com.telefonica.mistica.compose.card.postercard.TopActionData +import com.telefonica.mistica.compose.input.DropDownInput +import com.telefonica.mistica.compose.tag.Tag +import com.telefonica.mistica.compose.theme.MisticaTheme +import com.telefonica.mistica.tag.TagView +import com.telefonica.mistica.tag.TagView.Companion.TYPE_PROMO + +@Composable +fun PosterCards() { + + var tag: String by remember { mutableStateOf("Headline") } + var tagType: Int by remember { mutableIntStateOf(TYPE_PROMO) } + + var aspectRatioType: PosterCardAspectRatio by remember { mutableStateOf(PosterCardAspectRatio.AR_AUTO) } + + var inverseDisplay: Boolean by remember { mutableStateOf(true) } + var backgroundType: BackgroundType by remember { mutableStateOf(BackgroundType.IMAGE) } + + var topActionsType: TopActionsType by remember { mutableStateOf(TopActionsType.NONE) } + + var preTitle: String by remember { mutableStateOf("Pretitle") } + var title: String by remember { mutableStateOf("Title") } + var subtitle: String by remember { mutableStateOf("Subtitle") } + var description: String by remember { mutableStateOf("Description") } + + var withAdditionalContent: Boolean by remember { mutableStateOf(false) } + + + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + DropDownInput( + modifier = Modifier + .fillMaxWidth() + .padding(top = 4.dp), + items = PosterCardAspectRatio.entries.map { aspectRatioLabelsMaps[it].orEmpty() }, + currentItemIndex = PosterCardAspectRatio.entries.indexOf(aspectRatioType), + onItemSelected = { index -> aspectRatioType = PosterCardAspectRatio.entries.toTypedArray()[index] }, + hint = "Aspect Ratio", + ) + + DropDownInput( + modifier = Modifier + .fillMaxWidth() + .padding(top = 4.dp), + items = BackgroundType.entries.map { backgroundTypeLabelsMap[it].orEmpty() }, + currentItemIndex = BackgroundType.entries.indexOf(backgroundType), + onItemSelected = { index -> backgroundType = BackgroundType.entries.toTypedArray()[index] }, + hint = "Background type", + ) + + if (backgroundType != BackgroundType.IMAGE) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text("Inverse display") + Checkbox(checked = inverseDisplay, onCheckedChange = { + inverseDisplay = !inverseDisplay + backgroundType.backgroundValue.inverseDisplay = inverseDisplay + }) + } + } + + DropDownInput( + modifier = Modifier + .fillMaxWidth() + .padding(top = 4.dp), + items = TopActionsType.entries.map { topActionsTypeLabelsMap[it].orEmpty() }, + currentItemIndex = TopActionsType.entries.indexOf(topActionsType), + onItemSelected = { index -> topActionsType = TopActionsType.entries.toTypedArray()[index] }, + hint = "Top actions", + ) + + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = tag, + onValueChange = { tag = it }, label = { Text("Tag label") } + ) + + DropDownInput( + modifier = Modifier + .fillMaxWidth() + .padding(top = 4.dp), + items = TagColorsValues.entries.map { it.name }, + currentItemIndex = tagType, + onItemSelected = { index -> tagType = index }, + hint = "Tag style", + ) + + OutlinedTextField(modifier = Modifier.fillMaxWidth(), value = preTitle, onValueChange = { preTitle = it }, label = { Text("Pretitle") }) + OutlinedTextField(modifier = Modifier.fillMaxWidth(), value = title, onValueChange = { title = it }, label = { Text("Title") }) + OutlinedTextField(modifier = Modifier.fillMaxWidth(), value = subtitle, onValueChange = { subtitle = it }, label = { Text("Subtitle") }) + OutlinedTextField(modifier = Modifier.fillMaxWidth(), value = description, onValueChange = { description = it }, label = { Text("Description") }) + + Row(verticalAlignment = Alignment.CenterVertically) { + Text("With additional content") + Checkbox(checked = withAdditionalContent, onCheckedChange = { withAdditionalContent = !withAdditionalContent }) + } + + PosterCard( + modifier = Modifier.fillMaxWidth(), + aspectRatio = aspectRatioType, + backgroundType = backgroundType.backgroundValue, + headline = if (tag.isNotEmpty()) Tag(tag).withStyle(tagType) else null, + preTitle = preTitle.getOrNullIfEmpty(), + title = title.getOrNullIfEmpty(), + subtitle = subtitle.getOrNullIfEmpty(), + description = description.getOrNullIfEmpty(), + firstTopAction = topActionsType.info?.firstTopAction, + secondTopAction = topActionsType.info?.secondTopAction, + customContent = { + if (withAdditionalContent) { + AdditionalContent() + } + } + ) + } +} + +private enum class TagColorsValues(@AttrRes val tagStyle: Int) { + PROMO(TYPE_PROMO), + ACTIVE(TagView.TYPE_ACTIVE), + INACTIVE(TagView.TYPE_INACTIVE), + SUCCESS(TagView.TYPE_SUCCESS), + WARNING(TagView.TYPE_WARNING), + ERROR(TagView.TYPE_ERROR), + INVERSE(TagView.TYPE_INVERSE), +} + +private val aspectRatioLabelsMaps = mapOf( + PosterCardAspectRatio.AR_AUTO to "Auto", + PosterCardAspectRatio.AR_1_1 to "1:1", + PosterCardAspectRatio.AR_7_10 to "7:10", + PosterCardAspectRatio.AR_9_10 to "9:10", + PosterCardAspectRatio.AR_16_9 to "16:9" +) + +private val backgroundTypeLabelsMap = mapOf( + BackgroundType.SOLID_COLOR to "Solid color", + BackgroundType.GRADIENT_COLOR to "Gradient color", + BackgroundType.IMAGE to "Image" +) + +private val topActionsTypeLabelsMap = mapOf( + TopActionsType.NONE to "None", + TopActionsType.ONE_ACTION_DISMISS to "One action (Dismiss)", + TopActionsType.TWO_ACTIONS to "Two actions (View + Dismiss)" +) + +private enum class BackgroundType(val backgroundValue: PosterCardBackgroundType) { + IMAGE(PosterCardBackgroundType.Image(imageResource = R.drawable.sample_background, contentDescription = "Mística PosterCard")), + SOLID_COLOR(PosterCardBackgroundType.Color(brush = SolidColor(Color.Red))), + GRADIENT_COLOR(PosterCardBackgroundType.Color(brush = Brush.verticalGradient(colors = listOf(Color.Blue, Color.Cyan)))), +} + +private enum class TopActionsType(val info: PosterCardTopActionInfo? = null) { + NONE, + ONE_ACTION_DISMISS( + info = PosterCardTopActionInfo( + firstTopAction = TopActionData(iconRes = R.drawable.ic_close_regular) + ) + ), + TWO_ACTIONS( + info = PosterCardTopActionInfo( + firstTopAction = TopActionData( + iconRes = R.drawable.icn_visibility, + contentDescription = "PosterCard Visible top action" + ), + secondTopAction = TopActionData( + iconRes = R.drawable.ic_close_regular, + contentDescription = "PosterCard Close top action" + ) + ) + ) +} + +private data class PosterCardTopActionInfo( + val firstTopAction: TopActionData? = null, + val secondTopAction: TopActionData? = null +) + +@Composable +internal fun AdditionalContent() { + Box( + modifier = Modifier + .fillMaxWidth() + .size(150.dp) + .background(color = MisticaTheme.colors.successHighInverse.copy(alpha = 0.5f)) + .border(width = 1.dp, color = MisticaTheme.colors.success), + contentAlignment = Alignment.Center + ) { + Text( + text = "Additional content", + style = MisticaTheme.typography.preset2, + color = MisticaTheme.colors.textPrimaryInverse + ) + } +} + +private fun String.getOrNullIfEmpty(): String? = this.ifEmpty { null } \ No newline at end of file diff --git a/catalog/src/main/res/drawable-nodpi/sample_background.webp b/catalog/src/main/res/drawable-nodpi/sample_background.webp new file mode 100644 index 000000000..70dcdc96b Binary files /dev/null and b/catalog/src/main/res/drawable-nodpi/sample_background.webp differ diff --git a/library/src/main/java/com/telefonica/mistica/compose/card/postercard/PosterCard.kt b/library/src/main/java/com/telefonica/mistica/compose/card/postercard/PosterCard.kt new file mode 100644 index 000000000..5bfb683c7 --- /dev/null +++ b/library/src/main/java/com/telefonica/mistica/compose/card/postercard/PosterCard.kt @@ -0,0 +1,96 @@ +package com.telefonica.mistica.compose.card.postercard + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import com.telefonica.mistica.compose.tag.Tag +import com.telefonica.mistica.compose.theme.MisticaTheme + +@Composable +fun PosterCard( + aspectRatio: PosterCardAspectRatio, + backgroundType: PosterCardBackgroundType, + modifier: Modifier = Modifier, + headline: Tag? = null, + preTitle: String? = null, + title: String? = null, + subtitle: String? = null, + description: String? = null, + firstTopAction: TopActionData? = null, + secondTopAction: TopActionData? = null, + onClickAction: (() -> Unit)? = null, + customContent: (@Composable () -> Unit)? = null, +) { + val anyTopActionsLoaded = firstTopAction != null || secondTopAction != null + + BoxWithConstraints(modifier = modifier) { + androidx.compose.material.Card( + elevation = 0.dp, + shape = RoundedCornerShape(MisticaTheme.radius.containerBorderRadius), + modifier = Modifier + .width(maxWidth) + .clickable(enabled = onClickAction != null) { + onClickAction?.invoke() + } + .heightIn( + min = maxWidth / aspectRatio.ratio, + max = Dp.Infinity + ) + ) { + PosterCardBackground(backgroundType = backgroundType) { + Column( + modifier = Modifier.align(alignment = Alignment.BottomCenter), + verticalArrangement = Arrangement.Bottom + ) { + if (anyTopActionsLoaded) { + Spacer(modifier = Modifier.height(40.dp)) + } + PosterCardMainContent( + backgroundType = backgroundType, + tag = headline, + preTitle = preTitle, + title = title, + description = description, + subtitle = subtitle, + customContent = customContent + ) + } + if (anyTopActionsLoaded) { + PosterCardTopActions( + modifier = Modifier.align(alignment = Alignment.TopCenter).zIndex(1f), + firstTopAction = firstTopAction, + secondTopAction = secondTopAction + ) + } + } + } + } +} + +enum class PosterCardAspectRatio(val ratio: Float) { + AR_AUTO(ratio = Float.NaN), + AR_1_1(ratio = 1f), + AR_7_10(ratio = 0.7f), + AR_9_10(ratio = 0.9f), + AR_16_9(16 / 9f) +} + +sealed class PosterCardBackgroundType(open var inverseDisplay: Boolean) { + data class Image(@DrawableRes val imageResource: Int, val contentDescription: String = "") : PosterCardBackgroundType(inverseDisplay = true) + data class Color(val brush: Brush, override var inverseDisplay: Boolean = true) : PosterCardBackgroundType(inverseDisplay = inverseDisplay) +} + diff --git a/library/src/main/java/com/telefonica/mistica/compose/card/postercard/PosterCardBackground.kt b/library/src/main/java/com/telefonica/mistica/compose/card/postercard/PosterCardBackground.kt new file mode 100644 index 000000000..29552eca1 --- /dev/null +++ b/library/src/main/java/com/telefonica/mistica/compose/card/postercard/PosterCardBackground.kt @@ -0,0 +1,40 @@ +package com.telefonica.mistica.compose.card.postercard + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource + +@Composable +internal fun PosterCardBackground( + backgroundType: PosterCardBackgroundType, + content: @Composable BoxScope.() -> Unit +) { + Box( + modifier = Modifier + .background( + brush = if (backgroundType is PosterCardBackgroundType.Color) { + backgroundType.brush + } else { + SolidColor(Color.Transparent) + }, + ) + ) { + if (backgroundType is PosterCardBackgroundType.Image) { + Image( + modifier = Modifier.matchParentSize(), + painter = painterResource(id = backgroundType.imageResource), + contentDescription = backgroundType.contentDescription, + contentScale = ContentScale.Crop + ) + } + content() + } +} + diff --git a/library/src/main/java/com/telefonica/mistica/compose/card/postercard/PosterCardMainContent.kt b/library/src/main/java/com/telefonica/mistica/compose/card/postercard/PosterCardMainContent.kt new file mode 100644 index 000000000..38719496a --- /dev/null +++ b/library/src/main/java/com/telefonica/mistica/compose/card/postercard/PosterCardMainContent.kt @@ -0,0 +1,138 @@ +package com.telefonica.mistica.compose.card.postercard + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shadow +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import com.telefonica.mistica.compose.tag.Tag +import com.telefonica.mistica.compose.theme.MisticaTheme + +@Composable +internal fun PosterCardMainContent( + modifier: Modifier = Modifier, + backgroundType: PosterCardBackgroundType, + tag: Tag?, + preTitle: String?, + title: String?, + subtitle: String?, + description: String?, + customContent: (@Composable () -> Unit)? = null, +) { + Column( + modifier = modifier + .fillMaxWidth() + .background( + brush = if (backgroundType.inverseDisplay) { + MisticaTheme.brushes.cardContentOverlay + } else { + SolidColor(Color.Transparent) + } + ) + .padding( + top = 16.dp, + start = 16.dp, + end = 16.dp, + bottom = if (customContent != null) { + 0.dp + } else { + 24.dp + }, + ) + ) { + if (tag != null) { + Box(modifier = Modifier.padding(top = 16.dp, bottom = 20.dp).zIndex(2f)) { + tag.build() + } + } + + preTitle?.let { + PosterCardText( + modifier = Modifier + .padding(top = 4.dp) + .zIndex(2f), + textValue = preTitle, + textStyle = MisticaTheme.typography.preset1, + inverseDisplay = backgroundType.inverseDisplay + ) + } + + title?.let { + PosterCardText( + modifier = Modifier + .padding(top = 4.dp) + .zIndex(0f), + textValue = title, + textStyle = MisticaTheme.typography.presetCardTitle, + inverseDisplay = backgroundType.inverseDisplay + ) + } + subtitle?.let { + PosterCardText( + modifier = Modifier + .padding(top = 4.dp) + .zIndex(4f), + textValue = subtitle, + textStyle = MisticaTheme.typography.preset2, + inverseDisplay = backgroundType.inverseDisplay + ) + } + description?.let { + PosterCardText( + modifier = Modifier + .padding(top = 8.dp) + .zIndex(5f), + textValue = description, + textStyle = MisticaTheme.typography.preset2, + inverseDisplay = backgroundType.inverseDisplay + ) + } + + customContent?.let { + Box( + modifier = Modifier + .padding(top = 4.dp, bottom = 24.dp) + .zIndex(6f), + ) { + it() + } + } + } +} + +@Composable +internal fun PosterCardText( + modifier: Modifier = Modifier, + textValue: String, + textStyle: TextStyle, + inverseDisplay: Boolean, +) { + Text( + modifier = modifier, + text = textValue, + style = textStyle.also { style -> + if (inverseDisplay) { + style.copy(shadow = buildPosterCardTextShadow()) + } + }, + color = if (inverseDisplay) { + MisticaTheme.colors.textPrimaryInverse + } else { + MisticaTheme.colors.textPrimary + } + ) +} + +internal fun buildPosterCardTextShadow() = Shadow( + color = Color.Black.copy(alpha = 0.4f), + blurRadius = 15f +) diff --git a/library/src/main/java/com/telefonica/mistica/compose/card/postercard/PosterCardPreviews.kt b/library/src/main/java/com/telefonica/mistica/compose/card/postercard/PosterCardPreviews.kt new file mode 100644 index 000000000..8bf9b59aa --- /dev/null +++ b/library/src/main/java/com/telefonica/mistica/compose/card/postercard/PosterCardPreviews.kt @@ -0,0 +1,107 @@ +package com.telefonica.mistica.compose.card.postercard + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.telefonica.mistica.R +import com.telefonica.mistica.compose.tag.Tag +import com.telefonica.mistica.compose.theme.MisticaTheme +import com.telefonica.mistica.compose.theme.brand.TuBrand + +@Preview +@Composable +internal fun PosterCardSolidColor() { + MisticaTheme(brand = TuBrand) { + PosterCard( + modifier = Modifier.width(300.dp), + aspectRatio = PosterCardAspectRatio.AR_1_1, + backgroundType = PosterCardBackgroundType.Color(brush = SolidColor(MisticaTheme.colors.background), inverseDisplay = false), + headline = Tag(content = TAG_HEADLINE_VALUE), + preTitle = PRE_TITLE_VALUE, + title = TITLE_VALUE, + subtitle = SUBTITLE_VALUE, + description = DESCRIPTION_VALUE, + ) + } +} + +@Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true) +@Composable +internal fun PosterCardGradientColor() { + MisticaTheme(brand = TuBrand) { + PosterCard( + firstTopAction = TopActionData(iconRes = R.drawable.icn_visibility), + secondTopAction = TopActionData(iconRes = R.drawable.ic_close_regular), + aspectRatio = PosterCardAspectRatio.AR_16_9, + backgroundType = PosterCardBackgroundType.Color( + brush = Brush.verticalGradient(colors = listOf(Color.Magenta, Color.Red)), + inverseDisplay = true + ), + headline = Tag(content = TAG_HEADLINE_VALUE), + preTitle = PRE_TITLE_VALUE, + title = TITLE_VALUE, + subtitle = SUBTITLE_VALUE, + description = DESCRIPTION_VALUE + ) + } +} + +@Preview +@Composable +internal fun PosterCardImage() { + MisticaTheme(brand = TuBrand) { + PosterCard( + firstTopAction = TopActionData(iconRes = R.drawable.icn_visibility), + modifier = Modifier.fillMaxWidth(), + aspectRatio = PosterCardAspectRatio.AR_16_9, + backgroundType = PosterCardBackgroundType.Color(brush = SolidColor(MisticaTheme.colors.successLow)), + headline = Tag(content = TAG_HEADLINE_VALUE), + preTitle = PRE_TITLE_VALUE, + title = TITLE_VALUE, + subtitle = SUBTITLE_VALUE, + description = DESCRIPTION_VALUE, + customContent = { + CustomLowerContent() + } + ) + } +} + +@Composable +internal fun CustomLowerContent() { + Box( + modifier = Modifier + .fillMaxWidth() + .size(150.dp) + .background(color = MisticaTheme.colors.successHighInverse.copy(alpha = 0.5f)) + .border(width = 1.dp, color = MisticaTheme.colors.success), + contentAlignment = Alignment.Center + ) { + Text( + text = CUSTOM_SLOT_VALUE, + style = MisticaTheme.typography.preset2, + color = MisticaTheme.colors.textPrimaryInverse + ) + } +} + +private const val TAG_HEADLINE_VALUE = "Headline" +private const val PRE_TITLE_VALUE = "Pretitle" +private const val TITLE_VALUE = "Title" +private const val SUBTITLE_VALUE = "Subtitle" +private const val DESCRIPTION_VALUE = "Description" + +private const val CUSTOM_SLOT_VALUE = "Custom slot" \ No newline at end of file diff --git a/library/src/main/java/com/telefonica/mistica/compose/card/postercard/PosterCardTopActions.kt b/library/src/main/java/com/telefonica/mistica/compose/card/postercard/PosterCardTopActions.kt new file mode 100644 index 000000000..e6e495b27 --- /dev/null +++ b/library/src/main/java/com/telefonica/mistica/compose/card/postercard/PosterCardTopActions.kt @@ -0,0 +1,94 @@ +package com.telefonica.mistica.compose.card.postercard + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import com.telefonica.mistica.compose.theme.MisticaTheme + +@Composable +internal fun PosterCardTopActions( + modifier: Modifier = Modifier, + firstTopAction: TopActionData?, + secondTopAction: TopActionData?, +) { + Row( + modifier = modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically + ) { + firstTopAction?.let { + TopAction(topActionData = it) + } + secondTopAction?.let { + if (firstTopAction != null) { + Spacer(modifier = Modifier.width(16.dp)) + } + TopAction(topActionData = it) + } + } +} + +@Composable +internal fun TopAction(topActionData: TopActionData) { + with(topActionData) { + Box( + modifier = Modifier + .testTag(testTag.orEmpty()) + .size(40.dp) + .clip(CircleShape) + .clickable { onClick() } + .background( + color = when { + isInverse -> Color.Transparent + withBackground -> MisticaTheme.colors.controlInverse.copy(alpha = 0.8f) + else -> Color.Transparent + } + ) + .wrapContentSize(align = Alignment.Center) + ) { + Image( + painter = painterResource(id = iconRes), + contentDescription = contentDescription, + colorFilter = ColorFilter.tint( + if (isInverse) { + MisticaTheme.colors.inverse + } else { + MisticaTheme.colors.neutralHigh + } + ), + contentScale = ContentScale.Crop + ) + } + } +} + +data class TopActionData( + val iconRes: Int, + val contentDescription: String = "", + val testTag: String? = null, + val withBackground: Boolean = true, + val isInverse: Boolean = false, + val onClick: () -> Unit = {}, +) \ No newline at end of file diff --git a/library/src/main/java/com/telefonica/mistica/compose/card/postercard/README.md b/library/src/main/java/com/telefonica/mistica/compose/card/postercard/README.md new file mode 100644 index 000000000..3a166775b --- /dev/null +++ b/library/src/main/java/com/telefonica/mistica/compose/card/postercard/README.md @@ -0,0 +1,25 @@ +# Poster Card + +This component allows you to display content organized by the following sections: +* Tag +* Pretitle +* Title +* Subtitle +* Description + +Whose background can be a color (solid or gradient) or an image. +In addition, up to a maximum of 2 actions can be defined, which will be displayed in the upper right corner inside. + +```kotlin +PosterCard( + firstTopAction = TopActionData(iconRes = R.drawable.icn_visibility), + secondTopAction = TopActionData(iconRes = R.drawable.ic_close_regular), + aspectRatio = PosterCardAspectRatio.AR_16_9, + backgroundType = PosterCardBackgroundType.Color(brush = SolidColor(MisticaTheme.colors.background), inverseDisplay = false), + headline = Tag(content = "Headline"), + preTitle = "Pretitle", + title = "Title", + subtitle = "Subtitle", + description = "Description" +) +``` \ No newline at end of file