fix(questions): update category and pack library screens
This commit is contained in:
parent
888ffa3c1a
commit
0e9606366b
|
|
@ -1,6 +1,8 @@
|
||||||
package com.couplesconnect.app.ui.questions
|
package com.couplesconnect.app.ui.questions
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
|
@ -10,6 +12,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
|
@ -22,6 +25,9 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
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.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
|
@ -59,6 +65,13 @@ private fun QuestionCategoryContent(
|
||||||
state: QuestionCategoryUiState,
|
state: QuestionCategoryUiState,
|
||||||
onQuestionSelected: (Question) -> Unit
|
onQuestionSelected: (Question) -> Unit
|
||||||
) {
|
) {
|
||||||
|
var selectedType by remember { mutableStateOf<String?>(null) }
|
||||||
|
val visibleQuestions = remember(state.questions, selectedType) {
|
||||||
|
state.questions.filter { question ->
|
||||||
|
selectedType == null || question.type == selectedType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
|
@ -81,22 +94,12 @@ private fun QuestionCategoryContent(
|
||||||
item {
|
item {
|
||||||
val title = state.category?.displayName
|
val title = state.category?.displayName
|
||||||
?: categoryId.displayCategoryName()
|
?: categoryId.displayCategoryName()
|
||||||
Column(
|
CategoryHero(
|
||||||
modifier = Modifier.padding(top = 20.dp, bottom = 6.dp),
|
title = title,
|
||||||
verticalArrangement = Arrangement.spacedBy(10.dp)
|
category = state.category,
|
||||||
) {
|
questionCount = state.questions.size,
|
||||||
Text(
|
modifier = Modifier.padding(top = 20.dp, bottom = 6.dp)
|
||||||
text = title,
|
)
|
||||||
style = MaterialTheme.typography.headlineLarge.copy(fontWeight = FontWeight.SemiBold),
|
|
||||||
color = Color(0xFF27211F)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = state.category?.description
|
|
||||||
?: "Browse prompts for this kind of conversation.",
|
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
|
||||||
color = Color(0xFF4E4642)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
when {
|
when {
|
||||||
|
|
@ -115,23 +118,163 @@ private fun QuestionCategoryContent(
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
item {
|
item {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
CategoryFilters(
|
||||||
CategoryPill("${state.questions.size} prompts")
|
questions = state.questions,
|
||||||
state.category?.access?.let { CategoryPill(it.displayCategoryName()) }
|
selectedType = selectedType,
|
||||||
}
|
onTypeSelected = { selectedType = it }
|
||||||
}
|
|
||||||
items(state.questions, key = { it.id }) { question ->
|
|
||||||
QuestionListCard(
|
|
||||||
question = question,
|
|
||||||
onClick = { onQuestionSelected(question) }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (visibleQuestions.isEmpty()) {
|
||||||
|
item {
|
||||||
|
CategoryMessageCard(
|
||||||
|
title = "No prompts match",
|
||||||
|
message = "Try another filter to keep browsing."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
visibleQuestions.groupBy { it.depthLevel }.toSortedMap().forEach { (depth, questions) ->
|
||||||
|
item(key = "depth-$depth") {
|
||||||
|
DepthHeader(depth = depth, count = questions.size)
|
||||||
|
}
|
||||||
|
items(questions, key = { it.id }) { question ->
|
||||||
|
QuestionListCard(
|
||||||
|
question = question,
|
||||||
|
onClick = { onQuestionSelected(question) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CategoryHero(
|
||||||
|
title: String,
|
||||||
|
category: QuestionCategory?,
|
||||||
|
questionCount: Int,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.headlineLarge.copy(fontWeight = FontWeight.SemiBold),
|
||||||
|
color = Color(0xFF27211F),
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = category?.description
|
||||||
|
?: "Browse prompts for this kind of conversation.",
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = Color(0xFF4E4642)
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.horizontalScroll(rememberScrollState()),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
CategoryPill("$questionCount ${if (questionCount == 1) "prompt" else "prompts"}", emphasis = true)
|
||||||
|
category?.access?.let { CategoryPill(it.displayCategoryName()) }
|
||||||
|
category?.iconName
|
||||||
|
?.takeIf { it.isNotBlank() }
|
||||||
|
?.let { it.displayCategoryName() }
|
||||||
|
?.takeIf { it != "Question" }
|
||||||
|
?.let { CategoryPill(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CategoryFilters(
|
||||||
|
questions: List<Question>,
|
||||||
|
selectedType: String?,
|
||||||
|
onTypeSelected: (String?) -> Unit
|
||||||
|
) {
|
||||||
|
val types = questions.map { it.type }.distinct().sorted()
|
||||||
|
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
Text(
|
||||||
|
text = "Format",
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = Color(0xFF4E4642),
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.horizontalScroll(rememberScrollState()),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
(listOf(null) + types).forEach { option ->
|
||||||
|
FilterPill(
|
||||||
|
label = option?.displayQuestionFilterName() ?: "All",
|
||||||
|
selected = selectedType == option,
|
||||||
|
onClick = { onTypeSelected(option) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.displayQuestionFilterName(): String {
|
||||||
|
return when (this) {
|
||||||
|
"single_choice" -> "Single"
|
||||||
|
"multi_choice" -> "Multi"
|
||||||
|
"this_or_that" -> "Either/or"
|
||||||
|
"scale" -> "Scale"
|
||||||
|
else -> "Written"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun FilterPill(
|
||||||
|
label: String,
|
||||||
|
selected: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.clickable(onClick = onClick),
|
||||||
|
shape = RoundedCornerShape(999.dp),
|
||||||
|
color = if (selected) Color(0xFFF3E8FF) else Color.White.copy(alpha = 0.74f),
|
||||||
|
shadowElevation = if (selected) 2.dp else 0.dp
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
modifier = Modifier.padding(horizontal = 13.dp, vertical = 8.dp),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = if (selected) Color(0xFF5F3A87) else Color(0xFF3E3734),
|
||||||
|
fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Medium,
|
||||||
|
maxLines = 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DepthHeader(depth: Int, count: Int) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 6.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Depth $depth",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = Color(0xFF27211F),
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
CategoryPill("$count ${if (count == 1) "prompt" else "prompts"}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun QuestionListCard(
|
private fun QuestionListCard(
|
||||||
question: Question,
|
question: Question,
|
||||||
|
|
@ -140,13 +283,13 @@ private fun QuestionListCard(
|
||||||
Card(
|
Card(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(22.dp),
|
shape = RoundedCornerShape(20.dp),
|
||||||
colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.84f)),
|
colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.9f)),
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 5.dp)
|
elevation = CardDefaults.cardElevation(defaultElevation = 3.dp)
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(17.dp),
|
modifier = Modifier.padding(16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = question.text,
|
text = question.text,
|
||||||
|
|
@ -157,7 +300,6 @@ private fun QuestionListCard(
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
CategoryPill("Depth ${question.depthLevel}")
|
|
||||||
CategoryPill(question.type.displayQuestionType())
|
CategoryPill(question.type.displayQuestionType())
|
||||||
if (question.isPremium) {
|
if (question.isPremium) {
|
||||||
CategoryPill("Premium")
|
CategoryPill("Premium")
|
||||||
|
|
@ -170,16 +312,19 @@ private fun QuestionListCard(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CategoryPill(label: String) {
|
private fun CategoryPill(
|
||||||
|
label: String,
|
||||||
|
emphasis: Boolean = false
|
||||||
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
shape = RoundedCornerShape(999.dp),
|
shape = RoundedCornerShape(999.dp),
|
||||||
color = Color(0xFFF8F4F1)
|
color = if (emphasis) Color(0xFFF3E8FF) else Color(0xFFF8F4F1)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
modifier = Modifier.padding(horizontal = 11.dp, vertical = 7.dp),
|
modifier = Modifier.padding(horizontal = 11.dp, vertical = 7.dp),
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = Color(0xFF3E3734),
|
color = if (emphasis) Color(0xFF5F3A87) else Color(0xFF3E3734),
|
||||||
maxLines = 1
|
maxLines = 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,36 @@
|
||||||
package com.couplesconnect.app.ui.questions
|
package com.couplesconnect.app.ui.questions
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Lock
|
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
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
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
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.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
|
@ -41,6 +44,13 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import com.couplesconnect.app.core.navigation.AppRoute
|
import com.couplesconnect.app.core.navigation.AppRoute
|
||||||
import com.couplesconnect.app.domain.model.QuestionCategory
|
import com.couplesconnect.app.domain.model.QuestionCategory
|
||||||
|
|
||||||
|
private enum class PackFilter(val label: String) {
|
||||||
|
ALL("All"),
|
||||||
|
FREE("Free"),
|
||||||
|
MIXED("Mixed"),
|
||||||
|
PREMIUM("Premium")
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun QuestionPackLibraryScreen(
|
fun QuestionPackLibraryScreen(
|
||||||
onNavigate: (String) -> Unit = {},
|
onNavigate: (String) -> Unit = {},
|
||||||
|
|
@ -61,6 +71,18 @@ private fun QuestionPackLibraryContent(
|
||||||
onPackSelected: (QuestionPackItem) -> Unit,
|
onPackSelected: (QuestionPackItem) -> Unit,
|
||||||
onPaywall: () -> Unit
|
onPaywall: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
var selectedFilter by remember { mutableStateOf(PackFilter.ALL) }
|
||||||
|
val visiblePacks = remember(state.packs, selectedFilter) {
|
||||||
|
state.packs.filter { item ->
|
||||||
|
when (selectedFilter) {
|
||||||
|
PackFilter.ALL -> true
|
||||||
|
PackFilter.FREE -> item.category.access == "free"
|
||||||
|
PackFilter.MIXED -> item.category.access == "mixed"
|
||||||
|
PackFilter.PREMIUM -> item.category.access == "premium"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
|
@ -112,8 +134,28 @@ private fun QuestionPackLibraryContent(
|
||||||
message = "Question packs are not available right now. Try again in a moment."
|
message = "Question packs are not available right now. Try again in a moment."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
visiblePacks.isEmpty() -> {
|
||||||
|
item {
|
||||||
|
PackFilterRow(
|
||||||
|
selected = selectedFilter,
|
||||||
|
onSelected = { selectedFilter = it }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
PackMessageCard(
|
||||||
|
title = "Nothing in ${selectedFilter.label.lowercase()} yet",
|
||||||
|
message = "Try another filter to keep browsing."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
items(state.packs, key = { it.category.id }) { item ->
|
item {
|
||||||
|
PackFilterRow(
|
||||||
|
selected = selectedFilter,
|
||||||
|
onSelected = { selectedFilter = it }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
items(visiblePacks, key = { it.category.id }) { item ->
|
||||||
QuestionPackCard(
|
QuestionPackCard(
|
||||||
item = item,
|
item = item,
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|
@ -148,82 +190,164 @@ private fun QuestionPackCard(
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
val containerColor = if (item.isLocked)
|
val containerColor = if (item.isLocked)
|
||||||
Color(0xFFF5F0EC).copy(alpha = 0.84f)
|
Color(0xFFFAF7F5).copy(alpha = 0.9f)
|
||||||
else
|
else
|
||||||
Color.White.copy(alpha = 0.84f)
|
Color.White.copy(alpha = 0.9f)
|
||||||
|
val accent = packAccent(item.category.id)
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(26.dp),
|
shape = RoundedCornerShape(22.dp),
|
||||||
colors = CardDefaults.cardColors(containerColor = containerColor),
|
colors = CardDefaults.cardColors(containerColor = containerColor),
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
|
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp)
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
modifier = Modifier.padding(18.dp),
|
Box(
|
||||||
verticalArrangement = Arrangement.spacedBy(14.dp)
|
modifier = Modifier
|
||||||
) {
|
.fillMaxWidth()
|
||||||
Row(
|
.height(5.dp)
|
||||||
modifier = Modifier.fillMaxWidth(),
|
.background(if (item.isLocked) Color(0xFFD9D1CE) else accent)
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
)
|
||||||
verticalAlignment = Alignment.Top
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(17.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
Column(
|
Row(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalArrangement = Arrangement.spacedBy(6.dp)
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
verticalAlignment = Alignment.Top
|
||||||
) {
|
) {
|
||||||
Text(
|
Column(
|
||||||
text = item.category.displayName.ifBlank { item.category.id.displayCategoryName() },
|
modifier = Modifier.weight(1f),
|
||||||
style = MaterialTheme.typography.titleLarge,
|
verticalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
color = if (item.isLocked) Color(0xFF9E9693) else Color(0xFF27211F),
|
) {
|
||||||
fontWeight = FontWeight.SemiBold,
|
Text(
|
||||||
maxLines = 2,
|
text = item.category.displayName.ifBlank { item.category.id.displayCategoryName() },
|
||||||
overflow = TextOverflow.Ellipsis
|
style = MaterialTheme.typography.titleLarge,
|
||||||
)
|
color = Color(0xFF27211F),
|
||||||
Text(
|
fontWeight = FontWeight.SemiBold,
|
||||||
text = item.category.description,
|
maxLines = 1,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
overflow = TextOverflow.Ellipsis
|
||||||
color = Color(0xFF4E4642),
|
)
|
||||||
maxLines = 3,
|
Text(
|
||||||
overflow = TextOverflow.Ellipsis
|
text = item.category.description,
|
||||||
)
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = Color(0xFF4E4642),
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
PackPill(item.promptCountLabel(), emphasis = true)
|
||||||
}
|
}
|
||||||
if (item.isLocked) {
|
Row(
|
||||||
Icon(
|
modifier = Modifier
|
||||||
imageVector = Icons.Default.Lock,
|
.fillMaxWidth()
|
||||||
contentDescription = "Premium",
|
.horizontalScroll(rememberScrollState()),
|
||||||
tint = Color(0xFFB0A9A6),
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
modifier = Modifier.size(20.dp)
|
) {
|
||||||
)
|
item.metadataLabels().forEach { label ->
|
||||||
} else {
|
PackPill(label, emphasis = label == "Premium")
|
||||||
PackPill("${item.questionCount} prompts")
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
||||||
if (item.isPremium) PackPill("Premium")
|
|
||||||
PackPill(item.category.access.displayCategoryName())
|
|
||||||
PackPill(item.category.iconName.ifBlank { "question" }.displayCategoryName())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PackPill(label: String) {
|
private fun PackFilterRow(
|
||||||
|
selected: PackFilter,
|
||||||
|
onSelected: (PackFilter) -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.horizontalScroll(rememberScrollState()),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
PackFilter.entries.forEach { filter ->
|
||||||
|
FilterPill(
|
||||||
|
label = filter.label,
|
||||||
|
selected = selected == filter,
|
||||||
|
onClick = { onSelected(filter) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun FilterPill(
|
||||||
|
label: String,
|
||||||
|
selected: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.clickable(onClick = onClick),
|
||||||
|
shape = RoundedCornerShape(999.dp),
|
||||||
|
color = if (selected) Color(0xFFF3E8FF) else Color.White.copy(alpha = 0.74f),
|
||||||
|
tonalElevation = 0.dp,
|
||||||
|
shadowElevation = if (selected) 3.dp else 0.dp
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
modifier = Modifier.padding(horizontal = 13.dp, vertical = 8.dp),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = if (selected) Color(0xFF5F3A87) else Color(0xFF3E3734),
|
||||||
|
fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Medium,
|
||||||
|
maxLines = 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PackPill(
|
||||||
|
label: String,
|
||||||
|
emphasis: Boolean = false
|
||||||
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
shape = RoundedCornerShape(999.dp),
|
shape = RoundedCornerShape(999.dp),
|
||||||
color = Color(0xFFF8F4F1)
|
color = if (emphasis) Color(0xFFF3E8FF) else Color(0xFFF8F4F1)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
modifier = Modifier.padding(horizontal = 11.dp, vertical = 7.dp),
|
modifier = Modifier.padding(horizontal = 11.dp, vertical = 7.dp),
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = Color(0xFF3E3734),
|
color = if (emphasis) Color(0xFF5F3A87) else Color(0xFF3E3734),
|
||||||
maxLines = 1
|
maxLines = 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun QuestionPackItem.promptCountLabel(): String =
|
||||||
|
"$questionCount ${if (questionCount == 1) "prompt" else "prompts"}"
|
||||||
|
|
||||||
|
private fun QuestionPackItem.metadataLabels(): List<String> {
|
||||||
|
val access = when (category.access) {
|
||||||
|
"premium" -> "Premium"
|
||||||
|
"mixed" -> "Mixed access"
|
||||||
|
"free" -> "Free"
|
||||||
|
else -> category.access.displayCategoryName()
|
||||||
|
}
|
||||||
|
return listOf(
|
||||||
|
access,
|
||||||
|
category.iconName.ifBlank { "question" }.displayCategoryName()
|
||||||
|
).filterNot { it == "Question" }.distinct()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun packAccent(categoryId: String): Color {
|
||||||
|
val palette = listOf(
|
||||||
|
Color(0xFF5F3A87),
|
||||||
|
Color(0xFF5C7C8A),
|
||||||
|
Color(0xFF6F7D4F),
|
||||||
|
Color(0xFF8A5A74),
|
||||||
|
Color(0xFF7A6A3A)
|
||||||
|
)
|
||||||
|
return palette[kotlin.math.abs(categoryId.hashCode()) % palette.size]
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LoadingPackCard() {
|
private fun LoadingPackCard() {
|
||||||
Card(
|
Card(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue