perf(res): convert all artwork to WebP; unify + blend pack art
Converts every PNG in res to WebP (124 files, 104M -> 28M on disk, -73%). Each file was encoded both lossless and lossy q95 and the smaller kept, but lossy only where it measured visually lossless (PSNR >= 40 dB); dimensions and alpha were verified per file before the PNG was removed, so 25 files that compressed better losslessly stayed exact. No nine-patches exist to break, and R.drawable refs are extension-agnostic (the only ".png" in code is a runtime share-cache filename). Debug APK 141.9 -> 128.8 MB; pack art in the APK 26.4 -> 11.5 MB. The disk saving is larger than the APK saving because AAPT2 already crunched PNGs at build time. Unifies pack artwork in a new PackArtwork.kt. packArtworkRes was duplicated verbatim in the library and detail screens — which is exactly how the two drifted before (one kept a grouped mapping that gave several packs the same illustration). It now exists once, alongside the two art composables. This is also where an imported pack's art would resolve. Pack detail page fixes: - Removed the second back arrow. The nav scaffold already supplies the "Question Pack" bar and its back affordance (AppRoute.kt), so the in-screen IconButton was a duplicate; onBack is now unused and gone. - The description is shown in full. It was capped at maxLines=4 with an ellipsis, which truncated most packs mid-sentence — this is the page where someone decides whether to open the pack, so it should not be abridged. - The hero art is blended instead of pasted on: it runs full-bleed (the list no longer pads horizontally; items pad themselves) and dissolves into the page. The dissolve is an alpha mask (BlendMode.DstIn), not a scrim in the background colour — the page behind is a gradient brush, so fading to any single colour left a visible pale band in light theme. Fading the image to transparent lets the real background through, correct in both themes. Library cards get a softer version of the same, into the card surface. Verified live on both fixtures (dark 5554 / light 5556): single back arrow, full description, art melting into the page with no hard edges, correct per-theme art. Unit tests green, 0 FATAL. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
@ -0,0 +1,141 @@
|
||||||
|
package app.closer.ui.questions
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.ui.draw.drawWithContent
|
||||||
|
import androidx.compose.ui.graphics.BlendMode
|
||||||
|
import androidx.compose.ui.graphics.CompositingStrategy
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import app.closer.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One home for pack artwork: which illustration a pack uses, and how it is blended onto a surface.
|
||||||
|
*
|
||||||
|
* The mapping lives here because it is needed by both the pack library and the pack detail screen.
|
||||||
|
* It previously existed as an identical copy in each, which is how the two drifted apart before
|
||||||
|
* (one screen kept a grouped mapping that gave several packs the same illustration).
|
||||||
|
*
|
||||||
|
* Light and dark artwork are separate assets resolved by the resource system
|
||||||
|
* (`drawable-nodpi` / `drawable-night-nodpi`), so nothing here needs to branch on theme.
|
||||||
|
*/
|
||||||
|
@DrawableRes
|
||||||
|
internal fun packArtworkRes(categoryId: String): Int = when (categoryId) {
|
||||||
|
"boundaries" -> R.drawable.pack_art_boundaries
|
||||||
|
"communication" -> R.drawable.pack_art_communication
|
||||||
|
"conflict" -> R.drawable.pack_art_conflict
|
||||||
|
"conflict_repair" -> R.drawable.pack_art_conflict_repair
|
||||||
|
"daily_fun_mc" -> R.drawable.pack_art_daily_fun_mc
|
||||||
|
"date_night" -> R.drawable.pack_art_date_night
|
||||||
|
"difficult_conversations" -> R.drawable.pack_art_deep_reflection
|
||||||
|
"emotional_intimacy" -> R.drawable.pack_art_intimacy
|
||||||
|
"couple_intimacy" -> R.drawable.pack_art_intimacy
|
||||||
|
"fun" -> R.drawable.pack_art_fun_date
|
||||||
|
"future" -> R.drawable.pack_art_future_goals
|
||||||
|
"gratitude" -> R.drawable.pack_art_gratitude
|
||||||
|
"home_life" -> R.drawable.pack_art_home_life
|
||||||
|
"marriage" -> R.drawable.pack_art_family_commitment
|
||||||
|
"money" -> R.drawable.pack_art_money_values
|
||||||
|
"parenting" -> R.drawable.pack_art_parenting
|
||||||
|
"physical_intimacy" -> R.drawable.pack_art_physical_intimacy
|
||||||
|
"quality_time" -> R.drawable.pack_art_quality_time
|
||||||
|
"rebuilding_trust" -> R.drawable.pack_art_trust_repair
|
||||||
|
"sex_and_desire" -> R.drawable.pack_art_desire
|
||||||
|
"sexual_preferences" -> R.drawable.pack_art_sexual_preferences
|
||||||
|
"stress" -> R.drawable.pack_art_stress
|
||||||
|
"trust" -> R.drawable.pack_art_trust
|
||||||
|
"values" -> R.drawable.pack_art_values
|
||||||
|
else -> R.drawable.pack_art_deep_reflection
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pack illustration on the pack detail page, blended into the page rather than sitting on it as
|
||||||
|
* a pasted rectangle: it runs full-bleed and its lower half dissolves away, so the title reads as
|
||||||
|
* continuing out of the illustration and there is no hard bottom edge.
|
||||||
|
*
|
||||||
|
* The dissolve is an alpha mask (`BlendMode.DstIn`) rather than a scrim in the background colour:
|
||||||
|
* the page behind is a gradient brush, not a flat colour, so fading to any single colour leaves a
|
||||||
|
* visible band. Fading the image itself to transparent lets whatever is behind show through, which
|
||||||
|
* is correct in both themes and for any future background.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
internal fun PackArtworkHeader(
|
||||||
|
categoryId: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
height: androidx.compose.ui.unit.Dp = 210.dp
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(packArtworkRes(categoryId)),
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(height)
|
||||||
|
// An offscreen layer is required for DstIn to mask only this image.
|
||||||
|
.graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }
|
||||||
|
.drawWithContent {
|
||||||
|
drawContent()
|
||||||
|
// Opaque over the subject (upper half of every pack illustration), then ramp to
|
||||||
|
// transparent so the art melts into the page.
|
||||||
|
drawRect(
|
||||||
|
brush = Brush.verticalGradient(
|
||||||
|
0f to Color.Black,
|
||||||
|
0.5f to Color.Black,
|
||||||
|
1f to Color.Transparent
|
||||||
|
),
|
||||||
|
blendMode = BlendMode.DstIn
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pack illustration on a library card. The card already separates art from body with an accent
|
||||||
|
* rule, so this only softens the join into [fadeTo] (the card surface) instead of dissolving fully.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
internal fun PackArtworkCardImage(
|
||||||
|
categoryId: String,
|
||||||
|
fadeTo: Color,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
height: androidx.compose.ui.unit.Dp = 94.dp
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(height)
|
||||||
|
.clip(RoundedCornerShape(topStart = 22.dp, topEnd = 22.dp))
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(packArtworkRes(categoryId)),
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.matchParentSize()
|
||||||
|
.background(
|
||||||
|
Brush.verticalGradient(
|
||||||
|
0f to Color.Transparent,
|
||||||
|
0.6f to Color.Transparent,
|
||||||
|
1f to fadeTo.copy(alpha = 0.55f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,6 @@ import app.closer.ui.theme.closerCardColor
|
||||||
import app.closer.ui.theme.closerBackgroundBrush
|
import app.closer.ui.theme.closerBackgroundBrush
|
||||||
import app.closer.ui.theme.closerSoftPurpleColor
|
import app.closer.ui.theme.closerSoftPurpleColor
|
||||||
import app.closer.ui.theme.closerSoftSurfaceColor
|
import app.closer.ui.theme.closerSoftSurfaceColor
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
|
@ -29,7 +28,6 @@ import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import app.closer.ui.components.CloserHeartLoader
|
import app.closer.ui.components.CloserHeartLoader
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
|
@ -39,10 +37,7 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
|
@ -53,7 +48,6 @@ import app.closer.core.navigation.AppRoute
|
||||||
import app.closer.domain.model.Question
|
import app.closer.domain.model.Question
|
||||||
import app.closer.domain.model.QuestionCategory
|
import app.closer.domain.model.QuestionCategory
|
||||||
import app.closer.ui.components.CategoryGlyph
|
import app.closer.ui.components.CategoryGlyph
|
||||||
import app.closer.ui.components.CloserGlyphs
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun QuestionCategoryScreen(
|
fun QuestionCategoryScreen(
|
||||||
|
|
@ -66,7 +60,6 @@ fun QuestionCategoryScreen(
|
||||||
QuestionCategoryContent(
|
QuestionCategoryContent(
|
||||||
categoryId = categoryId,
|
categoryId = categoryId,
|
||||||
state = state,
|
state = state,
|
||||||
onBack = { onNavigate("back") },
|
|
||||||
onPickPrompt = {
|
onPickPrompt = {
|
||||||
val question = state.questions.randomOrNull()
|
val question = state.questions.randomOrNull()
|
||||||
if (question != null) {
|
if (question != null) {
|
||||||
|
|
@ -85,7 +78,6 @@ fun QuestionCategoryScreen(
|
||||||
private fun QuestionCategoryContent(
|
private fun QuestionCategoryContent(
|
||||||
categoryId: String,
|
categoryId: String,
|
||||||
state: QuestionCategoryUiState,
|
state: QuestionCategoryUiState,
|
||||||
onBack: () -> Unit,
|
|
||||||
onPickPrompt: () -> Unit
|
onPickPrompt: () -> Unit
|
||||||
) {
|
) {
|
||||||
val title = state.category?.displayName ?: categoryId.displayCategoryName()
|
val title = state.category?.displayName ?: categoryId.displayCategoryName()
|
||||||
|
|
@ -99,33 +91,18 @@ private fun QuestionCategoryContent(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.safeDrawingPadding()
|
.safeDrawingPadding()
|
||||||
.navigationBarsPadding()
|
.navigationBarsPadding(),
|
||||||
.padding(horizontal = 20.dp),
|
// No horizontal padding here: the pack artwork runs full-bleed so it can dissolve into
|
||||||
|
// the page without hard side edges. Every other item pads itself instead.
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
item {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(top = 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
IconButton(onClick = onBack) {
|
|
||||||
Icon(
|
|
||||||
CloserGlyphs.Back,
|
|
||||||
contentDescription = "Back",
|
|
||||||
tint = MaterialTheme.colorScheme.onBackground
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
item {
|
||||||
PackArtworkHeader(categoryId = categoryId)
|
PackArtworkHeader(categoryId = categoryId)
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
CategoryHero(
|
CategoryHero(
|
||||||
|
modifier = Modifier.padding(horizontal = 20.dp),
|
||||||
title = title,
|
title = title,
|
||||||
category = state.category,
|
category = state.category,
|
||||||
questionCount = state.questions.size,
|
questionCount = state.questions.size,
|
||||||
|
|
@ -134,9 +111,12 @@ private fun QuestionCategoryContent(
|
||||||
}
|
}
|
||||||
|
|
||||||
when {
|
when {
|
||||||
state.isLoading -> item { CategoryLoadingCard() }
|
state.isLoading -> item {
|
||||||
|
CategoryLoadingCard(modifier = Modifier.padding(horizontal = 20.dp))
|
||||||
|
}
|
||||||
state.error != null -> item {
|
state.error != null -> item {
|
||||||
CategoryMessageCard(
|
CategoryMessageCard(
|
||||||
|
modifier = Modifier.padding(horizontal = 20.dp),
|
||||||
title = "Pack unavailable",
|
title = "Pack unavailable",
|
||||||
message = state.error
|
message = state.error
|
||||||
)
|
)
|
||||||
|
|
@ -147,6 +127,7 @@ private fun QuestionCategoryContent(
|
||||||
enabled = state.questions.isNotEmpty(),
|
enabled = state.questions.isNotEmpty(),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 20.dp)
|
||||||
.heightIn(min = 54.dp),
|
.heightIn(min = 54.dp),
|
||||||
shape = RoundedCornerShape(18.dp),
|
shape = RoundedCornerShape(18.dp),
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
|
@ -172,13 +153,14 @@ private fun QuestionCategoryContent(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CategoryHero(
|
private fun CategoryHero(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
title: String,
|
title: String,
|
||||||
category: QuestionCategory?,
|
category: QuestionCategory?,
|
||||||
questionCount: Int,
|
questionCount: Int,
|
||||||
isLoading: Boolean = false
|
isLoading: Boolean = false
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
|
|
@ -197,19 +179,17 @@ private fun CategoryHero(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
|
// The pack detail page is where someone decides whether to open this pack, so the
|
||||||
|
// title and the full description are shown in full — no line cap, no ellipsis.
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
style = MaterialTheme.typography.headlineLarge.copy(fontWeight = FontWeight.SemiBold),
|
style = MaterialTheme.typography.headlineLarge.copy(fontWeight = FontWeight.SemiBold),
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = category?.description ?: "Questions for this kind of conversation.",
|
text = category?.description ?: "Questions for this kind of conversation.",
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
maxLines = 4,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -235,50 +215,7 @@ private fun CategoryHero(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun PackArtworkHeader(categoryId: String) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(160.dp)
|
|
||||||
.clip(RoundedCornerShape(24.dp))
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(packArtworkRes(categoryId)),
|
|
||||||
contentDescription = null,
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun packArtworkRes(categoryId: String): Int = when (categoryId) {
|
|
||||||
"boundaries" -> R.drawable.pack_art_boundaries
|
|
||||||
"communication" -> R.drawable.pack_art_communication
|
|
||||||
"conflict" -> R.drawable.pack_art_conflict
|
|
||||||
"conflict_repair" -> R.drawable.pack_art_conflict_repair
|
|
||||||
"daily_fun_mc" -> R.drawable.pack_art_daily_fun_mc
|
|
||||||
"date_night" -> R.drawable.pack_art_date_night
|
|
||||||
"difficult_conversations" -> R.drawable.pack_art_deep_reflection
|
|
||||||
"emotional_intimacy" -> R.drawable.pack_art_intimacy
|
|
||||||
"couple_intimacy" -> R.drawable.pack_art_intimacy
|
|
||||||
"fun" -> R.drawable.pack_art_fun_date
|
|
||||||
"future" -> R.drawable.pack_art_future_goals
|
|
||||||
"gratitude" -> R.drawable.pack_art_gratitude
|
|
||||||
"home_life" -> R.drawable.pack_art_home_life
|
|
||||||
"marriage" -> R.drawable.pack_art_family_commitment
|
|
||||||
"money" -> R.drawable.pack_art_money_values
|
|
||||||
"parenting" -> R.drawable.pack_art_parenting
|
|
||||||
"physical_intimacy" -> R.drawable.pack_art_physical_intimacy
|
|
||||||
"quality_time" -> R.drawable.pack_art_quality_time
|
|
||||||
"rebuilding_trust" -> R.drawable.pack_art_trust_repair
|
|
||||||
"sex_and_desire" -> R.drawable.pack_art_desire
|
|
||||||
"sexual_preferences" -> R.drawable.pack_art_sexual_preferences
|
|
||||||
"stress" -> R.drawable.pack_art_stress
|
|
||||||
"trust" -> R.drawable.pack_art_trust
|
|
||||||
"values" -> R.drawable.pack_art_values
|
|
||||||
else -> R.drawable.pack_art_deep_reflection
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CategoryPill(
|
private fun CategoryPill(
|
||||||
|
|
@ -302,9 +239,9 @@ private fun CategoryPill(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CategoryLoadingCard() {
|
private fun CategoryLoadingCard(modifier: Modifier = Modifier) {
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(24.dp),
|
shape = RoundedCornerShape(24.dp),
|
||||||
colors = CardDefaults.cardColors(containerColor = closerCardColor(alpha = 0.82f))
|
colors = CardDefaults.cardColors(containerColor = closerCardColor(alpha = 0.82f))
|
||||||
) {
|
) {
|
||||||
|
|
@ -326,9 +263,9 @@ private fun CategoryLoadingCard() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CategoryMessageCard(title: String, message: String) {
|
private fun CategoryMessageCard(title: String, message: String, modifier: Modifier = Modifier) {
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(24.dp),
|
shape = RoundedCornerShape(24.dp),
|
||||||
colors = CardDefaults.cardColors(containerColor = closerCardColor(alpha = 0.82f))
|
colors = CardDefaults.cardColors(containerColor = closerCardColor(alpha = 0.82f))
|
||||||
) {
|
) {
|
||||||
|
|
@ -379,7 +316,6 @@ fun QuestionCategoryScreenPreview() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
onBack = {},
|
|
||||||
onPickPrompt = {}
|
onPickPrompt = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,9 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
|
@ -213,17 +210,12 @@ private fun QuestionPackCard(
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp)
|
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp)
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.fillMaxWidth()) {
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
Box(
|
Box(modifier = Modifier.fillMaxWidth()) {
|
||||||
modifier = Modifier
|
// Softened into the card surface so the illustration reads as part of the card
|
||||||
.fillMaxWidth()
|
// rather than a photo pasted on top of it; the accent rule still marks the join.
|
||||||
.height(94.dp)
|
PackArtworkCardImage(
|
||||||
.clip(RoundedCornerShape(topStart = 22.dp, topEnd = 22.dp))
|
categoryId = item.category.id,
|
||||||
) {
|
fadeTo = containerColor
|
||||||
Image(
|
|
||||||
painter = painterResource(packArtworkRes(item.category.id)),
|
|
||||||
contentDescription = null,
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
)
|
)
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -386,33 +378,6 @@ private fun packAccent(categoryId: String): Color {
|
||||||
return palette[kotlin.math.abs(categoryId.hashCode()) % palette.size]
|
return palette[kotlin.math.abs(categoryId.hashCode()) % palette.size]
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun packArtworkRes(categoryId: String): Int = when (categoryId) {
|
|
||||||
"boundaries" -> R.drawable.pack_art_boundaries
|
|
||||||
"communication" -> R.drawable.pack_art_communication
|
|
||||||
"conflict" -> R.drawable.pack_art_conflict
|
|
||||||
"conflict_repair" -> R.drawable.pack_art_conflict_repair
|
|
||||||
"daily_fun_mc" -> R.drawable.pack_art_daily_fun_mc
|
|
||||||
"date_night" -> R.drawable.pack_art_date_night
|
|
||||||
"difficult_conversations" -> R.drawable.pack_art_deep_reflection
|
|
||||||
"emotional_intimacy" -> R.drawable.pack_art_intimacy
|
|
||||||
"couple_intimacy" -> R.drawable.pack_art_intimacy
|
|
||||||
"fun" -> R.drawable.pack_art_fun_date
|
|
||||||
"future" -> R.drawable.pack_art_future_goals
|
|
||||||
"gratitude" -> R.drawable.pack_art_gratitude
|
|
||||||
"home_life" -> R.drawable.pack_art_home_life
|
|
||||||
"marriage" -> R.drawable.pack_art_family_commitment
|
|
||||||
"money" -> R.drawable.pack_art_money_values
|
|
||||||
"parenting" -> R.drawable.pack_art_parenting
|
|
||||||
"physical_intimacy" -> R.drawable.pack_art_physical_intimacy
|
|
||||||
"quality_time" -> R.drawable.pack_art_quality_time
|
|
||||||
"rebuilding_trust" -> R.drawable.pack_art_trust_repair
|
|
||||||
"sex_and_desire" -> R.drawable.pack_art_desire
|
|
||||||
"sexual_preferences" -> R.drawable.pack_art_sexual_preferences
|
|
||||||
"stress" -> R.drawable.pack_art_stress
|
|
||||||
"trust" -> R.drawable.pack_art_trust
|
|
||||||
"values" -> R.drawable.pack_art_values
|
|
||||||
else -> R.drawable.pack_art_deep_reflection
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LoadingPackCard() {
|
private fun LoadingPackCard() {
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 658 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 666 B |
|
Before Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 476 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 474 B |
|
Before Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 206 KiB |
|
Before Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 296 KiB |
|
Before Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 555 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 647 KiB |
|
After Width: | Height: | Size: 491 KiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 264 KiB |
|
Before Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 337 KiB |
|
Before Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 692 KiB |
|
After Width: | Height: | Size: 525 KiB |
|
Before Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 273 KiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 273 KiB |
|
Before Width: | Height: | Size: 850 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 892 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 531 KiB |
|
After Width: | Height: | Size: 397 KiB |
|
Before Width: | Height: | Size: 628 KiB |
|
After Width: | Height: | Size: 459 KiB |
|
Before Width: | Height: | Size: 422 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 633 KiB |
|
After Width: | Height: | Size: 458 KiB |
|
Before Width: | Height: | Size: 681 KiB |
|
After Width: | Height: | Size: 498 KiB |
|
Before Width: | Height: | Size: 650 KiB |
|
After Width: | Height: | Size: 483 KiB |
|
Before Width: | Height: | Size: 697 KiB |
|
After Width: | Height: | Size: 515 KiB |
|
Before Width: | Height: | Size: 477 KiB |
|
After Width: | Height: | Size: 357 KiB |
|
Before Width: | Height: | Size: 446 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 458 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 443 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 463 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 671 KiB |
|
After Width: | Height: | Size: 489 KiB |
|
Before Width: | Height: | Size: 449 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 437 KiB |