android ui
This commit is contained in:
@@ -23,6 +23,7 @@ import com.example.casera.viewmodel.TaskCompletionViewModel
|
|||||||
import com.example.casera.viewmodel.TaskViewModel
|
import com.example.casera.viewmodel.TaskViewModel
|
||||||
import com.example.casera.models.TaskDetail
|
import com.example.casera.models.TaskDetail
|
||||||
import com.example.casera.network.ApiResult
|
import com.example.casera.network.ApiResult
|
||||||
|
import com.example.casera.ui.theme.*
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -105,7 +106,9 @@ fun AllTasksScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WarmGradientBackground {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
containerColor = androidx.compose.ui.graphics.Color.Transparent,
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
@@ -135,7 +138,7 @@ fun AllTasksScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
containerColor = androidx.compose.ui.graphics.Color.Transparent
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -156,46 +159,35 @@ fun AllTasksScreen(
|
|||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally,
|
horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.cozy),
|
||||||
modifier = Modifier.padding(24.dp)
|
modifier = Modifier.padding(OrganicSpacing.comfortable)
|
||||||
) {
|
) {
|
||||||
Icon(
|
OrganicIconContainer(
|
||||||
Icons.Default.Assignment,
|
icon = Icons.Default.Assignment,
|
||||||
contentDescription = null,
|
size = 80.dp,
|
||||||
modifier = Modifier.size(80.dp),
|
iconScale = 0.6f,
|
||||||
tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f)
|
backgroundColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f),
|
||||||
|
iconColor = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
"No tasks yet",
|
"No tasks yet",
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
fontWeight = FontWeight.SemiBold
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
"Create your first task to get started",
|
"Create your first task to get started",
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.textSecondary
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.compact))
|
||||||
Button(
|
OrganicPrimaryButton(
|
||||||
|
text = "Add Task",
|
||||||
onClick = { showNewTaskDialog = true },
|
onClick = { showNewTaskDialog = true },
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(0.7f),
|
||||||
.fillMaxWidth(0.7f)
|
|
||||||
.height(56.dp),
|
|
||||||
enabled = myResidencesState is ApiResult.Success &&
|
enabled = myResidencesState is ApiResult.Success &&
|
||||||
(myResidencesState as ApiResult.Success).data.residences.isNotEmpty()
|
(myResidencesState as ApiResult.Success).data.residences.isNotEmpty()
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Icon(Icons.Default.Add, contentDescription = null)
|
|
||||||
Text(
|
|
||||||
"Add Task",
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
if (myResidencesState is ApiResult.Success &&
|
if (myResidencesState is ApiResult.Success &&
|
||||||
(myResidencesState as ApiResult.Success).data.residences.isEmpty()) {
|
(myResidencesState as ApiResult.Success).data.residences.isEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
@@ -266,6 +258,7 @@ fun AllTasksScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (showCompleteDialog && selectedTask != null) {
|
if (showCompleteDialog && selectedTask != null) {
|
||||||
CompleteTaskDialog(
|
CompleteTaskDialog(
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import androidx.compose.foundation.horizontalScroll
|
|||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -30,8 +29,7 @@ import com.example.casera.models.TaskCompletionCreateRequest
|
|||||||
import com.example.casera.models.ContractorSummary
|
import com.example.casera.models.ContractorSummary
|
||||||
import com.example.casera.network.ApiResult
|
import com.example.casera.network.ApiResult
|
||||||
import com.example.casera.platform.*
|
import com.example.casera.platform.*
|
||||||
import com.example.casera.ui.theme.AppRadius
|
import com.example.casera.ui.theme.*
|
||||||
import com.example.casera.ui.theme.AppSpacing
|
|
||||||
import com.example.casera.viewmodel.ContractorViewModel
|
import com.example.casera.viewmodel.ContractorViewModel
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@@ -74,7 +72,9 @@ fun CompleteTaskScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WarmGradientBackground {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
containerColor = androidx.compose.ui.graphics.Color.Transparent,
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
@@ -90,7 +90,7 @@ fun CompleteTaskScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
containerColor = androidx.compose.ui.graphics.Color.Transparent
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -102,17 +102,13 @@ fun CompleteTaskScreen(
|
|||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
// Task Info Section
|
// Task Info Section
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = AppSpacing.lg, vertical = AppSpacing.md),
|
.padding(horizontal = OrganicSpacing.lg, vertical = OrganicSpacing.md)
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(AppSpacing.lg)
|
modifier = Modifier.padding(OrganicSpacing.lg)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = taskTitle,
|
text = taskTitle,
|
||||||
@@ -120,7 +116,7 @@ fun CompleteTaskScreen(
|
|||||||
fontWeight = FontWeight.SemiBold
|
fontWeight = FontWeight.SemiBold
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.sm))
|
Spacer(modifier = Modifier.height(OrganicSpacing.sm))
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -130,7 +126,7 @@ fun CompleteTaskScreen(
|
|||||||
if (residenceName.isNotEmpty()) {
|
if (residenceName.isNotEmpty()) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.xs)
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.xs)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Home,
|
Icons.Default.Home,
|
||||||
@@ -155,31 +151,26 @@ fun CompleteTaskScreen(
|
|||||||
subtitle = stringResource(Res.string.completions_contractor_helper)
|
subtitle = stringResource(Res.string.completions_contractor_helper)
|
||||||
)
|
)
|
||||||
|
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = AppSpacing.lg)
|
.padding(horizontal = OrganicSpacing.lg)
|
||||||
.clickable { showContractorPicker = true },
|
.clickable { showContractorPicker = true }
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(AppSpacing.lg),
|
.padding(OrganicSpacing.lg),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.md)
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
Icon(
|
OrganicIconContainer(
|
||||||
Icons.Default.Build,
|
icon = Icons.Default.Build,
|
||||||
contentDescription = null,
|
size = 24.dp
|
||||||
tint = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
)
|
||||||
Column {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
@@ -204,7 +195,7 @@ fun CompleteTaskScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.lg))
|
Spacer(modifier = Modifier.height(OrganicSpacing.lg))
|
||||||
|
|
||||||
// Completion Details Section
|
// Completion Details Section
|
||||||
SectionHeader(
|
SectionHeader(
|
||||||
@@ -213,8 +204,8 @@ fun CompleteTaskScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(horizontal = AppSpacing.lg),
|
modifier = Modifier.padding(horizontal = OrganicSpacing.lg),
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.md)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
// Completed By Name
|
// Completed By Name
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
@@ -226,7 +217,7 @@ fun CompleteTaskScreen(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
enabled = selectedContractor == null,
|
enabled = selectedContractor == null,
|
||||||
shape = RoundedCornerShape(AppRadius.md)
|
shape = OrganicShapes.medium
|
||||||
)
|
)
|
||||||
|
|
||||||
// Actual Cost
|
// Actual Cost
|
||||||
@@ -239,11 +230,11 @@ fun CompleteTaskScreen(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
|
||||||
shape = RoundedCornerShape(AppRadius.md)
|
shape = OrganicShapes.medium
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.lg))
|
Spacer(modifier = Modifier.height(OrganicSpacing.lg))
|
||||||
|
|
||||||
// Notes Section
|
// Notes Section
|
||||||
SectionHeader(
|
SectionHeader(
|
||||||
@@ -257,12 +248,12 @@ fun CompleteTaskScreen(
|
|||||||
placeholder = { Text(stringResource(Res.string.completions_notes_placeholder)) },
|
placeholder = { Text(stringResource(Res.string.completions_notes_placeholder)) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = AppSpacing.lg)
|
.padding(horizontal = OrganicSpacing.lg)
|
||||||
.height(120.dp),
|
.height(120.dp),
|
||||||
shape = RoundedCornerShape(AppRadius.md)
|
shape = OrganicShapes.medium
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.lg))
|
Spacer(modifier = Modifier.height(OrganicSpacing.lg))
|
||||||
|
|
||||||
// Rating Section
|
// Rating Section
|
||||||
SectionHeader(
|
SectionHeader(
|
||||||
@@ -270,19 +261,15 @@ fun CompleteTaskScreen(
|
|||||||
subtitle = stringResource(Res.string.completions_rate_quality)
|
subtitle = stringResource(Res.string.completions_rate_quality)
|
||||||
)
|
)
|
||||||
|
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = AppSpacing.lg),
|
.padding(horizontal = OrganicSpacing.lg)
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(AppSpacing.lg),
|
.padding(OrganicSpacing.lg),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@@ -291,7 +278,7 @@ fun CompleteTaskScreen(
|
|||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.md))
|
Spacer(modifier = Modifier.height(OrganicSpacing.md))
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
@@ -325,7 +312,7 @@ fun CompleteTaskScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.lg))
|
Spacer(modifier = Modifier.height(OrganicSpacing.lg))
|
||||||
|
|
||||||
// Photos Section
|
// Photos Section
|
||||||
SectionHeader(
|
SectionHeader(
|
||||||
@@ -334,12 +321,12 @@ fun CompleteTaskScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(horizontal = AppSpacing.lg),
|
modifier = Modifier.padding(horizontal = OrganicSpacing.lg),
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.md)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.md)
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -350,7 +337,7 @@ fun CompleteTaskScreen(
|
|||||||
enabled = selectedImages.size < MAX_IMAGES
|
enabled = selectedImages.size < MAX_IMAGES
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.CameraAlt, null, modifier = Modifier.size(20.dp))
|
Icon(Icons.Default.CameraAlt, null, modifier = Modifier.size(20.dp))
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
Spacer(modifier = Modifier.width(OrganicSpacing.sm))
|
||||||
Text(stringResource(Res.string.completions_camera))
|
Text(stringResource(Res.string.completions_camera))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,7 +350,7 @@ fun CompleteTaskScreen(
|
|||||||
enabled = selectedImages.size < MAX_IMAGES
|
enabled = selectedImages.size < MAX_IMAGES
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.PhotoLibrary, null, modifier = Modifier.size(20.dp))
|
Icon(Icons.Default.PhotoLibrary, null, modifier = Modifier.size(20.dp))
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
Spacer(modifier = Modifier.width(OrganicSpacing.sm))
|
||||||
Text(stringResource(Res.string.completions_library))
|
Text(stringResource(Res.string.completions_library))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -373,7 +360,7 @@ fun CompleteTaskScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.horizontalScroll(rememberScrollState()),
|
.horizontalScroll(rememberScrollState()),
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.md)
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
selectedImages.forEachIndexed { index, imageData ->
|
selectedImages.forEachIndexed { index, imageData ->
|
||||||
ImageThumbnailCard(
|
ImageThumbnailCard(
|
||||||
@@ -390,10 +377,11 @@ fun CompleteTaskScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl))
|
||||||
|
|
||||||
// Complete Button
|
// Complete Button
|
||||||
Button(
|
OrganicPrimaryButton(
|
||||||
|
text = stringResource(Res.string.completions_complete_button),
|
||||||
onClick = {
|
onClick = {
|
||||||
isSubmitting = true
|
isSubmitting = true
|
||||||
val notesWithContractor = buildString {
|
val notesWithContractor = buildString {
|
||||||
@@ -424,28 +412,14 @@ fun CompleteTaskScreen(
|
|||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = AppSpacing.lg)
|
.padding(horizontal = OrganicSpacing.lg),
|
||||||
.height(56.dp),
|
|
||||||
enabled = !isSubmitting,
|
enabled = !isSubmitting,
|
||||||
shape = RoundedCornerShape(AppRadius.md)
|
isLoading = isSubmitting,
|
||||||
) {
|
icon = Icons.Default.CheckCircle
|
||||||
if (isSubmitting) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
|
||||||
strokeWidth = 2.dp
|
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
Icon(Icons.Default.CheckCircle, null)
|
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
|
||||||
Text(
|
|
||||||
stringResource(Res.string.completions_complete_button),
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,7 +449,7 @@ private fun SectionHeader(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = AppSpacing.lg, vertical = AppSpacing.sm)
|
.padding(horizontal = OrganicSpacing.lg, vertical = OrganicSpacing.sm)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
@@ -503,7 +477,7 @@ private fun ImageThumbnailCard(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(100.dp)
|
.size(100.dp)
|
||||||
.clip(RoundedCornerShape(AppRadius.md))
|
.clip(OrganicShapes.medium)
|
||||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||||
) {
|
) {
|
||||||
if (imageBitmap != null) {
|
if (imageBitmap != null) {
|
||||||
@@ -530,7 +504,7 @@ private fun ImageThumbnailCard(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.TopEnd)
|
.align(Alignment.TopEnd)
|
||||||
.padding(AppSpacing.xs)
|
.padding(OrganicSpacing.xs)
|
||||||
.size(24.dp)
|
.size(24.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(MaterialTheme.colorScheme.error)
|
.background(MaterialTheme.colorScheme.error)
|
||||||
@@ -565,16 +539,16 @@ private fun ContractorPickerSheet(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(bottom = AppSpacing.xl)
|
.padding(bottom = OrganicSpacing.xl)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.completions_select_contractor),
|
text = stringResource(Res.string.completions_select_contractor),
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
modifier = Modifier.padding(horizontal = AppSpacing.lg, vertical = AppSpacing.md)
|
modifier = Modifier.padding(horizontal = OrganicSpacing.lg, vertical = OrganicSpacing.md)
|
||||||
)
|
)
|
||||||
|
|
||||||
HorizontalDivider()
|
OrganicDivider()
|
||||||
|
|
||||||
// None option
|
// None option
|
||||||
ListItem(
|
ListItem(
|
||||||
@@ -592,13 +566,13 @@ private fun ContractorPickerSheet(
|
|||||||
modifier = Modifier.clickable { onSelect(null) }
|
modifier = Modifier.clickable { onSelect(null) }
|
||||||
)
|
)
|
||||||
|
|
||||||
HorizontalDivider()
|
OrganicDivider()
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(AppSpacing.xl),
|
.padding(OrganicSpacing.xl),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
CircularProgressIndicator()
|
CircularProgressIndicator()
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
package com.example.casera.ui.screens
|
package com.example.casera.ui.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
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.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
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.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
@@ -31,6 +27,7 @@ import com.example.casera.network.ApiResult
|
|||||||
import com.example.casera.platform.rememberShareContractor
|
import com.example.casera.platform.rememberShareContractor
|
||||||
import com.example.casera.utils.SubscriptionHelper
|
import com.example.casera.utils.SubscriptionHelper
|
||||||
import com.example.casera.ui.subscription.UpgradePromptDialog
|
import com.example.casera.ui.subscription.UpgradePromptDialog
|
||||||
|
import com.example.casera.ui.theme.*
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@@ -123,16 +120,16 @@ fun ContractorDetailScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = Color(0xFFF9FAFB)
|
containerColor = MaterialTheme.colorScheme.surface
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { padding ->
|
) { padding ->
|
||||||
|
WarmGradientBackground {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
.background(Color(0xFFF9FAFB))
|
|
||||||
) {
|
) {
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val residences = DataManager.residences.value
|
val residences = DataManager.residences.value
|
||||||
@@ -147,40 +144,28 @@ fun ContractorDetailScreen(
|
|||||||
) { contractor ->
|
) { contractor ->
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentPadding = PaddingValues(16.dp),
|
contentPadding = PaddingValues(OrganicSpacing.medium),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.medium)
|
||||||
) {
|
) {
|
||||||
// Header Card
|
// Header Card
|
||||||
item {
|
item {
|
||||||
Card(
|
OrganicCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
shape = RoundedCornerShape(16.dp),
|
|
||||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)
|
|
||||||
) {
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(24.dp),
|
.padding(OrganicSpacing.large),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
// Avatar
|
// Avatar
|
||||||
Box(
|
OrganicIconContainer(
|
||||||
modifier = Modifier
|
icon = Icons.Default.Person,
|
||||||
.size(80.dp)
|
size = 80.dp,
|
||||||
.clip(CircleShape)
|
iconSize = 48.dp,
|
||||||
.background(MaterialTheme.colorScheme.primaryContainer),
|
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
contentAlignment = Alignment.Center
|
iconTint = MaterialTheme.colorScheme.primary
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.Person,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(48.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.medium))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = contractor.name,
|
text = contractor.name,
|
||||||
@@ -198,18 +183,18 @@ fun ContractorDetailScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (contractor.specialties.isNotEmpty()) {
|
if (contractor.specialties.isNotEmpty()) {
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.medium))
|
||||||
FlowRow(
|
FlowRow(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.small, Alignment.CenterHorizontally),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.small)
|
||||||
) {
|
) {
|
||||||
contractor.specialties.forEach { specialty ->
|
contractor.specialties.forEach { specialty ->
|
||||||
Surface(
|
Surface(
|
||||||
shape = RoundedCornerShape(20.dp),
|
shape = OrganicShapes.large,
|
||||||
color = MaterialTheme.colorScheme.primaryContainer
|
color = MaterialTheme.colorScheme.primaryContainer
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
|
modifier = Modifier.padding(horizontal = OrganicSpacing.medium, vertical = OrganicSpacing.extraSmall),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -218,7 +203,7 @@ fun ContractorDetailScreen(
|
|||||||
modifier = Modifier.size(16.dp),
|
modifier = Modifier.size(16.dp),
|
||||||
tint = MaterialTheme.colorScheme.primary
|
tint = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(OrganicSpacing.extraSmall))
|
||||||
Text(
|
Text(
|
||||||
text = specialty.name,
|
text = specialty.name,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
@@ -232,7 +217,7 @@ fun ContractorDetailScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (contractor.rating != null && contractor.rating > 0) {
|
if (contractor.rating != null && contractor.rating > 0) {
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.medium))
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
repeat(5) { index ->
|
repeat(5) { index ->
|
||||||
Icon(
|
Icon(
|
||||||
@@ -242,7 +227,7 @@ fun ContractorDetailScreen(
|
|||||||
tint = Color(0xFFF59E0B)
|
tint = Color(0xFFF59E0B)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(OrganicSpacing.small))
|
||||||
Text(
|
Text(
|
||||||
text = ((contractor.rating * 10).toInt() / 10.0).toString(),
|
text = ((contractor.rating * 10).toInt() / 10.0).toString(),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
@@ -253,7 +238,7 @@ fun ContractorDetailScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (contractor.taskCount > 0) {
|
if (contractor.taskCount > 0) {
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.extraSmall))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.contractors_completed_tasks, contractor.taskCount),
|
text = stringResource(Res.string.contractors_completed_tasks, contractor.taskCount),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
@@ -269,7 +254,7 @@ fun ContractorDetailScreen(
|
|||||||
item {
|
item {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.medium)
|
||||||
) {
|
) {
|
||||||
contractor.phone?.let { phone ->
|
contractor.phone?.let { phone ->
|
||||||
QuickActionButton(
|
QuickActionButton(
|
||||||
@@ -388,7 +373,7 @@ fun ContractorDetailScreen(
|
|||||||
text = stringResource(Res.string.contractors_no_contact_info),
|
text = stringResource(Res.string.contractors_no_contact_info),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.padding(16.dp)
|
modifier = Modifier.padding(OrganicSpacing.medium)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -459,8 +444,8 @@ fun ContractorDetailScreen(
|
|||||||
item {
|
item {
|
||||||
DetailSection(title = stringResource(Res.string.contractors_notes)) {
|
DetailSection(title = stringResource(Res.string.contractors_notes)) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
|
modifier = Modifier.padding(horizontal = OrganicSpacing.medium, vertical = OrganicSpacing.medium),
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.medium)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Notes,
|
Icons.Default.Notes,
|
||||||
@@ -484,7 +469,7 @@ fun ContractorDetailScreen(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp),
|
.padding(OrganicSpacing.medium),
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly
|
horizontalArrangement = Arrangement.SpaceEvenly
|
||||||
) {
|
) {
|
||||||
StatCard(
|
StatCard(
|
||||||
@@ -516,7 +501,7 @@ fun ContractorDetailScreen(
|
|||||||
value = createdBy.username,
|
value = createdBy.username,
|
||||||
iconTint = MaterialTheme.colorScheme.onSurfaceVariant
|
iconTint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))
|
OrganicDivider(modifier = Modifier.padding(horizontal = OrganicSpacing.medium))
|
||||||
}
|
}
|
||||||
|
|
||||||
DetailRow(
|
DetailRow(
|
||||||
@@ -531,6 +516,7 @@ fun ContractorDetailScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (showEditDialog) {
|
if (showEditDialog) {
|
||||||
AddContractorDialog(
|
AddContractorDialog(
|
||||||
@@ -565,8 +551,8 @@ fun ContractorDetailScreen(
|
|||||||
Text(stringResource(Res.string.common_cancel))
|
Text(stringResource(Res.string.common_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
containerColor = Color.White,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
shape = RoundedCornerShape(16.dp)
|
shape = OrganicShapes.large
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,19 +577,14 @@ fun DetailSection(
|
|||||||
title: String,
|
title: String,
|
||||||
content: @Composable ColumnScope.() -> Unit
|
content: @Composable ColumnScope.() -> Unit
|
||||||
) {
|
) {
|
||||||
Card(
|
OrganicCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
shape = RoundedCornerShape(16.dp),
|
|
||||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)
|
|
||||||
) {
|
|
||||||
Column(modifier = Modifier.fillMaxWidth()) {
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
modifier = Modifier.padding(16.dp).padding(bottom = 0.dp)
|
modifier = Modifier.padding(OrganicSpacing.medium).padding(bottom = 0.dp)
|
||||||
)
|
)
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
@@ -620,7 +601,7 @@ fun DetailRow(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
.padding(horizontal = OrganicSpacing.medium, vertical = OrganicSpacing.medium),
|
||||||
verticalAlignment = Alignment.Top
|
verticalAlignment = Alignment.Top
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -629,7 +610,7 @@ fun DetailRow(
|
|||||||
modifier = Modifier.size(20.dp),
|
modifier = Modifier.size(20.dp),
|
||||||
tint = iconTint
|
tint = iconTint
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
Spacer(modifier = Modifier.width(OrganicSpacing.medium))
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
@@ -658,7 +639,7 @@ fun ClickableDetailRow(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
.padding(horizontal = OrganicSpacing.medium, vertical = OrganicSpacing.medium),
|
||||||
verticalAlignment = Alignment.Top
|
verticalAlignment = Alignment.Top
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -667,7 +648,7 @@ fun ClickableDetailRow(
|
|||||||
modifier = Modifier.size(20.dp),
|
modifier = Modifier.size(20.dp),
|
||||||
tint = iconTint
|
tint = iconTint
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
Spacer(modifier = Modifier.width(OrganicSpacing.medium))
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
@@ -698,33 +679,23 @@ fun QuickActionButton(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = modifier.clickable(onClick = onClick),
|
modifier = modifier.clickable(onClick = onClick)
|
||||||
shape = RoundedCornerShape(12.dp),
|
|
||||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 12.dp),
|
.padding(vertical = OrganicSpacing.medium),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Box(
|
OrganicIconContainer(
|
||||||
modifier = Modifier
|
icon = icon,
|
||||||
.size(44.dp)
|
size = 44.dp,
|
||||||
.clip(CircleShape)
|
iconSize = 22.dp,
|
||||||
.background(color.copy(alpha = 0.1f)),
|
containerColor = color.copy(alpha = 0.1f),
|
||||||
contentAlignment = Alignment.Center
|
iconTint = color
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
icon,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(22.dp),
|
|
||||||
tint = color
|
|
||||||
)
|
)
|
||||||
}
|
Spacer(modifier = Modifier.height(OrganicSpacing.small))
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
@@ -745,21 +716,14 @@ fun StatCard(
|
|||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Box(
|
OrganicIconContainer(
|
||||||
modifier = Modifier
|
icon = icon,
|
||||||
.size(44.dp)
|
size = 44.dp,
|
||||||
.clip(CircleShape)
|
iconSize = 22.dp,
|
||||||
.background(color.copy(alpha = 0.1f)),
|
containerColor = color.copy(alpha = 0.1f),
|
||||||
contentAlignment = Alignment.Center
|
iconTint = color
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
icon,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(22.dp),
|
|
||||||
tint = color
|
|
||||||
)
|
)
|
||||||
}
|
Spacer(modifier = Modifier.height(OrganicSpacing.small))
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
Text(
|
Text(
|
||||||
text = value,
|
text = value,
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
package com.example.casera.ui.screens
|
package com.example.casera.ui.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
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.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
@@ -14,8 +11,6 @@ import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
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.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.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -31,6 +26,7 @@ import com.example.casera.ui.subscription.UpgradeFeatureScreen
|
|||||||
import com.example.casera.utils.SubscriptionHelper
|
import com.example.casera.utils.SubscriptionHelper
|
||||||
import com.example.casera.analytics.PostHogAnalytics
|
import com.example.casera.analytics.PostHogAnalytics
|
||||||
import com.example.casera.analytics.AnalyticsEvents
|
import com.example.casera.analytics.AnalyticsEvents
|
||||||
|
import com.example.casera.ui.theme.*
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@@ -215,11 +211,11 @@ fun ContractorsScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
WarmGradientBackground {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
) {
|
) {
|
||||||
// Search bar
|
// Search bar
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
@@ -227,7 +223,7 @@ fun ContractorsScreen(
|
|||||||
onValueChange = { searchQuery = it },
|
onValueChange = { searchQuery = it },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
.padding(horizontal = OrganicSpacing.medium, vertical = OrganicSpacing.small),
|
||||||
placeholder = { Text(stringResource(Res.string.contractors_search)) },
|
placeholder = { Text(stringResource(Res.string.contractors_search)) },
|
||||||
leadingIcon = { Icon(Icons.Default.Search, stringResource(Res.string.common_search)) },
|
leadingIcon = { Icon(Icons.Default.Search, stringResource(Res.string.common_search)) },
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
@@ -238,7 +234,7 @@ fun ContractorsScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
shape = RoundedCornerShape(12.dp),
|
shape = OrganicShapes.medium,
|
||||||
colors = OutlinedTextFieldDefaults.colors(
|
colors = OutlinedTextFieldDefaults.colors(
|
||||||
focusedContainerColor = MaterialTheme.colorScheme.surface,
|
focusedContainerColor = MaterialTheme.colorScheme.surface,
|
||||||
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
|
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
|
||||||
@@ -252,8 +248,8 @@ fun ContractorsScreen(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp, vertical = 4.dp),
|
.padding(horizontal = OrganicSpacing.medium, vertical = OrganicSpacing.extraSmall),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.small)
|
||||||
) {
|
) {
|
||||||
if (showFavoritesOnly) {
|
if (showFavoritesOnly) {
|
||||||
FilterChip(
|
FilterChip(
|
||||||
@@ -288,33 +284,37 @@ fun ContractorsScreen(
|
|||||||
) { _ ->
|
) { _ ->
|
||||||
// Use filteredContractors for client-side filtering
|
// Use filteredContractors for client-side filtering
|
||||||
if (filteredContractors.isEmpty()) {
|
if (filteredContractors.isEmpty()) {
|
||||||
// Empty state
|
// Empty state with organic styling
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.small)
|
||||||
) {
|
) {
|
||||||
Icon(
|
OrganicIconContainer(
|
||||||
Icons.Default.PersonAdd,
|
icon = Icons.Default.PersonAdd,
|
||||||
contentDescription = null,
|
size = 80.dp,
|
||||||
modifier = Modifier.size(64.dp),
|
iconSize = 40.dp,
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
iconTint = MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.height(OrganicSpacing.small))
|
||||||
Text(
|
Text(
|
||||||
if (searchQuery.isNotEmpty() || selectedFilter != null || showFavoritesOnly)
|
if (searchQuery.isNotEmpty() || selectedFilter != null || showFavoritesOnly)
|
||||||
stringResource(Res.string.contractors_no_results)
|
stringResource(Res.string.contractors_no_results)
|
||||||
else
|
else
|
||||||
stringResource(Res.string.contractors_empty_title),
|
stringResource(Res.string.contractors_empty_title),
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
if (searchQuery.isEmpty() && selectedFilter == null && !showFavoritesOnly) {
|
if (searchQuery.isEmpty() && selectedFilter == null && !showFavoritesOnly) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(Res.string.contractors_empty_subtitle_first),
|
stringResource(Res.string.contractors_empty_subtitle_first),
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
style = MaterialTheme.typography.bodySmall
|
style = MaterialTheme.typography.bodyMedium
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -330,8 +330,8 @@ fun ContractorsScreen(
|
|||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentPadding = PaddingValues(16.dp),
|
contentPadding = PaddingValues(OrganicSpacing.medium),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.medium)
|
||||||
) {
|
) {
|
||||||
items(filteredContractors, key = { it.id }) { contractor ->
|
items(filteredContractors, key = { it.id }) { contractor ->
|
||||||
ContractorCard(
|
ContractorCard(
|
||||||
@@ -347,6 +347,7 @@ fun ContractorsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (showAddDialog) {
|
if (showAddDialog) {
|
||||||
AddContractorDialog(
|
AddContractorDialog(
|
||||||
@@ -381,41 +382,27 @@ fun ContractorCard(
|
|||||||
onToggleFavorite: (Int) -> Unit,
|
onToggleFavorite: (Int) -> Unit,
|
||||||
onClick: (Int) -> Unit
|
onClick: (Int) -> Unit
|
||||||
) {
|
) {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable { onClick(contractor.id) },
|
.clickable { onClick(contractor.id) }
|
||||||
shape = RoundedCornerShape(16.dp),
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
|
||||||
),
|
|
||||||
elevation = CardDefaults.cardElevation(
|
|
||||||
defaultElevation = 1.dp
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp),
|
.padding(OrganicSpacing.medium),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
// Avatar/Icon
|
// Avatar/Icon
|
||||||
Box(
|
OrganicIconContainer(
|
||||||
modifier = Modifier
|
icon = Icons.Default.Person,
|
||||||
.size(56.dp)
|
size = 56.dp,
|
||||||
.clip(CircleShape)
|
iconSize = 32.dp,
|
||||||
.background(MaterialTheme.colorScheme.primaryContainer),
|
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
contentAlignment = Alignment.Center
|
iconTint = MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.Person,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(32.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.onPrimaryContainer
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(OrganicSpacing.medium))
|
||||||
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
@@ -428,7 +415,7 @@ fun ContractorCard(
|
|||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
if (contractor.isFavorite) {
|
if (contractor.isFavorite) {
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(OrganicSpacing.extraSmall))
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Star,
|
Icons.Default.Star,
|
||||||
contentDescription = stringResource(Res.string.contractors_favorite),
|
contentDescription = stringResource(Res.string.contractors_favorite),
|
||||||
@@ -448,10 +435,10 @@ fun ContractorCard(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.small))
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.medium),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
if (contractor.specialties.isNotEmpty()) {
|
if (contractor.specialties.isNotEmpty()) {
|
||||||
@@ -462,7 +449,7 @@ fun ContractorCard(
|
|||||||
modifier = Modifier.size(14.dp),
|
modifier = Modifier.size(14.dp),
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(OrganicSpacing.extraSmall))
|
||||||
Text(
|
Text(
|
||||||
text = contractor.specialties.first().name,
|
text = contractor.specialties.first().name,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
@@ -479,7 +466,7 @@ fun ContractorCard(
|
|||||||
modifier = Modifier.size(14.dp),
|
modifier = Modifier.size(14.dp),
|
||||||
tint = MaterialTheme.colorScheme.tertiary
|
tint = MaterialTheme.colorScheme.tertiary
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(OrganicSpacing.extraSmall))
|
||||||
Text(
|
Text(
|
||||||
text = "${(contractor.rating * 10).toInt() / 10.0}",
|
text = "${(contractor.rating * 10).toInt() / 10.0}",
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
@@ -497,7 +484,7 @@ fun ContractorCard(
|
|||||||
modifier = Modifier.size(14.dp),
|
modifier = Modifier.size(14.dp),
|
||||||
tint = MaterialTheme.colorScheme.secondary
|
tint = MaterialTheme.colorScheme.secondary
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(OrganicSpacing.extraSmall))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.contractors_tasks_count, contractor.taskCount),
|
text = stringResource(Res.string.contractors_tasks_count, contractor.taskCount),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
@@ -528,4 +515,3 @@ fun ContractorCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import coil3.compose.SubcomposeAsyncImageContent
|
|||||||
import coil3.compose.AsyncImagePainter
|
import coil3.compose.AsyncImagePainter
|
||||||
import com.example.casera.ui.components.AuthenticatedImage
|
import com.example.casera.ui.components.AuthenticatedImage
|
||||||
import com.example.casera.util.DateUtils
|
import com.example.casera.util.DateUtils
|
||||||
|
import com.example.casera.ui.theme.*
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@@ -96,6 +97,7 @@ fun DocumentDetailScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { padding ->
|
) { padding ->
|
||||||
|
WarmGradientBackground {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -110,8 +112,8 @@ fun DocumentDetailScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 96.dp),
|
.padding(start = OrganicSpacing.lg, end = OrganicSpacing.lg, top = OrganicSpacing.lg, bottom = 96.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.lg)
|
||||||
) {
|
) {
|
||||||
// Status badge (for warranties)
|
// Status badge (for warranties)
|
||||||
if (document.documentType == "warranty") {
|
if (document.documentType == "warranty") {
|
||||||
@@ -124,16 +126,14 @@ fun DocumentDetailScreen(
|
|||||||
else -> Color(0xFF10B981)
|
else -> Color(0xFF10B981)
|
||||||
}
|
}
|
||||||
|
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = CardDefaults.cardColors(
|
accentColor = statusColor.copy(alpha = 0.1f)
|
||||||
containerColor = statusColor.copy(alpha = 0.1f)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp),
|
.padding(OrganicSpacing.lg),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@@ -175,17 +175,17 @@ fun DocumentDetailScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Basic Information
|
// Basic Information
|
||||||
Card(modifier = Modifier.fillMaxWidth()) {
|
OrganicCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(OrganicSpacing.lg),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(Res.string.documents_basic_info),
|
stringResource(Res.string.documents_basic_info),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
Divider()
|
OrganicDivider()
|
||||||
|
|
||||||
DetailRow(stringResource(Res.string.documents_title_label), document.title)
|
DetailRow(stringResource(Res.string.documents_title_label), document.title)
|
||||||
DetailRow(stringResource(Res.string.documents_type_label), DocumentType.fromValue(document.documentType).displayName)
|
DetailRow(stringResource(Res.string.documents_type_label), DocumentType.fromValue(document.documentType).displayName)
|
||||||
@@ -202,17 +202,17 @@ fun DocumentDetailScreen(
|
|||||||
if (document.documentType == "warranty" &&
|
if (document.documentType == "warranty" &&
|
||||||
(document.itemName != null || document.modelNumber != null ||
|
(document.itemName != null || document.modelNumber != null ||
|
||||||
document.serialNumber != null || document.provider != null)) {
|
document.serialNumber != null || document.provider != null)) {
|
||||||
Card(modifier = Modifier.fillMaxWidth()) {
|
OrganicCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(OrganicSpacing.lg),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(Res.string.documents_item_details),
|
stringResource(Res.string.documents_item_details),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
Divider()
|
OrganicDivider()
|
||||||
|
|
||||||
document.itemName?.let { DetailRow(stringResource(Res.string.documents_item_name), it) }
|
document.itemName?.let { DetailRow(stringResource(Res.string.documents_item_name), it) }
|
||||||
document.modelNumber?.let { DetailRow(stringResource(Res.string.documents_model_number), it) }
|
document.modelNumber?.let { DetailRow(stringResource(Res.string.documents_model_number), it) }
|
||||||
@@ -227,17 +227,17 @@ fun DocumentDetailScreen(
|
|||||||
if (document.documentType == "warranty" &&
|
if (document.documentType == "warranty" &&
|
||||||
(document.claimPhone != null || document.claimEmail != null ||
|
(document.claimPhone != null || document.claimEmail != null ||
|
||||||
document.claimWebsite != null)) {
|
document.claimWebsite != null)) {
|
||||||
Card(modifier = Modifier.fillMaxWidth()) {
|
OrganicCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(OrganicSpacing.lg),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(Res.string.documents_claim_info),
|
stringResource(Res.string.documents_claim_info),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
Divider()
|
OrganicDivider()
|
||||||
|
|
||||||
document.claimPhone?.let { DetailRow(stringResource(Res.string.documents_claim_phone), it) }
|
document.claimPhone?.let { DetailRow(stringResource(Res.string.documents_claim_phone), it) }
|
||||||
document.claimEmail?.let { DetailRow(stringResource(Res.string.documents_claim_email), it) }
|
document.claimEmail?.let { DetailRow(stringResource(Res.string.documents_claim_email), it) }
|
||||||
@@ -249,17 +249,17 @@ fun DocumentDetailScreen(
|
|||||||
// Dates
|
// Dates
|
||||||
if (document.purchaseDate != null || document.startDate != null ||
|
if (document.purchaseDate != null || document.startDate != null ||
|
||||||
document.endDate != null) {
|
document.endDate != null) {
|
||||||
Card(modifier = Modifier.fillMaxWidth()) {
|
OrganicCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(OrganicSpacing.lg),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(Res.string.documents_important_dates),
|
stringResource(Res.string.documents_important_dates),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
Divider()
|
OrganicDivider()
|
||||||
|
|
||||||
document.purchaseDate?.let { DetailRow(stringResource(Res.string.documents_purchase_date), DateUtils.formatDateMedium(it)) }
|
document.purchaseDate?.let { DetailRow(stringResource(Res.string.documents_purchase_date), DateUtils.formatDateMedium(it)) }
|
||||||
document.startDate?.let { DetailRow(stringResource(Res.string.documents_start_date), DateUtils.formatDateMedium(it)) }
|
document.startDate?.let { DetailRow(stringResource(Res.string.documents_start_date), DateUtils.formatDateMedium(it)) }
|
||||||
@@ -269,17 +269,17 @@ fun DocumentDetailScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Residence & Contractor
|
// Residence & Contractor
|
||||||
Card(modifier = Modifier.fillMaxWidth()) {
|
OrganicCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(OrganicSpacing.lg),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(Res.string.documents_associations),
|
stringResource(Res.string.documents_associations),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
Divider()
|
OrganicDivider()
|
||||||
|
|
||||||
document.residenceAddress?.let { DetailRow(stringResource(Res.string.documents_residence), it) }
|
document.residenceAddress?.let { DetailRow(stringResource(Res.string.documents_residence), it) }
|
||||||
document.contractorName?.let { DetailRow(stringResource(Res.string.documents_contractor), it) }
|
document.contractorName?.let { DetailRow(stringResource(Res.string.documents_contractor), it) }
|
||||||
@@ -289,17 +289,17 @@ fun DocumentDetailScreen(
|
|||||||
|
|
||||||
// Additional Information
|
// Additional Information
|
||||||
if (document.tags != null || document.notes != null) {
|
if (document.tags != null || document.notes != null) {
|
||||||
Card(modifier = Modifier.fillMaxWidth()) {
|
OrganicCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(OrganicSpacing.lg),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(Res.string.documents_additional_info),
|
stringResource(Res.string.documents_additional_info),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
Divider()
|
OrganicDivider()
|
||||||
|
|
||||||
document.tags?.let { DetailRow(stringResource(Res.string.documents_tags), it) }
|
document.tags?.let { DetailRow(stringResource(Res.string.documents_tags), it) }
|
||||||
document.notes?.let { DetailRow(stringResource(Res.string.documents_notes), it) }
|
document.notes?.let { DetailRow(stringResource(Res.string.documents_notes), it) }
|
||||||
@@ -309,22 +309,22 @@ fun DocumentDetailScreen(
|
|||||||
|
|
||||||
// Images
|
// Images
|
||||||
if (document.images.isNotEmpty()) {
|
if (document.images.isNotEmpty()) {
|
||||||
Card(modifier = Modifier.fillMaxWidth()) {
|
OrganicCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(OrganicSpacing.lg),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(Res.string.documents_images, document.images.size),
|
stringResource(Res.string.documents_images, document.images.size),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
Divider()
|
OrganicDivider()
|
||||||
|
|
||||||
// Image grid
|
// Image grid
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.sm)
|
||||||
) {
|
) {
|
||||||
document.images.take(4).forEachIndexed { index, image ->
|
document.images.take(4).forEachIndexed { index, image ->
|
||||||
Box(
|
Box(
|
||||||
@@ -366,17 +366,17 @@ fun DocumentDetailScreen(
|
|||||||
|
|
||||||
// File Information
|
// File Information
|
||||||
if (document.fileUrl != null) {
|
if (document.fileUrl != null) {
|
||||||
Card(modifier = Modifier.fillMaxWidth()) {
|
OrganicCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(OrganicSpacing.lg),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(Res.string.documents_attached_file),
|
stringResource(Res.string.documents_attached_file),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
Divider()
|
OrganicDivider()
|
||||||
|
|
||||||
document.fileType?.let { DetailRow(stringResource(Res.string.documents_file_type), it) }
|
document.fileType?.let { DetailRow(stringResource(Res.string.documents_file_type), it) }
|
||||||
document.fileSize?.let {
|
document.fileSize?.let {
|
||||||
@@ -388,7 +388,7 @@ fun DocumentDetailScreen(
|
|||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Download, null)
|
Icon(Icons.Default.Download, null)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(OrganicSpacing.sm))
|
||||||
Text(stringResource(Res.string.documents_download_file))
|
Text(stringResource(Res.string.documents_download_file))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -396,17 +396,17 @@ fun DocumentDetailScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Metadata
|
// Metadata
|
||||||
Card(modifier = Modifier.fillMaxWidth()) {
|
OrganicCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(OrganicSpacing.lg),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(Res.string.documents_metadata),
|
stringResource(Res.string.documents_metadata),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
Divider()
|
OrganicDivider()
|
||||||
|
|
||||||
document.uploadedByUsername?.let { DetailRow(stringResource(Res.string.documents_uploaded_by), it) }
|
document.uploadedByUsername?.let { DetailRow(stringResource(Res.string.documents_uploaded_by), it) }
|
||||||
document.createdAt?.let { DetailRow(stringResource(Res.string.documents_created), DateUtils.formatDateMedium(it)) }
|
document.createdAt?.let { DetailRow(stringResource(Res.string.documents_created), DateUtils.formatDateMedium(it)) }
|
||||||
@@ -417,6 +417,7 @@ fun DocumentDetailScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Delete confirmation dialog
|
// Delete confirmation dialog
|
||||||
if (showDeleteDialog) {
|
if (showDeleteDialog) {
|
||||||
@@ -463,7 +464,7 @@ fun DetailRow(label: String, value: String) {
|
|||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = Color.Gray
|
color = Color.Gray
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xs))
|
||||||
Text(
|
Text(
|
||||||
value,
|
value,
|
||||||
style = MaterialTheme.typography.bodyLarge
|
style = MaterialTheme.typography.bodyLarge
|
||||||
@@ -498,7 +499,7 @@ fun DocumentImageViewer(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(0.95f)
|
.fillMaxWidth(0.95f)
|
||||||
.fillMaxHeight(0.9f),
|
.fillMaxHeight(0.9f),
|
||||||
shape = RoundedCornerShape(16.dp),
|
shape = RoundedCornerShape(OrganicSpacing.lg),
|
||||||
color = MaterialTheme.colorScheme.background
|
color = MaterialTheme.colorScheme.background
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
@@ -508,7 +509,7 @@ fun DocumentImageViewer(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp),
|
.padding(OrganicSpacing.lg),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@@ -531,7 +532,7 @@ fun DocumentImageViewer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalDivider()
|
OrganicDivider()
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
if (showFullImage) {
|
if (showFullImage) {
|
||||||
@@ -539,7 +540,7 @@ fun DocumentImageViewer(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(16.dp),
|
.padding(OrganicSpacing.lg),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
@@ -553,16 +554,13 @@ fun DocumentImageViewer(
|
|||||||
)
|
)
|
||||||
|
|
||||||
images[selectedIndex].caption?.let { caption ->
|
images[selectedIndex].caption?.let { caption ->
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.lg))
|
||||||
Card(
|
OrganicCard(
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
),
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = caption,
|
text = caption,
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(OrganicSpacing.lg),
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyMedium
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -570,7 +568,7 @@ fun DocumentImageViewer(
|
|||||||
|
|
||||||
// Navigation buttons
|
// Navigation buttons
|
||||||
if (images.size > 1) {
|
if (images.size > 1) {
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.lg))
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
@@ -580,7 +578,7 @@ fun DocumentImageViewer(
|
|||||||
enabled = selectedIndex > 0
|
enabled = selectedIndex > 0
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.ArrowBack, "Previous")
|
Icon(Icons.Default.ArrowBack, "Previous")
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(OrganicSpacing.sm))
|
||||||
Text("Previous")
|
Text("Previous")
|
||||||
}
|
}
|
||||||
Button(
|
Button(
|
||||||
@@ -588,7 +586,7 @@ fun DocumentImageViewer(
|
|||||||
enabled = selectedIndex < images.size - 1
|
enabled = selectedIndex < images.size - 1
|
||||||
) {
|
) {
|
||||||
Text("Next")
|
Text("Next")
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(OrganicSpacing.sm))
|
||||||
Icon(Icons.Default.ArrowForward, "Next")
|
Icon(Icons.Default.ArrowForward, "Next")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -600,19 +598,19 @@ fun DocumentImageViewer(
|
|||||||
columns = GridCells.Fixed(2),
|
columns = GridCells.Fixed(2),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(16.dp),
|
.padding(OrganicSpacing.lg),
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.md),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
items(images.size) { index ->
|
items(images.size) { index ->
|
||||||
val image = images[index]
|
val image = images[index]
|
||||||
Card(
|
OrganicCard(
|
||||||
onClick = {
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
selectedIndex = index
|
selectedIndex = index
|
||||||
showFullImage = true
|
showFullImage = true
|
||||||
},
|
}
|
||||||
shape = RoundedCornerShape(12.dp),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
AuthenticatedImage(
|
AuthenticatedImage(
|
||||||
@@ -627,7 +625,7 @@ fun DocumentImageViewer(
|
|||||||
image.caption?.let { caption ->
|
image.caption?.let { caption ->
|
||||||
Text(
|
Text(
|
||||||
text = caption,
|
text = caption,
|
||||||
modifier = Modifier.padding(8.dp),
|
modifier = Modifier.padding(OrganicSpacing.sm),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
maxLines = 2
|
maxLines = 2
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import com.example.casera.platform.rememberImagePicker
|
|||||||
import com.example.casera.platform.rememberCameraPicker
|
import com.example.casera.platform.rememberCameraPicker
|
||||||
import com.example.casera.analytics.PostHogAnalytics
|
import com.example.casera.analytics.PostHogAnalytics
|
||||||
import com.example.casera.analytics.AnalyticsEvents
|
import com.example.casera.analytics.AnalyticsEvents
|
||||||
|
import com.example.casera.ui.theme.*
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@@ -201,13 +202,14 @@ fun DocumentFormScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { padding ->
|
) { padding ->
|
||||||
|
WarmGradientBackground {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 96.dp),
|
.padding(start = OrganicSpacing.cozy, end = OrganicSpacing.cozy, top = OrganicSpacing.cozy, bottom = 96.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.cozy)
|
||||||
) {
|
) {
|
||||||
// Loading state for edit mode
|
// Loading state for edit mode
|
||||||
if (isEditMode && documentDetailState is ApiResult.Loading) {
|
if (isEditMode && documentDetailState is ApiResult.Loading) {
|
||||||
@@ -497,14 +499,12 @@ fun DocumentFormScreen(
|
|||||||
|
|
||||||
// Existing images (edit mode only)
|
// Existing images (edit mode only)
|
||||||
if (isEditMode && existingImages.isNotEmpty()) {
|
if (isEditMode && existingImages.isNotEmpty()) {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = CardDefaults.cardColors(
|
showBlob = false
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(OrganicSpacing.cozy),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@@ -529,14 +529,12 @@ fun DocumentFormScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Image upload section
|
// Image upload section
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = CardDefaults.cardColors(
|
showBlob = false
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(OrganicSpacing.cozy),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@@ -549,7 +547,7 @@ fun DocumentFormScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
) {
|
) {
|
||||||
Button(
|
Button(
|
||||||
onClick = { cameraPicker() },
|
onClick = { cameraPicker() },
|
||||||
@@ -575,7 +573,7 @@ fun DocumentFormScreen(
|
|||||||
// Display selected images
|
// Display selected images
|
||||||
if (selectedImages.isNotEmpty()) {
|
if (selectedImages.isNotEmpty()) {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
) {
|
) {
|
||||||
selectedImages.forEachIndexed { index, image ->
|
selectedImages.forEachIndexed { index, image ->
|
||||||
Row(
|
Row(
|
||||||
@@ -584,7 +582,7 @@ fun DocumentFormScreen(
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.compact),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -617,16 +615,14 @@ fun DocumentFormScreen(
|
|||||||
|
|
||||||
// Error message
|
// Error message
|
||||||
if (operationState is ApiResult.Error) {
|
if (operationState is ApiResult.Error) {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = CardDefaults.cardColors(
|
showBlob = false
|
||||||
containerColor = MaterialTheme.colorScheme.errorContainer
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
com.example.casera.util.ErrorMessageParser.parse((operationState as ApiResult.Error).message),
|
com.example.casera.util.ErrorMessageParser.parse((operationState as ApiResult.Error).message),
|
||||||
modifier = Modifier.padding(12.dp),
|
modifier = Modifier.padding(12.dp),
|
||||||
color = MaterialTheme.colorScheme.onErrorContainer
|
color = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -638,7 +634,13 @@ fun DocumentFormScreen(
|
|||||||
val providerRequiredError = stringResource(Res.string.documents_form_provider_error)
|
val providerRequiredError = stringResource(Res.string.documents_form_provider_error)
|
||||||
|
|
||||||
// Save Button
|
// Save Button
|
||||||
Button(
|
OrganicPrimaryButton(
|
||||||
|
text = when {
|
||||||
|
isEditMode && isWarranty -> stringResource(Res.string.documents_form_update_warranty)
|
||||||
|
isEditMode -> stringResource(Res.string.documents_form_update_document)
|
||||||
|
isWarranty -> stringResource(Res.string.documents_form_add_warranty)
|
||||||
|
else -> stringResource(Res.string.documents_form_add_document)
|
||||||
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
// Validate
|
// Validate
|
||||||
var hasError = false
|
var hasError = false
|
||||||
@@ -723,25 +725,10 @@ fun DocumentFormScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
enabled = operationState !is ApiResult.Loading,
|
||||||
enabled = operationState !is ApiResult.Loading
|
isLoading = operationState is ApiResult.Loading
|
||||||
) {
|
|
||||||
if (operationState is ApiResult.Loading) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
when {
|
|
||||||
isEditMode && isWarranty -> stringResource(Res.string.documents_form_update_warranty)
|
|
||||||
isEditMode -> stringResource(Res.string.documents_form_update_document)
|
|
||||||
isWarranty -> stringResource(Res.string.documents_form_add_warranty)
|
|
||||||
else -> stringResource(Res.string.documents_form_add_document)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import com.example.casera.viewmodel.DocumentViewModel
|
|||||||
import com.example.casera.models.*
|
import com.example.casera.models.*
|
||||||
import com.example.casera.analytics.PostHogAnalytics
|
import com.example.casera.analytics.PostHogAnalytics
|
||||||
import com.example.casera.analytics.AnalyticsEvents
|
import com.example.casera.analytics.AnalyticsEvents
|
||||||
|
import com.example.casera.ui.theme.*
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@@ -120,7 +121,7 @@ fun DocumentsScreen(
|
|||||||
showFiltersMenu = false
|
showFiltersMenu = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Divider()
|
OrganicDivider()
|
||||||
DocumentCategory.values().forEach { category ->
|
DocumentCategory.values().forEach { category ->
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(category.displayName) },
|
text = { Text(category.displayName) },
|
||||||
@@ -138,7 +139,7 @@ fun DocumentsScreen(
|
|||||||
showFiltersMenu = false
|
showFiltersMenu = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Divider()
|
OrganicDivider()
|
||||||
DocumentType.values().forEach { type ->
|
DocumentType.values().forEach { type ->
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(type.displayName) },
|
text = { Text(type.displayName) },
|
||||||
@@ -199,6 +200,7 @@ fun DocumentsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { padding ->
|
) { padding ->
|
||||||
|
WarmGradientBackground {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -227,6 +229,7 @@ fun DocumentsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Show upgrade dialog when user hits limit
|
// Show upgrade dialog when user hits limit
|
||||||
if (showUpgradeDialog) {
|
if (showUpgradeDialog) {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import com.example.casera.viewmodel.ResidenceViewModel
|
|||||||
import com.example.casera.repository.LookupsRepository
|
import com.example.casera.repository.LookupsRepository
|
||||||
import com.example.casera.models.*
|
import com.example.casera.models.*
|
||||||
import com.example.casera.network.ApiResult
|
import com.example.casera.network.ApiResult
|
||||||
|
import com.example.casera.ui.theme.*
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@@ -103,13 +104,14 @@ fun EditTaskScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
|
WarmGradientBackground {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
.padding(16.dp)
|
.padding(OrganicSpacing.cozy)
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.cozy)
|
||||||
) {
|
) {
|
||||||
// Required fields section
|
// Required fields section
|
||||||
Text(
|
Text(
|
||||||
@@ -298,7 +300,8 @@ fun EditTaskScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Submit button
|
// Submit button
|
||||||
Button(
|
OrganicPrimaryButton(
|
||||||
|
text = stringResource(Res.string.tasks_update),
|
||||||
onClick = {
|
onClick = {
|
||||||
if (validateForm() && selectedCategory != null &&
|
if (validateForm() && selectedCategory != null &&
|
||||||
selectedFrequency != null && selectedPriority != null) {
|
selectedFrequency != null && selectedPriority != null) {
|
||||||
@@ -321,21 +324,13 @@ fun EditTaskScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
enabled = validateForm() && selectedCategory != null &&
|
enabled = validateForm() && selectedCategory != null &&
|
||||||
selectedFrequency != null && selectedPriority != null
|
selectedFrequency != null && selectedPriority != null,
|
||||||
) {
|
isLoading = updateTaskState is ApiResult.Loading
|
||||||
if (updateTaskState is ApiResult.Loading) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary
|
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
Text(stringResource(Res.string.tasks_update))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.cozy))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.example.casera.ui.screens
|
package com.example.casera.ui.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
@@ -18,6 +16,7 @@ import com.example.casera.ui.components.auth.AuthHeader
|
|||||||
import com.example.casera.ui.components.common.ErrorCard
|
import com.example.casera.ui.components.common.ErrorCard
|
||||||
import com.example.casera.viewmodel.PasswordResetViewModel
|
import com.example.casera.viewmodel.PasswordResetViewModel
|
||||||
import com.example.casera.network.ApiResult
|
import com.example.casera.network.ApiResult
|
||||||
|
import com.example.casera.ui.theme.*
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@@ -74,37 +73,51 @@ fun ForgotPasswordScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
|
WarmGradientBackground {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.padding(paddingValues),
|
.padding(paddingValues),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(0.9f)
|
.fillMaxWidth(0.9f)
|
||||||
.wrapContentHeight(),
|
.wrapContentHeight(),
|
||||||
shape = RoundedCornerShape(24.dp),
|
showBlob = true,
|
||||||
colors = CardDefaults.cardColors(
|
blobVariation = 0
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
|
||||||
),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(32.dp),
|
.padding(OrganicSpacing.spacious),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(20.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.cozy)
|
||||||
) {
|
) {
|
||||||
AuthHeader(
|
OrganicIconContainer(
|
||||||
icon = Icons.Default.Key,
|
icon = Icons.Default.Key,
|
||||||
title = stringResource(Res.string.auth_forgot_title),
|
size = 80.dp,
|
||||||
subtitle = stringResource(Res.string.auth_forgot_subtitle)
|
iconScale = 0.5f,
|
||||||
|
backgroundColor = MaterialTheme.colorScheme.primary,
|
||||||
|
iconColor = MaterialTheme.colorScheme.onPrimary
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Text(
|
||||||
|
text = stringResource(Res.string.auth_forgot_title),
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.textPrimary,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(Res.string.auth_forgot_subtitle),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.textSecondary,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(OrganicSpacing.compact))
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = email,
|
value = email,
|
||||||
@@ -118,74 +131,58 @@ fun ForgotPasswordScreen(
|
|||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
shape = RoundedCornerShape(12.dp),
|
|
||||||
enabled = !isLoading
|
enabled = !isLoading
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
"We'll send a 6-digit verification code to this address",
|
"We'll send a 6-digit verification code to this address",
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.textSecondary,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
|
|
||||||
ErrorCard(message = errorMessage)
|
ErrorCard(message = errorMessage)
|
||||||
|
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = CardDefaults.cardColors(
|
accentColor = MaterialTheme.colorScheme.primary,
|
||||||
containerColor = MaterialTheme.colorScheme.primaryContainer
|
showBlob = false
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp),
|
.padding(OrganicSpacing.cozy),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.CheckCircle,
|
Icons.Default.CheckCircle,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.primary
|
tint = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
|
||||||
Text(
|
Text(
|
||||||
"Check your email for a 6-digit verification code",
|
"Check your email for a 6-digit verification code",
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onPrimaryContainer
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(
|
OrganicDivider(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
OrganicPrimaryButton(
|
||||||
|
text = stringResource(Res.string.auth_forgot_button),
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.setEmail(email)
|
viewModel.setEmail(email)
|
||||||
viewModel.requestPasswordReset(email)
|
viewModel.requestPasswordReset(email)
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(56.dp),
|
|
||||||
enabled = email.isNotEmpty() && !isLoading,
|
enabled = email.isNotEmpty() && !isLoading,
|
||||||
shape = RoundedCornerShape(12.dp)
|
isLoading = isLoading
|
||||||
) {
|
|
||||||
if (isLoading) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
|
||||||
strokeWidth = 2.dp
|
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
Icon(Icons.Default.Send, contentDescription = null)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(
|
|
||||||
stringResource(Res.string.auth_forgot_button),
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = onNavigateBack,
|
onClick = onNavigateBack,
|
||||||
@@ -201,3 +198,4 @@ fun ForgotPasswordScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,22 +1,18 @@
|
|||||||
package com.example.casera.ui.screens
|
package com.example.casera.ui.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
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.Brush
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.example.casera.ui.components.HandleErrors
|
import com.example.casera.ui.components.HandleErrors
|
||||||
import com.example.casera.ui.theme.AppRadius
|
import com.example.casera.ui.theme.*
|
||||||
import com.example.casera.viewmodel.ResidenceViewModel
|
import com.example.casera.viewmodel.ResidenceViewModel
|
||||||
import com.example.casera.viewmodel.TaskViewModel
|
import com.example.casera.viewmodel.TaskViewModel
|
||||||
import com.example.casera.network.ApiResult
|
import com.example.casera.network.ApiResult
|
||||||
@@ -67,17 +63,17 @@ fun HomeScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
|
WarmGradientBackground {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
.padding(horizontal = OrganicSpacing.comfortable, vertical = OrganicSpacing.cozy),
|
||||||
verticalArrangement = Arrangement.spacedBy(20.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.generous)
|
||||||
) {
|
) {
|
||||||
// Personalized Greeting
|
// Personalized Greeting
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(vertical = 8.dp)
|
modifier = Modifier.padding(vertical = OrganicSpacing.cozy)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.home_welcome),
|
text = stringResource(Res.string.home_welcome),
|
||||||
@@ -94,45 +90,23 @@ fun HomeScreen(
|
|||||||
when (summaryState) {
|
when (summaryState) {
|
||||||
is ApiResult.Success -> {
|
is ApiResult.Success -> {
|
||||||
val summary = (summaryState as ApiResult.Success).data
|
val summary = (summaryState as ApiResult.Success).data
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = MaterialTheme.shapes.large,
|
showBlob = true,
|
||||||
colors = CardDefaults.cardColors(
|
blobVariation = 0
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
|
||||||
),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth()
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(20.dp)
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.cozy)
|
||||||
) {
|
) {
|
||||||
// Gradient circular icon
|
// Gradient circular icon
|
||||||
Box(
|
OrganicIconContainer(
|
||||||
modifier = Modifier
|
icon = Icons.Default.Home,
|
||||||
.size(44.dp)
|
size = 44.dp
|
||||||
.clip(CircleShape)
|
|
||||||
.background(
|
|
||||||
Brush.linearGradient(
|
|
||||||
listOf(
|
|
||||||
Color(0xFF2563EB),
|
|
||||||
Color(0xFF8B5CF6)
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.Home,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = Color.White,
|
|
||||||
modifier = Modifier.size(24.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Column {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.home_overview),
|
text = stringResource(Res.string.home_overview),
|
||||||
@@ -147,35 +121,38 @@ fun HomeScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(20.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.generous))
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly
|
horizontalArrangement = Arrangement.SpaceEvenly
|
||||||
) {
|
) {
|
||||||
StatItem(
|
OrganicStatPill(
|
||||||
|
icon = Icons.Default.Home,
|
||||||
value = "${summary.residences.size}",
|
value = "${summary.residences.size}",
|
||||||
label = stringResource(Res.string.home_properties),
|
label = stringResource(Res.string.home_properties),
|
||||||
color = Color(0xFF3B82F6)
|
color = Color(0xFF3B82F6)
|
||||||
)
|
)
|
||||||
Divider(
|
OrganicDivider(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(48.dp)
|
.height(48.dp)
|
||||||
.width(1.dp),
|
.width(1.dp),
|
||||||
color = MaterialTheme.colorScheme.outlineVariant
|
vertical = true
|
||||||
)
|
)
|
||||||
StatItem(
|
OrganicStatPill(
|
||||||
|
icon = Icons.Default.Task,
|
||||||
value = "${totalSummary?.totalTasks ?: 0}",
|
value = "${totalSummary?.totalTasks ?: 0}",
|
||||||
label = stringResource(Res.string.home_total_tasks),
|
label = stringResource(Res.string.home_total_tasks),
|
||||||
color = Color(0xFF8B5CF6)
|
color = Color(0xFF8B5CF6)
|
||||||
)
|
)
|
||||||
Divider(
|
OrganicDivider(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(48.dp)
|
.height(48.dp)
|
||||||
.width(1.dp),
|
.width(1.dp),
|
||||||
color = MaterialTheme.colorScheme.outlineVariant
|
vertical = true
|
||||||
)
|
)
|
||||||
StatItem(
|
OrganicStatPill(
|
||||||
|
icon = Icons.Default.Schedule,
|
||||||
value = "${totalSummary?.totalPending ?: 0}",
|
value = "${totalSummary?.totalPending ?: 0}",
|
||||||
label = stringResource(Res.string.home_pending),
|
label = stringResource(Res.string.home_pending),
|
||||||
color = Color(0xFFF59E0B)
|
color = Color(0xFFF59E0B)
|
||||||
@@ -185,7 +162,7 @@ fun HomeScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
is ApiResult.Idle, is ApiResult.Loading -> {
|
is ApiResult.Idle, is ApiResult.Loading -> {
|
||||||
Card(modifier = Modifier.fillMaxWidth()) {
|
OrganicCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -223,36 +200,6 @@ fun HomeScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun StatItem(
|
|
||||||
value: String,
|
|
||||||
label: String,
|
|
||||||
color: Color
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.size(36.dp)
|
|
||||||
.clip(CircleShape)
|
|
||||||
.background(color.copy(alpha = 0.1f)),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = value,
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
color = color
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = label,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -263,45 +210,24 @@ private fun NavigationCard(
|
|||||||
iconColor: Color,
|
iconColor: Color,
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable { onClick() },
|
.clickable { onClick() },
|
||||||
shape = MaterialTheme.shapes.large,
|
showBlob = true,
|
||||||
colors = CardDefaults.cardColors(
|
blobVariation = 1
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
|
||||||
),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(20.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.comfortable)
|
||||||
) {
|
) {
|
||||||
// Gradient circular icon
|
// Gradient circular icon
|
||||||
Box(
|
OrganicIconContainer(
|
||||||
modifier = Modifier
|
icon = icon,
|
||||||
.size(56.dp)
|
size = 56.dp,
|
||||||
.clip(CircleShape)
|
iconColor = iconColor
|
||||||
.background(
|
|
||||||
Brush.linearGradient(
|
|
||||||
listOf(
|
|
||||||
iconColor,
|
|
||||||
iconColor.copy(alpha = 0.7f)
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
icon,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = Color.White,
|
|
||||||
modifier = Modifier.size(28.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
@@ -311,7 +237,7 @@ private fun NavigationCard(
|
|||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.minimal))
|
||||||
Text(
|
Text(
|
||||||
text = subtitle,
|
text = subtitle,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import com.example.casera.viewmodel.AuthViewModel
|
|||||||
import com.example.casera.network.ApiResult
|
import com.example.casera.network.ApiResult
|
||||||
import com.example.casera.analytics.PostHogAnalytics
|
import com.example.casera.analytics.PostHogAnalytics
|
||||||
import com.example.casera.analytics.AnalyticsEvents
|
import com.example.casera.analytics.AnalyticsEvents
|
||||||
|
import com.example.casera.ui.theme.*
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@@ -93,28 +94,25 @@ fun LoginScreen(
|
|||||||
|
|
||||||
val isLoading = loginState is ApiResult.Loading || googleSignInState is ApiResult.Loading
|
val isLoading = loginState is ApiResult.Loading || googleSignInState is ApiResult.Loading
|
||||||
|
|
||||||
|
WarmGradientBackground {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize(),
|
||||||
.background(MaterialTheme.colorScheme.background),
|
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(0.9f)
|
.fillMaxWidth(0.9f)
|
||||||
.wrapContentHeight(),
|
.wrapContentHeight(),
|
||||||
shape = MaterialTheme.shapes.extraLarge,
|
showBlob = true,
|
||||||
colors = CardDefaults.cardColors(
|
blobVariation = 0
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
|
||||||
),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(32.dp),
|
.padding(OrganicSpacing.xxl),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(20.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.lg)
|
||||||
) {
|
) {
|
||||||
AuthHeader(
|
AuthHeader(
|
||||||
icon = Icons.Default.Home,
|
icon = Icons.Default.Home,
|
||||||
@@ -122,7 +120,7 @@ fun LoginScreen(
|
|||||||
subtitle = stringResource(Res.string.app_tagline)
|
subtitle = stringResource(Res.string.app_tagline)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.sm))
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = username,
|
value = username,
|
||||||
@@ -133,7 +131,7 @@ fun LoginScreen(
|
|||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
shape = RoundedCornerShape(12.dp),
|
shape = RoundedCornerShape(OrganicRadius.md),
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
keyboardType = KeyboardType.Email,
|
keyboardType = KeyboardType.Email,
|
||||||
imeAction = ImeAction.Next
|
imeAction = ImeAction.Next
|
||||||
@@ -158,7 +156,7 @@ fun LoginScreen(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
shape = RoundedCornerShape(12.dp)
|
shape = RoundedCornerShape(OrganicRadius.md)
|
||||||
)
|
)
|
||||||
|
|
||||||
ErrorCard(message = errorMessage)
|
ErrorCard(message = errorMessage)
|
||||||
@@ -168,75 +166,33 @@ fun LoginScreen(
|
|||||||
googleSignInError = null
|
googleSignInError = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gradient button
|
// Organic Primary Button
|
||||||
Box(
|
OrganicPrimaryButton(
|
||||||
modifier = Modifier
|
text = stringResource(Res.string.auth_login_button),
|
||||||
.fillMaxWidth()
|
|
||||||
.height(56.dp)
|
|
||||||
.clip(MaterialTheme.shapes.medium)
|
|
||||||
.then(
|
|
||||||
if (username.isNotEmpty() && password.isNotEmpty() && !isLoading) {
|
|
||||||
Modifier.background(
|
|
||||||
Brush.linearGradient(
|
|
||||||
listOf(
|
|
||||||
Color(0xFF2563EB),
|
|
||||||
Color(0xFF8B5CF6)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Modifier.background(MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f))
|
|
||||||
}
|
|
||||||
),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Button(
|
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.login(username, password)
|
viewModel.login(username, password)
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
enabled = username.isNotEmpty() && password.isNotEmpty(),
|
enabled = username.isNotEmpty() && password.isNotEmpty(),
|
||||||
shape = MaterialTheme.shapes.medium,
|
isLoading = isLoading
|
||||||
colors = ButtonDefaults.buttonColors(
|
|
||||||
containerColor = Color.Transparent,
|
|
||||||
disabledContainerColor = Color.Transparent
|
|
||||||
)
|
)
|
||||||
) {
|
|
||||||
if (isLoading) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
color = Color.White,
|
|
||||||
strokeWidth = 2.dp
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
stringResource(Res.string.auth_login_button),
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
color = Color.White
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Divider with "or"
|
// Divider with "or"
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
HorizontalDivider(
|
OrganicDivider(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f)
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)
|
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "or",
|
text = "or",
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.padding(horizontal = OrganicSpacing.lg),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
HorizontalDivider(
|
OrganicDivider(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f)
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,3 +234,4 @@ fun LoginScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import com.example.casera.models.Residence
|
|||||||
import com.example.casera.models.TaskDetail
|
import com.example.casera.models.TaskDetail
|
||||||
import com.example.casera.storage.TokenStorage
|
import com.example.casera.storage.TokenStorage
|
||||||
import com.example.casera.ui.subscription.UpgradeScreen
|
import com.example.casera.ui.subscription.UpgradeScreen
|
||||||
|
import com.example.casera.ui.theme.*
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
@@ -45,7 +46,9 @@ fun MainScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WarmGradientBackground {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
containerColor = androidx.compose.ui.graphics.Color.Transparent,
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
NavigationBar(
|
NavigationBar(
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceContainer,
|
containerColor = MaterialTheme.colorScheme.surfaceContainer,
|
||||||
@@ -123,24 +126,6 @@ fun MainScreen(
|
|||||||
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
|
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// NavigationBarItem(
|
|
||||||
// icon = { Icon(Icons.Default.Person, contentDescription = "Profile") },
|
|
||||||
// label = { Text("Profile") },
|
|
||||||
// selected = selectedTab == 4,
|
|
||||||
// onClick = {
|
|
||||||
// selectedTab = 4
|
|
||||||
// navController.navigate(MainTabProfileRoute) {
|
|
||||||
// popUpTo(MainTabResidencesRoute) { inclusive = false }
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// colors = NavigationBarItemDefaults.colors(
|
|
||||||
// selectedIconColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
|
||||||
// selectedTextColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
|
||||||
// indicatorColor = MaterialTheme.colorScheme.secondaryContainer,
|
|
||||||
// unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
// unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
@@ -347,3 +332,4 @@ fun MainScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.example.casera.ui.screens
|
|||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
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.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
@@ -22,8 +21,7 @@ import com.example.casera.models.ResidenceShareCode
|
|||||||
import com.example.casera.network.ApiResult
|
import com.example.casera.network.ApiResult
|
||||||
import com.example.casera.network.ResidenceApi
|
import com.example.casera.network.ResidenceApi
|
||||||
import com.example.casera.storage.TokenStorage
|
import com.example.casera.storage.TokenStorage
|
||||||
import com.example.casera.ui.theme.AppRadius
|
import com.example.casera.ui.theme.*
|
||||||
import com.example.casera.ui.theme.AppSpacing
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@@ -68,7 +66,9 @@ fun ManageUsersScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WarmGradientBackground {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
containerColor = androidx.compose.ui.graphics.Color.Transparent,
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
@@ -90,7 +90,7 @@ fun ManageUsersScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
containerColor = androidx.compose.ui.graphics.Color.Transparent
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -114,7 +114,7 @@ fun ManageUsersScreen(
|
|||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.md)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Error,
|
Icons.Default.Error,
|
||||||
@@ -133,26 +133,23 @@ fun ManageUsersScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues),
|
.padding(paddingValues),
|
||||||
contentPadding = PaddingValues(AppSpacing.lg),
|
contentPadding = PaddingValues(OrganicSpacing.lg),
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.lg)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.lg)
|
||||||
) {
|
) {
|
||||||
// Share sections (primary owner only)
|
// Share sections (primary owner only)
|
||||||
if (isPrimaryOwner) {
|
if (isPrimaryOwner) {
|
||||||
// Easy Share Section
|
// Easy Share Section
|
||||||
item {
|
item {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(AppRadius.lg),
|
accentColor = MaterialTheme.colorScheme.primaryContainer
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.primaryContainer
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(AppSpacing.lg)
|
modifier = Modifier.padding(OrganicSpacing.lg)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.sm)
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.sm)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Share,
|
Icons.Default.Share,
|
||||||
@@ -167,24 +164,16 @@ fun ManageUsersScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.md))
|
Spacer(modifier = Modifier.height(OrganicSpacing.md))
|
||||||
|
|
||||||
Button(
|
OrganicPrimaryButton(
|
||||||
|
text = stringResource(Res.string.manage_users_send_invite),
|
||||||
onClick = onSharePackage,
|
onClick = onSharePackage,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = ButtonDefaults.buttonColors(
|
icon = Icons.Default.Send
|
||||||
containerColor = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
)
|
||||||
) {
|
|
||||||
Icon(Icons.Default.Send, null, modifier = Modifier.size(20.dp))
|
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
|
||||||
Text(
|
|
||||||
stringResource(Res.string.manage_users_send_invite),
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.sm))
|
Spacer(modifier = Modifier.height(OrganicSpacing.sm))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.manage_users_easy_share_desc),
|
text = stringResource(Res.string.manage_users_easy_share_desc),
|
||||||
@@ -201,37 +190,32 @@ fun ManageUsersScreen(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
HorizontalDivider(modifier = Modifier.weight(1f))
|
OrganicDivider(modifier = Modifier.weight(1f))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.manage_users_or),
|
text = stringResource(Res.string.manage_users_or),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.padding(horizontal = AppSpacing.lg)
|
modifier = Modifier.padding(horizontal = OrganicSpacing.lg)
|
||||||
)
|
)
|
||||||
HorizontalDivider(modifier = Modifier.weight(1f))
|
OrganicDivider(modifier = Modifier.weight(1f))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Share Code Section
|
// Share Code Section
|
||||||
item {
|
item {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth()
|
||||||
shape = RoundedCornerShape(AppRadius.lg),
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(AppSpacing.lg)
|
modifier = Modifier.padding(OrganicSpacing.lg)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.sm)
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.sm)
|
||||||
) {
|
) {
|
||||||
Icon(
|
OrganicIconContainer(
|
||||||
Icons.Default.Key,
|
icon = Icons.Default.Key,
|
||||||
contentDescription = null,
|
size = 24.dp
|
||||||
tint = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.manage_users_share_code),
|
text = stringResource(Res.string.manage_users_share_code),
|
||||||
@@ -240,20 +224,17 @@ fun ManageUsersScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.lg))
|
Spacer(modifier = Modifier.height(OrganicSpacing.lg))
|
||||||
|
|
||||||
// Share code display
|
// Share code display
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
accentColor = MaterialTheme.colorScheme.surface
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(AppSpacing.lg),
|
.padding(OrganicSpacing.lg),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@@ -291,9 +272,11 @@ fun ManageUsersScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.md))
|
Spacer(modifier = Modifier.height(OrganicSpacing.md))
|
||||||
|
|
||||||
Button(
|
OrganicPrimaryButton(
|
||||||
|
text = if (shareCode != null) stringResource(Res.string.manage_users_generate_new)
|
||||||
|
else stringResource(Res.string.manage_users_generate),
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
isGeneratingCode = true
|
isGeneratingCode = true
|
||||||
@@ -312,27 +295,14 @@ fun ManageUsersScreen(
|
|||||||
isGeneratingCode = false
|
isGeneratingCode = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
enabled = !isGeneratingCode,
|
enabled = !isGeneratingCode,
|
||||||
modifier = Modifier.fillMaxWidth()
|
isLoading = isGeneratingCode,
|
||||||
) {
|
icon = Icons.Default.Refresh
|
||||||
if (isGeneratingCode) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(20.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
|
||||||
strokeWidth = 2.dp
|
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
Icon(Icons.Default.Refresh, null, modifier = Modifier.size(20.dp))
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
|
||||||
Text(
|
|
||||||
if (shareCode != null) stringResource(Res.string.manage_users_generate_new)
|
|
||||||
else stringResource(Res.string.manage_users_generate)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shareCode != null) {
|
if (shareCode != null) {
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.sm))
|
Spacer(modifier = Modifier.height(OrganicSpacing.sm))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.manage_users_code_desc),
|
text = stringResource(Res.string.manage_users_code_desc),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
@@ -344,7 +314,7 @@ fun ManageUsersScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
HorizontalDivider()
|
OrganicDivider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,7 +339,8 @@ fun ManageUsersScreen(
|
|||||||
|
|
||||||
// Bottom spacing
|
// Bottom spacing
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -424,27 +395,23 @@ private fun UserCard(
|
|||||||
canRemove: Boolean,
|
canRemove: Boolean,
|
||||||
onRemove: () -> Unit
|
onRemove: () -> Unit
|
||||||
) {
|
) {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth()
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(AppSpacing.lg),
|
.padding(OrganicSpacing.lg),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.md)
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
// Avatar
|
// Avatar
|
||||||
Surface(
|
Surface(
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
shape = OrganicShapes.medium,
|
||||||
color = MaterialTheme.colorScheme.primaryContainer,
|
color = MaterialTheme.colorScheme.primaryContainer,
|
||||||
modifier = Modifier.size(48.dp)
|
modifier = Modifier.size(48.dp)
|
||||||
) {
|
) {
|
||||||
@@ -461,7 +428,7 @@ private fun UserCard(
|
|||||||
Column {
|
Column {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.sm)
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.sm)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = user.username,
|
text = user.username,
|
||||||
@@ -471,13 +438,13 @@ private fun UserCard(
|
|||||||
if (isOwner) {
|
if (isOwner) {
|
||||||
Surface(
|
Surface(
|
||||||
color = MaterialTheme.colorScheme.primaryContainer,
|
color = MaterialTheme.colorScheme.primaryContainer,
|
||||||
shape = RoundedCornerShape(AppRadius.xs)
|
shape = OrganicShapes.extraSmall
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.manage_users_owner_badge),
|
text = stringResource(Res.string.manage_users_owner_badge),
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
color = MaterialTheme.colorScheme.onPrimaryContainer,
|
color = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
modifier = Modifier.padding(horizontal = AppSpacing.sm, vertical = 2.dp)
|
modifier = Modifier.padding(horizontal = OrganicSpacing.sm, vertical = 2.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.example.casera.ui.screens
|
|||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
@@ -15,8 +14,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.example.casera.network.ApiResult
|
import com.example.casera.network.ApiResult
|
||||||
import com.example.casera.ui.theme.AppRadius
|
import com.example.casera.ui.theme.*
|
||||||
import com.example.casera.ui.theme.AppSpacing
|
|
||||||
import com.example.casera.util.DateUtils
|
import com.example.casera.util.DateUtils
|
||||||
import com.example.casera.viewmodel.NotificationPreferencesViewModel
|
import com.example.casera.viewmodel.NotificationPreferencesViewModel
|
||||||
import com.example.casera.analytics.PostHogAnalytics
|
import com.example.casera.analytics.PostHogAnalytics
|
||||||
@@ -93,7 +91,9 @@ fun NotificationPreferencesScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WarmGradientBackground {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
containerColor = androidx.compose.ui.graphics.Color.Transparent,
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(Res.string.notifications_title), fontWeight = FontWeight.SemiBold) },
|
title = { Text(stringResource(Res.string.notifications_title), fontWeight = FontWeight.SemiBold) },
|
||||||
@@ -103,7 +103,7 @@ fun NotificationPreferencesScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
containerColor = androidx.compose.ui.graphics.Color.Transparent
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -113,29 +113,23 @@ fun NotificationPreferencesScreen(
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(horizontal = AppSpacing.lg, vertical = AppSpacing.md),
|
.padding(horizontal = OrganicSpacing.lg, vertical = OrganicSpacing.md),
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.md)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
// Header
|
// Header
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth()
|
||||||
shape = RoundedCornerShape(AppRadius.lg),
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(AppSpacing.xl),
|
.padding(OrganicSpacing.xl),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.md)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
Icon(
|
OrganicIconContainer(
|
||||||
imageVector = Icons.Default.Notifications,
|
icon = Icons.Default.Notifications,
|
||||||
contentDescription = null,
|
size = 60.dp
|
||||||
modifier = Modifier.size(60.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
@@ -157,7 +151,7 @@ fun NotificationPreferencesScreen(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(AppSpacing.xl),
|
.padding(OrganicSpacing.xl),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
CircularProgressIndicator()
|
CircularProgressIndicator()
|
||||||
@@ -165,21 +159,18 @@ fun NotificationPreferencesScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is ApiResult.Error -> {
|
is ApiResult.Error -> {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = CardDefaults.cardColors(
|
accentColor = MaterialTheme.colorScheme.errorContainer
|
||||||
containerColor = MaterialTheme.colorScheme.errorContainer
|
|
||||||
),
|
|
||||||
shape = RoundedCornerShape(AppRadius.md)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(AppSpacing.lg),
|
.padding(OrganicSpacing.lg),
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.md)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.md),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.md),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -194,12 +185,11 @@ fun NotificationPreferencesScreen(
|
|||||||
fontWeight = FontWeight.SemiBold
|
fontWeight = FontWeight.SemiBold
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Button(
|
OrganicPrimaryButton(
|
||||||
|
text = stringResource(Res.string.common_retry),
|
||||||
onClick = { viewModel.loadPreferences() },
|
onClick = { viewModel.loadPreferences() },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
)
|
||||||
Text(stringResource(Res.string.common_retry))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,15 +200,11 @@ fun NotificationPreferencesScreen(
|
|||||||
stringResource(Res.string.notifications_task_section),
|
stringResource(Res.string.notifications_task_section),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
modifier = Modifier.padding(top = AppSpacing.md)
|
modifier = Modifier.padding(top = OrganicSpacing.md)
|
||||||
)
|
)
|
||||||
|
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth()
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
NotificationToggle(
|
NotificationToggle(
|
||||||
@@ -247,9 +233,8 @@ fun NotificationPreferencesScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalDivider(
|
OrganicDivider(
|
||||||
modifier = Modifier.padding(horizontal = AppSpacing.lg),
|
modifier = Modifier.padding(horizontal = OrganicSpacing.lg)
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
NotificationToggle(
|
NotificationToggle(
|
||||||
@@ -278,9 +263,8 @@ fun NotificationPreferencesScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalDivider(
|
OrganicDivider(
|
||||||
modifier = Modifier.padding(horizontal = AppSpacing.lg),
|
modifier = Modifier.padding(horizontal = OrganicSpacing.lg)
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
NotificationToggle(
|
NotificationToggle(
|
||||||
@@ -295,9 +279,8 @@ fun NotificationPreferencesScreen(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
HorizontalDivider(
|
OrganicDivider(
|
||||||
modifier = Modifier.padding(horizontal = AppSpacing.lg),
|
modifier = Modifier.padding(horizontal = OrganicSpacing.lg)
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
NotificationToggle(
|
NotificationToggle(
|
||||||
@@ -346,15 +329,11 @@ fun NotificationPreferencesScreen(
|
|||||||
stringResource(Res.string.notifications_other_section),
|
stringResource(Res.string.notifications_other_section),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
modifier = Modifier.padding(top = AppSpacing.md)
|
modifier = Modifier.padding(top = OrganicSpacing.md)
|
||||||
)
|
)
|
||||||
|
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth()
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
NotificationToggle(
|
NotificationToggle(
|
||||||
@@ -369,9 +348,8 @@ fun NotificationPreferencesScreen(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
HorizontalDivider(
|
OrganicDivider(
|
||||||
modifier = Modifier.padding(horizontal = AppSpacing.lg),
|
modifier = Modifier.padding(horizontal = OrganicSpacing.lg)
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
NotificationToggle(
|
NotificationToggle(
|
||||||
@@ -386,9 +364,8 @@ fun NotificationPreferencesScreen(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
HorizontalDivider(
|
OrganicDivider(
|
||||||
modifier = Modifier.padding(horizontal = AppSpacing.lg),
|
modifier = Modifier.padding(horizontal = OrganicSpacing.lg)
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
NotificationToggle(
|
NotificationToggle(
|
||||||
@@ -438,15 +415,11 @@ fun NotificationPreferencesScreen(
|
|||||||
stringResource(Res.string.notifications_email_section),
|
stringResource(Res.string.notifications_email_section),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
modifier = Modifier.padding(top = AppSpacing.md)
|
modifier = Modifier.padding(top = OrganicSpacing.md)
|
||||||
)
|
)
|
||||||
|
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth()
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
NotificationToggle(
|
NotificationToggle(
|
||||||
@@ -463,7 +436,8 @@ fun NotificationPreferencesScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -482,8 +456,8 @@ private fun NotificationToggle(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(AppSpacing.lg),
|
.padding(OrganicSpacing.lg),
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.md),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.md),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -528,8 +502,8 @@ private fun NotificationTimePickerRow(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(start = AppSpacing.lg + 24.dp + AppSpacing.md, end = AppSpacing.lg, bottom = AppSpacing.md),
|
.padding(start = OrganicSpacing.lg + 24.dp + OrganicSpacing.md, end = OrganicSpacing.lg, bottom = OrganicSpacing.md),
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.sm),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.sm),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -584,7 +558,7 @@ private fun HourPickerDialog(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.md)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = DateUtils.formatHour(selectedHour),
|
text = DateUtils.formatHour(selectedHour),
|
||||||
@@ -601,7 +575,7 @@ private fun HourPickerDialog(
|
|||||||
// AM hours (6 AM - 11 AM)
|
// AM hours (6 AM - 11 AM)
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.xs)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.xs)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
"AM",
|
"AM",
|
||||||
@@ -620,7 +594,7 @@ private fun HourPickerDialog(
|
|||||||
// PM hours (12 PM - 5 PM)
|
// PM hours (12 PM - 5 PM)
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.xs)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.xs)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
"PM",
|
"PM",
|
||||||
@@ -639,7 +613,7 @@ private fun HourPickerDialog(
|
|||||||
// Evening hours (6 PM - 11 PM)
|
// Evening hours (6 PM - 11 PM)
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.xs)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.xs)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
"EVE",
|
"EVE",
|
||||||
@@ -687,7 +661,7 @@ private fun HourChip(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(56.dp)
|
.width(56.dp)
|
||||||
.clickable { onClick() },
|
.clickable { onClick() },
|
||||||
shape = RoundedCornerShape(AppRadius.sm),
|
shape = OrganicShapes.small,
|
||||||
color = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceVariant
|
color = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceVariant
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@@ -695,7 +669,7 @@ private fun HourChip(
|
|||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal,
|
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal,
|
||||||
color = if (isSelected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurfaceVariant,
|
color = if (isSelected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.padding(horizontal = AppSpacing.sm, vertical = AppSpacing.xs),
|
modifier = Modifier.padding(horizontal = OrganicSpacing.sm, vertical = OrganicSpacing.xs),
|
||||||
textAlign = androidx.compose.ui.text.style.TextAlign.Center
|
textAlign = androidx.compose.ui.text.style.TextAlign.Center
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.example.casera.utils.SubscriptionHelper
|
|||||||
import com.example.casera.ui.theme.AppRadius
|
import com.example.casera.ui.theme.AppRadius
|
||||||
import com.example.casera.ui.theme.AppSpacing
|
import com.example.casera.ui.theme.AppSpacing
|
||||||
import com.example.casera.ui.theme.ThemeManager
|
import com.example.casera.ui.theme.ThemeManager
|
||||||
|
import com.example.casera.ui.theme.*
|
||||||
import com.example.casera.viewmodel.AuthViewModel
|
import com.example.casera.viewmodel.AuthViewModel
|
||||||
import com.example.casera.network.ApiResult
|
import com.example.casera.network.ApiResult
|
||||||
import com.example.casera.storage.TokenStorage
|
import com.example.casera.storage.TokenStorage
|
||||||
@@ -140,6 +141,7 @@ fun ProfileScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
|
WarmGradientBackground {
|
||||||
if (isLoadingUser) {
|
if (isLoadingUser) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -157,16 +159,15 @@ fun ProfileScreen(
|
|||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(start = 24.dp, end = 24.dp, top = 24.dp, bottom = 96.dp),
|
.padding(start = 24.dp, end = 24.dp, top = 24.dp, bottom = 96.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(20.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.sm))
|
||||||
|
|
||||||
// Profile Icon
|
// Profile Icon
|
||||||
Icon(
|
OrganicIconContainer(
|
||||||
Icons.Default.AccountCircle,
|
icon = Icons.Default.AccountCircle,
|
||||||
contentDescription = null,
|
size = 80.dp,
|
||||||
modifier = Modifier.size(80.dp),
|
iconSize = 48.dp
|
||||||
tint = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
@@ -175,27 +176,24 @@ fun ProfileScreen(
|
|||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.sm))
|
||||||
|
|
||||||
// Edit Profile Section (scrolls down to profile fields)
|
// Edit Profile Section
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable { /* Profile fields are below - could add scroll behavior */ },
|
.clickable { /* Profile fields are below - could add scroll behavior */ }
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
.naturalShadow()
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(AppSpacing.lg),
|
.padding(OrganicSpacing.lg),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.xs)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.xs)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.profile_edit_profile),
|
text = stringResource(Res.string.profile_edit_profile),
|
||||||
@@ -219,24 +217,21 @@ fun ProfileScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Theme Selector Section
|
// Theme Selector Section
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable { showThemePicker = true },
|
.clickable { showThemePicker = true }
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
.naturalShadow()
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(AppSpacing.lg),
|
.padding(OrganicSpacing.lg),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.xs)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.xs)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.profile_appearance),
|
text = stringResource(Res.string.profile_appearance),
|
||||||
@@ -258,24 +253,21 @@ fun ProfileScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Notification Preferences Section
|
// Notification Preferences Section
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable { onNavigateToNotificationPreferences() },
|
.clickable { onNavigateToNotificationPreferences() }
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
.naturalShadow()
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(AppSpacing.lg),
|
.padding(OrganicSpacing.lg),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.xs)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.xs)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.profile_notifications),
|
text = stringResource(Res.string.profile_notifications),
|
||||||
@@ -298,26 +290,23 @@ fun ProfileScreen(
|
|||||||
|
|
||||||
// Contact Support Section
|
// Contact Support Section
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable {
|
||||||
uriHandler.openUri("mailto:caseraSupport@treymail.com?subject=Casera%20Support%20Request")
|
uriHandler.openUri("mailto:caseraSupport@treymail.com?subject=Casera%20Support%20Request")
|
||||||
},
|
}
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
.naturalShadow()
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(AppSpacing.lg),
|
.padding(OrganicSpacing.lg),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.xs)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.xs)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.profile_contact_support),
|
text = stringResource(Res.string.profile_contact_support),
|
||||||
@@ -339,26 +328,23 @@ fun ProfileScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Privacy Policy Section
|
// Privacy Policy Section
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable {
|
||||||
uriHandler.openUri("https://mycrib.treytartt.com/privacy")
|
uriHandler.openUri("https://mycrib.treytartt.com/privacy")
|
||||||
},
|
}
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
.naturalShadow()
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(AppSpacing.lg),
|
.padding(OrganicSpacing.lg),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.xs)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.xs)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.profile_privacy),
|
text = stringResource(Res.string.profile_privacy),
|
||||||
@@ -381,24 +367,22 @@ fun ProfileScreen(
|
|||||||
|
|
||||||
// Subscription Section - Only show if limitations are enabled
|
// Subscription Section - Only show if limitations are enabled
|
||||||
if (currentSubscription?.limitationsEnabled == true) {
|
if (currentSubscription?.limitationsEnabled == true) {
|
||||||
Divider(modifier = Modifier.padding(vertical = AppSpacing.sm))
|
OrganicDivider(modifier = Modifier.padding(vertical = OrganicSpacing.sm))
|
||||||
|
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
.fillMaxWidth()
|
||||||
colors = CardDefaults.cardColors(
|
.naturalShadow()
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(AppSpacing.lg),
|
.padding(OrganicSpacing.lg),
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.md)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.md)
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Star,
|
imageVector = Icons.Default.Star,
|
||||||
@@ -407,7 +391,7 @@ fun ProfileScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.xs)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.xs)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = if (SubscriptionHelper.currentTier == "pro") stringResource(Res.string.profile_pro_plan) else stringResource(Res.string.profile_free_plan),
|
text = if (SubscriptionHelper.currentTier == "pro") stringResource(Res.string.profile_pro_plan) else stringResource(Res.string.profile_free_plan),
|
||||||
@@ -430,8 +414,8 @@ fun ProfileScreen(
|
|||||||
if (SubscriptionHelper.currentTier != "pro") {
|
if (SubscriptionHelper.currentTier != "pro") {
|
||||||
// Upgrade Benefits List
|
// Upgrade Benefits List
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(vertical = AppSpacing.sm),
|
modifier = Modifier.padding(vertical = OrganicSpacing.sm),
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.sm)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.sm)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.profile_upgrade_benefits_title),
|
text = stringResource(Res.string.profile_upgrade_benefits_title),
|
||||||
@@ -483,7 +467,7 @@ fun ProfileScreen(
|
|||||||
imageVector = Icons.Default.KeyboardArrowUp,
|
imageVector = Icons.Default.KeyboardArrowUp,
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
Spacer(modifier = Modifier.width(OrganicSpacing.sm))
|
||||||
Text(stringResource(Res.string.profile_upgrade_to_pro), fontWeight = FontWeight.SemiBold)
|
Text(stringResource(Res.string.profile_upgrade_to_pro), fontWeight = FontWeight.SemiBold)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -491,13 +475,13 @@ fun ProfileScreen(
|
|||||||
text = stringResource(Res.string.profile_manage_subscription),
|
text = stringResource(Res.string.profile_manage_subscription),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.padding(top = AppSpacing.xs)
|
modifier = Modifier.padding(top = OrganicSpacing.xs)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider(modifier = Modifier.padding(vertical = AppSpacing.sm))
|
OrganicDivider(modifier = Modifier.padding(vertical = OrganicSpacing.sm))
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
@@ -610,7 +594,7 @@ fun ProfileScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.sm))
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -648,14 +632,14 @@ fun ProfileScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.lg))
|
||||||
|
|
||||||
// App Version Section
|
// App Version Section
|
||||||
HorizontalDivider(modifier = Modifier.padding(vertical = AppSpacing.md))
|
OrganicDivider(modifier = Modifier.padding(vertical = OrganicSpacing.md))
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.xs)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.xs)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.profile_app_name),
|
text = stringResource(Res.string.profile_app_name),
|
||||||
@@ -670,7 +654,8 @@ fun ProfileScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.lg))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -713,7 +698,7 @@ private fun UpgradeBenefitRow(
|
|||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.sm)
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.sm)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
import com.example.casera.ui.components.HandleErrors
|
import com.example.casera.ui.components.HandleErrors
|
||||||
import com.example.casera.ui.components.auth.AuthHeader
|
import com.example.casera.ui.components.auth.AuthHeader
|
||||||
import com.example.casera.ui.components.common.ErrorCard
|
import com.example.casera.ui.components.common.ErrorCard
|
||||||
|
import com.example.casera.ui.theme.*
|
||||||
import com.example.casera.viewmodel.AuthViewModel
|
import com.example.casera.viewmodel.AuthViewModel
|
||||||
import com.example.casera.network.ApiResult
|
import com.example.casera.network.ApiResult
|
||||||
import com.example.casera.analytics.PostHogAnalytics
|
import com.example.casera.analytics.PostHogAnalytics
|
||||||
@@ -65,6 +66,7 @@ fun RegisterScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WarmGradientBackground {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
@@ -75,21 +77,22 @@ fun RegisterScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
containerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.9f)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
containerColor = androidx.compose.ui.graphics.Color.Transparent
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(24.dp),
|
.padding(OrganicSpacing.xl),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(20.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.lg)
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.sm))
|
||||||
|
|
||||||
AuthHeader(
|
AuthHeader(
|
||||||
icon = Icons.Default.PersonAdd,
|
icon = Icons.Default.PersonAdd,
|
||||||
@@ -97,8 +100,14 @@ fun RegisterScreen(
|
|||||||
subtitle = stringResource(Res.string.auth_register_subtitle)
|
subtitle = stringResource(Res.string.auth_register_subtitle)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.md))
|
||||||
|
|
||||||
|
OrganicCard(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = username,
|
value = username,
|
||||||
onValueChange = { username = it },
|
onValueChange = { username = it },
|
||||||
@@ -123,6 +132,8 @@ fun RegisterScreen(
|
|||||||
shape = RoundedCornerShape(12.dp)
|
shape = RoundedCornerShape(12.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
OrganicDivider()
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = password,
|
value = password,
|
||||||
onValueChange = { password = it },
|
onValueChange = { password = it },
|
||||||
@@ -148,13 +159,16 @@ fun RegisterScreen(
|
|||||||
visualTransformation = PasswordVisualTransformation(),
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
shape = RoundedCornerShape(12.dp)
|
shape = RoundedCornerShape(12.dp)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ErrorCard(message = errorMessage)
|
ErrorCard(message = errorMessage)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.sm))
|
||||||
|
|
||||||
val passwordsDontMatchMessage = stringResource(Res.string.auth_passwords_dont_match)
|
val passwordsDontMatchMessage = stringResource(Res.string.auth_passwords_dont_match)
|
||||||
Button(
|
OrganicPrimaryButton(
|
||||||
|
text = stringResource(Res.string.auth_register_button),
|
||||||
onClick = {
|
onClick = {
|
||||||
when {
|
when {
|
||||||
password != confirmPassword -> {
|
password != confirmPassword -> {
|
||||||
@@ -167,29 +181,14 @@ fun RegisterScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.fillMaxWidth()
|
|
||||||
.height(56.dp),
|
|
||||||
enabled = username.isNotEmpty() && email.isNotEmpty() &&
|
enabled = username.isNotEmpty() && email.isNotEmpty() &&
|
||||||
password.isNotEmpty() && !isLoading,
|
password.isNotEmpty() && !isLoading,
|
||||||
shape = RoundedCornerShape(12.dp)
|
isLoading = isLoading
|
||||||
) {
|
|
||||||
if (isLoading) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
|
||||||
strokeWidth = 2.dp
|
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
stringResource(Res.string.auth_register_button),
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.md))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.example.casera.ui.screens
|
package com.example.casera.ui.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
@@ -20,6 +18,7 @@ import com.example.casera.ui.components.auth.RequirementItem
|
|||||||
import com.example.casera.ui.components.common.ErrorCard
|
import com.example.casera.ui.components.common.ErrorCard
|
||||||
import com.example.casera.viewmodel.PasswordResetViewModel
|
import com.example.casera.viewmodel.PasswordResetViewModel
|
||||||
import com.example.casera.network.ApiResult
|
import com.example.casera.network.ApiResult
|
||||||
|
import com.example.casera.ui.theme.*
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@@ -80,90 +79,116 @@ fun ResetPasswordScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
|
WarmGradientBackground {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.padding(paddingValues),
|
.padding(paddingValues),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(0.9f)
|
.fillMaxWidth(0.9f)
|
||||||
.wrapContentHeight(),
|
.wrapContentHeight(),
|
||||||
shape = RoundedCornerShape(24.dp),
|
showBlob = true,
|
||||||
colors = CardDefaults.cardColors(
|
blobVariation = 2
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
|
||||||
),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(32.dp),
|
.padding(OrganicSpacing.spacious),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(20.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.cozy)
|
||||||
) {
|
) {
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
// Success State
|
// Success State
|
||||||
AuthHeader(
|
OrganicIconContainer(
|
||||||
icon = Icons.Default.CheckCircle,
|
icon = Icons.Default.CheckCircle,
|
||||||
title = "Success!",
|
size = 80.dp,
|
||||||
subtitle = "Your password has been reset successfully"
|
iconScale = 0.5f,
|
||||||
|
backgroundColor = MaterialTheme.colorScheme.primary,
|
||||||
|
iconColor = MaterialTheme.colorScheme.onPrimary
|
||||||
)
|
)
|
||||||
|
|
||||||
Card(
|
Text(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
text = "Success!",
|
||||||
colors = CardDefaults.cardColors(
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
containerColor = MaterialTheme.colorScheme.primaryContainer
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.textPrimary,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Your password has been reset successfully",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.textSecondary,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
|
OrganicCard(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
accentColor = MaterialTheme.colorScheme.primary,
|
||||||
|
showBlob = false
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
"You can now log in with your new password",
|
"You can now log in with your new password",
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(OrganicSpacing.cozy),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onPrimaryContainer,
|
color = MaterialTheme.colorScheme.textPrimary,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(
|
OrganicDivider(
|
||||||
onClick = onPasswordResetSuccess,
|
modifier = Modifier.fillMaxWidth()
|
||||||
modifier = Modifier
|
)
|
||||||
.fillMaxWidth()
|
|
||||||
.height(56.dp),
|
OrganicPrimaryButton(
|
||||||
shape = RoundedCornerShape(12.dp)
|
text = "Return to Login",
|
||||||
) {
|
onClick = onPasswordResetSuccess
|
||||||
Text(
|
|
||||||
"Return to Login",
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Reset Password Form
|
// Reset Password Form
|
||||||
AuthHeader(
|
OrganicIconContainer(
|
||||||
icon = Icons.Default.LockReset,
|
icon = Icons.Default.LockReset,
|
||||||
title = "Set New Password",
|
size = 80.dp,
|
||||||
subtitle = "Create a strong password to secure your account"
|
iconScale = 0.5f,
|
||||||
|
backgroundColor = MaterialTheme.colorScheme.primary,
|
||||||
|
iconColor = MaterialTheme.colorScheme.onPrimary
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Set New Password",
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.textPrimary,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Create a strong password to secure your account",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.textSecondary,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
|
|
||||||
// Password Requirements
|
// Password Requirements
|
||||||
Card(
|
OrganicCard(
|
||||||
colors = CardDefaults.cardColors(
|
modifier = Modifier.fillMaxWidth(),
|
||||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
accentColor = MaterialTheme.colorScheme.secondary,
|
||||||
)
|
showBlob = false
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp),
|
.padding(OrganicSpacing.cozy),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
"Password Requirements",
|
"Password Requirements",
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
fontWeight = FontWeight.SemiBold
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
)
|
)
|
||||||
|
|
||||||
RequirementItem(
|
RequirementItem(
|
||||||
@@ -206,7 +231,6 @@ fun ResetPasswordScreen(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
visualTransformation = if (newPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
visualTransformation = if (newPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
shape = RoundedCornerShape(12.dp),
|
|
||||||
enabled = !isLoading
|
enabled = !isLoading
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -231,41 +255,22 @@ fun ResetPasswordScreen(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
visualTransformation = if (confirmPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
visualTransformation = if (confirmPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
shape = RoundedCornerShape(12.dp),
|
|
||||||
enabled = !isLoading
|
enabled = !isLoading
|
||||||
)
|
)
|
||||||
|
|
||||||
ErrorCard(message = errorMessage)
|
ErrorCard(message = errorMessage)
|
||||||
|
|
||||||
Button(
|
OrganicDivider(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
OrganicPrimaryButton(
|
||||||
|
text = if (isLoggingIn) "Logging in..." else stringResource(Res.string.auth_reset_button),
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.resetPassword(newPassword, confirmPassword)
|
viewModel.resetPassword(newPassword, confirmPassword)
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(56.dp),
|
|
||||||
enabled = isFormValid && !isLoading && !isLoggingIn,
|
enabled = isFormValid && !isLoading && !isLoggingIn,
|
||||||
shape = RoundedCornerShape(12.dp)
|
isLoading = isLoading || isLoggingIn
|
||||||
) {
|
|
||||||
if (isLoading || isLoggingIn) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
|
||||||
strokeWidth = 2.dp
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(
|
|
||||||
if (isLoggingIn) "Logging in..." else "Resetting...",
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Icon(Icons.Default.LockReset, contentDescription = null)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(
|
|
||||||
stringResource(Res.string.auth_reset_button),
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -274,4 +279,3 @@ fun ResetPasswordScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import com.example.casera.util.DateUtils
|
|||||||
import com.example.casera.platform.rememberShareResidence
|
import com.example.casera.platform.rememberShareResidence
|
||||||
import com.example.casera.analytics.PostHogAnalytics
|
import com.example.casera.analytics.PostHogAnalytics
|
||||||
import com.example.casera.analytics.AnalyticsEvents
|
import com.example.casera.analytics.AnalyticsEvents
|
||||||
|
import com.example.casera.ui.theme.*
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@@ -539,6 +540,7 @@ fun ResidenceDetailScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
|
WarmGradientBackground {
|
||||||
ApiResultHandler(
|
ApiResultHandler(
|
||||||
state = residenceState,
|
state = residenceState,
|
||||||
onRetry = {
|
onRetry = {
|
||||||
@@ -551,7 +553,7 @@ fun ResidenceDetailScreen(
|
|||||||
loadingContent = {
|
loadingContent = {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.cozy)
|
||||||
) {
|
) {
|
||||||
CircularProgressIndicator()
|
CircularProgressIndicator()
|
||||||
Text(
|
Text(
|
||||||
@@ -566,23 +568,21 @@ fun ResidenceDetailScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues),
|
.padding(paddingValues),
|
||||||
contentPadding = PaddingValues(16.dp),
|
contentPadding = PaddingValues(OrganicSpacing.cozy),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.cozy)
|
||||||
) {
|
) {
|
||||||
// Property Header Card
|
// Property Header Card
|
||||||
item {
|
item {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = CardDefaults.cardColors(
|
accentColor = MaterialTheme.colorScheme.primary,
|
||||||
containerColor = MaterialTheme.colorScheme.primaryContainer
|
showBlob = true,
|
||||||
),
|
blobVariation = 0
|
||||||
shape = RoundedCornerShape(20.dp),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(24.dp)
|
.padding(OrganicSpacing.comfortable)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -594,7 +594,7 @@ fun ResidenceDetailScreen(
|
|||||||
text = residence.name,
|
text = residence.name,
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = MaterialTheme.colorScheme.onPrimaryContainer
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -607,21 +607,60 @@ fun ResidenceDetailScreen(
|
|||||||
residence.stateProvince != null || residence.postalCode != null ||
|
residence.stateProvince != null || residence.postalCode != null ||
|
||||||
residence.country != null) {
|
residence.country != null) {
|
||||||
item {
|
item {
|
||||||
InfoCard(
|
OrganicCard(
|
||||||
icon = Icons.Default.LocationOn,
|
modifier = Modifier.fillMaxWidth(),
|
||||||
title = stringResource(Res.string.properties_address_section)
|
showBlob = true,
|
||||||
|
blobVariation = 1
|
||||||
) {
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(OrganicSpacing.cozy),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
|
) {
|
||||||
|
OrganicIconContainer(
|
||||||
|
icon = Icons.Default.LocationOn,
|
||||||
|
size = 40.dp,
|
||||||
|
iconScale = 0.5f,
|
||||||
|
backgroundColor = MaterialTheme.colorScheme.primary,
|
||||||
|
iconColor = MaterialTheme.colorScheme.onPrimary
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(Res.string.properties_address_section),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
OrganicDivider(horizontalPadding = OrganicSpacing.compact)
|
||||||
if (residence.streetAddress != null) {
|
if (residence.streetAddress != null) {
|
||||||
Text(text = residence.streetAddress)
|
Text(
|
||||||
|
text = residence.streetAddress,
|
||||||
|
color = MaterialTheme.colorScheme.textSecondary
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (residence.apartmentUnit != null) {
|
if (residence.apartmentUnit != null) {
|
||||||
Text(text = "Unit: ${residence.apartmentUnit}")
|
Text(
|
||||||
|
text = "Unit: ${residence.apartmentUnit}",
|
||||||
|
color = MaterialTheme.colorScheme.textSecondary
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (residence.city != null || residence.stateProvince != null || residence.postalCode != null) {
|
if (residence.city != null || residence.stateProvince != null || residence.postalCode != null) {
|
||||||
Text(text = "${residence.city ?: ""}, ${residence.stateProvince ?: ""} ${residence.postalCode ?: ""}")
|
Text(
|
||||||
|
text = "${residence.city ?: ""}, ${residence.stateProvince ?: ""} ${residence.postalCode ?: ""}",
|
||||||
|
color = MaterialTheme.colorScheme.textSecondary
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (residence.country != null) {
|
if (residence.country != null) {
|
||||||
Text(text = residence.country)
|
Text(
|
||||||
|
text = residence.country,
|
||||||
|
color = MaterialTheme.colorScheme.textSecondary
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -631,10 +670,36 @@ fun ResidenceDetailScreen(
|
|||||||
if (residence.bedrooms != null || residence.bathrooms != null ||
|
if (residence.bedrooms != null || residence.bathrooms != null ||
|
||||||
residence.squareFootage != null || residence.yearBuilt != null) {
|
residence.squareFootage != null || residence.yearBuilt != null) {
|
||||||
item {
|
item {
|
||||||
InfoCard(
|
OrganicCard(
|
||||||
icon = Icons.Default.Info,
|
modifier = Modifier.fillMaxWidth(),
|
||||||
title = stringResource(Res.string.properties_property_details_section)
|
showBlob = true,
|
||||||
|
blobVariation = 2
|
||||||
) {
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(OrganicSpacing.cozy),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
|
) {
|
||||||
|
OrganicIconContainer(
|
||||||
|
icon = Icons.Default.Info,
|
||||||
|
size = 40.dp,
|
||||||
|
iconScale = 0.5f,
|
||||||
|
backgroundColor = MaterialTheme.colorScheme.primary,
|
||||||
|
iconColor = MaterialTheme.colorScheme.onPrimary
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(Res.string.properties_property_details_section),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
OrganicDivider(horizontalPadding = OrganicSpacing.compact)
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly
|
horizontalArrangement = Arrangement.SpaceEvenly
|
||||||
@@ -646,7 +711,7 @@ fun ResidenceDetailScreen(
|
|||||||
PropertyDetailItem(Icons.Default.Bathroom, "$it", "Bathrooms")
|
PropertyDetailItem(Icons.Default.Bathroom, "$it", "Bathrooms")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.compact))
|
||||||
residence.squareFootage?.let {
|
residence.squareFootage?.let {
|
||||||
DetailRow(Icons.Default.SquareFoot, "Square Footage", "$it sq ft")
|
DetailRow(Icons.Default.SquareFoot, "Square Footage", "$it sq ft")
|
||||||
}
|
}
|
||||||
@@ -659,30 +724,84 @@ fun ResidenceDetailScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Description Card
|
// Description Card
|
||||||
if (residence.description != null && !residence.description.isEmpty()) {
|
if (residence.description != null && !residence.description.isEmpty()) {
|
||||||
item {
|
item {
|
||||||
InfoCard(
|
OrganicCard(
|
||||||
icon = Icons.Default.Description,
|
modifier = Modifier.fillMaxWidth(),
|
||||||
title = stringResource(Res.string.properties_description_section)
|
showBlob = true,
|
||||||
|
blobVariation = 0
|
||||||
) {
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(OrganicSpacing.cozy),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
|
) {
|
||||||
|
OrganicIconContainer(
|
||||||
|
icon = Icons.Default.Description,
|
||||||
|
size = 40.dp,
|
||||||
|
iconScale = 0.5f,
|
||||||
|
backgroundColor = MaterialTheme.colorScheme.primary,
|
||||||
|
iconColor = MaterialTheme.colorScheme.onPrimary
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(Res.string.properties_description_section),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
OrganicDivider(horizontalPadding = OrganicSpacing.compact)
|
||||||
Text(
|
Text(
|
||||||
text = residence.description,
|
text = residence.description,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.textSecondary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Purchase Information
|
// Purchase Information
|
||||||
if (residence.purchaseDate != null || residence.purchasePrice != null) {
|
if (residence.purchaseDate != null || residence.purchasePrice != null) {
|
||||||
item {
|
item {
|
||||||
InfoCard(
|
OrganicCard(
|
||||||
icon = Icons.Default.AttachMoney,
|
modifier = Modifier.fillMaxWidth(),
|
||||||
title = stringResource(Res.string.properties_purchase_info)
|
showBlob = true,
|
||||||
|
blobVariation = 1
|
||||||
) {
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(OrganicSpacing.cozy),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
|
) {
|
||||||
|
OrganicIconContainer(
|
||||||
|
icon = Icons.Default.AttachMoney,
|
||||||
|
size = 40.dp,
|
||||||
|
iconScale = 0.5f,
|
||||||
|
backgroundColor = MaterialTheme.colorScheme.primary,
|
||||||
|
iconColor = MaterialTheme.colorScheme.onPrimary
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(Res.string.properties_purchase_info),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
OrganicDivider(horizontalPadding = OrganicSpacing.compact)
|
||||||
residence.purchaseDate?.let {
|
residence.purchaseDate?.let {
|
||||||
DetailRow(Icons.Default.Event, "Purchase Date", DateUtils.formatDateMedium(it))
|
DetailRow(Icons.Default.Event, "Purchase Date", DateUtils.formatDateMedium(it))
|
||||||
}
|
}
|
||||||
@@ -692,27 +811,29 @@ fun ResidenceDetailScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Tasks Section Header
|
// Tasks Section Header
|
||||||
item {
|
item {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 8.dp),
|
.padding(vertical = OrganicSpacing.compact),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
) {
|
) {
|
||||||
Icon(
|
OrganicIconContainer(
|
||||||
Icons.Default.Assignment,
|
icon = Icons.Default.Assignment,
|
||||||
contentDescription = null,
|
size = 36.dp,
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
iconScale = 0.5f,
|
||||||
modifier = Modifier.size(28.dp)
|
backgroundColor = MaterialTheme.colorScheme.primary,
|
||||||
|
iconColor = MaterialTheme.colorScheme.onPrimary
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.tasks_title),
|
text = stringResource(Res.string.tasks_title),
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = MaterialTheme.colorScheme.primary
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -732,16 +853,15 @@ fun ResidenceDetailScreen(
|
|||||||
}
|
}
|
||||||
is ApiResult.Error -> {
|
is ApiResult.Error -> {
|
||||||
item {
|
item {
|
||||||
Card(
|
OrganicCard(
|
||||||
colors = CardDefaults.cardColors(
|
modifier = Modifier.fillMaxWidth(),
|
||||||
containerColor = MaterialTheme.colorScheme.errorContainer
|
accentColor = MaterialTheme.colorScheme.error,
|
||||||
),
|
showBlob = false
|
||||||
shape = RoundedCornerShape(12.dp)
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Error loading tasks: ${com.example.casera.util.ErrorMessageParser.parse((tasksState as ApiResult.Error).message)}",
|
text = "Error loading tasks: ${com.example.casera.util.ErrorMessageParser.parse((tasksState as ApiResult.Error).message)}",
|
||||||
color = MaterialTheme.colorScheme.onErrorContainer,
|
color = MaterialTheme.colorScheme.error,
|
||||||
modifier = Modifier.padding(16.dp)
|
modifier = Modifier.padding(OrganicSpacing.cozy)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -751,32 +871,35 @@ fun ResidenceDetailScreen(
|
|||||||
val allTasksEmpty = taskData.columns.all { it.tasks.isEmpty() }
|
val allTasksEmpty = taskData.columns.all { it.tasks.isEmpty() }
|
||||||
if (allTasksEmpty) {
|
if (allTasksEmpty) {
|
||||||
item {
|
item {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(16.dp)
|
showBlob = true,
|
||||||
|
blobVariation = 2
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(32.dp),
|
.padding(OrganicSpacing.spacious),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.cozy)
|
||||||
) {
|
) {
|
||||||
Icon(
|
OrganicIconContainer(
|
||||||
Icons.Default.Assignment,
|
icon = Icons.Default.Assignment,
|
||||||
contentDescription = null,
|
size = 64.dp,
|
||||||
modifier = Modifier.size(64.dp),
|
iconScale = 0.5f,
|
||||||
tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f)
|
backgroundColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.3f),
|
||||||
|
iconColor = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
Text(
|
Text(
|
||||||
stringResource(Res.string.properties_no_tasks),
|
stringResource(Res.string.properties_no_tasks),
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
fontWeight = FontWeight.SemiBold
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
stringResource(Res.string.properties_add_task_start),
|
stringResource(Res.string.properties_add_task_start),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.textSecondary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -833,25 +956,26 @@ fun ResidenceDetailScreen(
|
|||||||
|
|
||||||
// Contractors Section Header
|
// Contractors Section Header
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.cozy))
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 8.dp),
|
.padding(vertical = OrganicSpacing.compact),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
) {
|
) {
|
||||||
Icon(
|
OrganicIconContainer(
|
||||||
Icons.Default.People,
|
icon = Icons.Default.People,
|
||||||
contentDescription = null,
|
size = 36.dp,
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
iconScale = 0.5f,
|
||||||
modifier = Modifier.size(28.dp)
|
backgroundColor = MaterialTheme.colorScheme.primary,
|
||||||
|
iconColor = MaterialTheme.colorScheme.onPrimary
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.contractors_title),
|
text = stringResource(Res.string.contractors_title),
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = MaterialTheme.colorScheme.primary
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -871,16 +995,15 @@ fun ResidenceDetailScreen(
|
|||||||
}
|
}
|
||||||
is ApiResult.Error -> {
|
is ApiResult.Error -> {
|
||||||
item {
|
item {
|
||||||
Card(
|
OrganicCard(
|
||||||
colors = CardDefaults.cardColors(
|
modifier = Modifier.fillMaxWidth(),
|
||||||
containerColor = MaterialTheme.colorScheme.errorContainer
|
accentColor = MaterialTheme.colorScheme.error,
|
||||||
),
|
showBlob = false
|
||||||
shape = RoundedCornerShape(12.dp)
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Error loading contractors: ${com.example.casera.util.ErrorMessageParser.parse((contractorsState as ApiResult.Error).message)}",
|
text = "Error loading contractors: ${com.example.casera.util.ErrorMessageParser.parse((contractorsState as ApiResult.Error).message)}",
|
||||||
color = MaterialTheme.colorScheme.onErrorContainer,
|
color = MaterialTheme.colorScheme.error,
|
||||||
modifier = Modifier.padding(16.dp)
|
modifier = Modifier.padding(OrganicSpacing.cozy)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -889,32 +1012,35 @@ fun ResidenceDetailScreen(
|
|||||||
val contractors = (contractorsState as ApiResult.Success<List<ContractorSummary>>).data
|
val contractors = (contractorsState as ApiResult.Success<List<ContractorSummary>>).data
|
||||||
if (contractors.isEmpty()) {
|
if (contractors.isEmpty()) {
|
||||||
item {
|
item {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(16.dp)
|
showBlob = true,
|
||||||
|
blobVariation = 1
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(24.dp),
|
.padding(OrganicSpacing.comfortable),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
) {
|
) {
|
||||||
Icon(
|
OrganicIconContainer(
|
||||||
Icons.Default.PersonAdd,
|
icon = Icons.Default.PersonAdd,
|
||||||
contentDescription = null,
|
size = 56.dp,
|
||||||
modifier = Modifier.size(48.dp),
|
iconScale = 0.5f,
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f)
|
backgroundColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.3f),
|
||||||
|
iconColor = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
Text(
|
Text(
|
||||||
stringResource(Res.string.properties_no_contractors),
|
stringResource(Res.string.properties_no_contractors),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.SemiBold
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
stringResource(Res.string.properties_add_contractors_hint),
|
stringResource(Res.string.properties_add_contractors_hint),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.textSecondary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -940,3 +1066,4 @@ fun ResidenceDetailScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import com.example.casera.network.ResidenceApi
|
|||||||
import com.example.casera.storage.TokenStorage
|
import com.example.casera.storage.TokenStorage
|
||||||
import com.example.casera.analytics.PostHogAnalytics
|
import com.example.casera.analytics.PostHogAnalytics
|
||||||
import com.example.casera.analytics.AnalyticsEvents
|
import com.example.casera.analytics.AnalyticsEvents
|
||||||
|
import com.example.casera.ui.theme.*
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
@@ -170,13 +171,14 @@ fun ResidenceFormScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
|
WarmGradientBackground {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
.padding(16.dp)
|
.padding(OrganicSpacing.cozy)
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.cozy)
|
||||||
) {
|
) {
|
||||||
// Basic Information section
|
// Basic Information section
|
||||||
Text(
|
Text(
|
||||||
@@ -279,7 +281,7 @@ fun ResidenceFormScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Optional fields section
|
// Optional fields section
|
||||||
Divider()
|
OrganicDivider()
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.properties_form_optional),
|
text = stringResource(Res.string.properties_form_optional),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
@@ -288,7 +290,7 @@ fun ResidenceFormScreen(
|
|||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = bedrooms,
|
value = bedrooms,
|
||||||
@@ -353,7 +355,7 @@ fun ResidenceFormScreen(
|
|||||||
|
|
||||||
// Users section (edit mode only, owner only)
|
// Users section (edit mode only, owner only)
|
||||||
if (isEditMode && isCurrentUserOwner) {
|
if (isEditMode && isCurrentUserOwner) {
|
||||||
Divider()
|
OrganicDivider()
|
||||||
Text(
|
Text(
|
||||||
text = "Shared Users (${users.size})",
|
text = "Shared Users (${users.size})",
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
@@ -362,7 +364,7 @@ fun ResidenceFormScreen(
|
|||||||
|
|
||||||
if (isLoadingUsers) {
|
if (isLoadingUsers) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxWidth().padding(16.dp),
|
modifier = Modifier.fillMaxWidth().padding(OrganicSpacing.cozy),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
CircularProgressIndicator(modifier = Modifier.size(24.dp))
|
CircularProgressIndicator(modifier = Modifier.size(24.dp))
|
||||||
@@ -372,7 +374,7 @@ fun ResidenceFormScreen(
|
|||||||
text = "No shared users",
|
text = "No shared users",
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.padding(vertical = 8.dp)
|
modifier = Modifier.padding(vertical = OrganicSpacing.compact)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
users.forEach { user ->
|
users.forEach { user ->
|
||||||
@@ -404,7 +406,8 @@ fun ResidenceFormScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Submit button
|
// Submit button
|
||||||
Button(
|
OrganicPrimaryButton(
|
||||||
|
text = if (isEditMode) stringResource(Res.string.properties_form_update) else stringResource(Res.string.properties_form_create),
|
||||||
onClick = {
|
onClick = {
|
||||||
if (validateForm()) {
|
if (validateForm()) {
|
||||||
val request = ResidenceCreateRequest(
|
val request = ResidenceCreateRequest(
|
||||||
@@ -432,20 +435,12 @@ fun ResidenceFormScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
enabled = validateForm(),
|
||||||
enabled = validateForm()
|
isLoading = operationState is ApiResult.Loading
|
||||||
) {
|
|
||||||
if (operationState is ApiResult.Loading) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary
|
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
Text(if (isEditMode) stringResource(Res.string.properties_form_update) else stringResource(Res.string.properties_form_create))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.cozy))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,8 +503,9 @@ private fun UserListItem(
|
|||||||
user: ResidenceUser,
|
user: ResidenceUser,
|
||||||
onRemove: () -> Unit
|
onRemove: () -> Unit
|
||||||
) {
|
) {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)
|
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
|
||||||
|
showBlob = false
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().padding(12.dp),
|
modifier = Modifier.fillMaxWidth().padding(12.dp),
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import com.example.casera.cache.SubscriptionCache
|
|||||||
import com.example.casera.analytics.PostHogAnalytics
|
import com.example.casera.analytics.PostHogAnalytics
|
||||||
import com.example.casera.analytics.AnalyticsEvents
|
import com.example.casera.analytics.AnalyticsEvents
|
||||||
import com.example.casera.data.DataManager
|
import com.example.casera.data.DataManager
|
||||||
|
import com.example.casera.ui.theme.*
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@@ -225,14 +226,14 @@ fun ResidencesScreen(
|
|||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.cozy),
|
||||||
modifier = Modifier.padding(24.dp)
|
modifier = Modifier.padding(OrganicSpacing.comfortable)
|
||||||
) {
|
) {
|
||||||
Icon(
|
OrganicIconContainer(
|
||||||
Icons.Default.Home,
|
icon = Icons.Default.Home,
|
||||||
contentDescription = null,
|
size = 80.dp,
|
||||||
modifier = Modifier.size(80.dp),
|
iconScale = 0.6f,
|
||||||
tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f)
|
backgroundColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
stringResource(Res.string.properties_empty_title),
|
stringResource(Res.string.properties_empty_title),
|
||||||
@@ -244,7 +245,7 @@ fun ResidencesScreen(
|
|||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.compact))
|
||||||
// Only show Add Property button if not blocked (limit>0)
|
// Only show Add Property button if not blocked (limit>0)
|
||||||
if (!isBlocked.allowed) {
|
if (!isBlocked.allowed) {
|
||||||
Button(
|
Button(
|
||||||
@@ -263,7 +264,7 @@ fun ResidencesScreen(
|
|||||||
shape = RoundedCornerShape(12.dp)
|
shape = RoundedCornerShape(12.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.compact),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Add, contentDescription = null)
|
Icon(Icons.Default.Add, contentDescription = null)
|
||||||
@@ -274,7 +275,7 @@ fun ResidencesScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.compact))
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
val (allowed, triggerKey) = canAddProperty()
|
val (allowed, triggerKey) = canAddProperty()
|
||||||
@@ -291,7 +292,7 @@ fun ResidencesScreen(
|
|||||||
shape = RoundedCornerShape(12.dp)
|
shape = RoundedCornerShape(12.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.compact),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.GroupAdd, contentDescription = null)
|
Icon(Icons.Default.GroupAdd, contentDescription = null)
|
||||||
@@ -315,7 +316,7 @@ fun ResidencesScreen(
|
|||||||
shape = RoundedCornerShape(12.dp)
|
shape = RoundedCornerShape(12.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.compact),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Star, contentDescription = null)
|
Icon(Icons.Default.Star, contentDescription = null)
|
||||||
@@ -344,28 +345,27 @@ fun ResidencesScreen(
|
|||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentPadding = PaddingValues(
|
contentPadding = PaddingValues(
|
||||||
start = 16.dp,
|
start = OrganicSpacing.cozy,
|
||||||
end = 16.dp,
|
end = OrganicSpacing.cozy,
|
||||||
top = 16.dp,
|
top = OrganicSpacing.cozy,
|
||||||
bottom = 96.dp
|
bottom = 96.dp
|
||||||
),
|
),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.cozy)
|
||||||
) {
|
) {
|
||||||
// Summary Card
|
// Summary Card
|
||||||
item {
|
item {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = CardDefaults.cardColors(
|
accentColor = MaterialTheme.colorScheme.primary,
|
||||||
containerColor = MaterialTheme.colorScheme.primaryContainer
|
showBlob = true,
|
||||||
),
|
blobVariation = 0,
|
||||||
shape = RoundedCornerShape(20.dp),
|
shadowIntensity = ShadowIntensity.Medium
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(20.dp),
|
.padding(OrganicSpacing.cozy),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.cozy)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
@@ -373,15 +373,15 @@ fun ResidencesScreen(
|
|||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Dashboard,
|
Icons.Default.Dashboard,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.onPrimaryContainer,
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
modifier = Modifier.size(24.dp)
|
modifier = Modifier.size(24.dp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(OrganicSpacing.compact))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.home_overview),
|
text = stringResource(Res.string.home_overview),
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = MaterialTheme.colorScheme.onPrimaryContainer
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,8 +401,8 @@ fun ResidencesScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalDivider(
|
OrganicDivider(
|
||||||
color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.2f)
|
color = MaterialTheme.colorScheme.textSecondary.copy(alpha = 0.2f)
|
||||||
)
|
)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
@@ -436,7 +436,7 @@ fun ResidencesScreen(
|
|||||||
text = stringResource(Res.string.home_your_properties),
|
text = stringResource(Res.string.home_your_properties),
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
modifier = Modifier.padding(top = 8.dp)
|
modifier = Modifier.padding(top = OrganicSpacing.compact)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,46 +456,41 @@ fun ResidencesScreen(
|
|||||||
label = "pulseScale"
|
label = "pulseScale"
|
||||||
)
|
)
|
||||||
|
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable { onResidenceClick(residence.id) },
|
.clickable { onResidenceClick(residence.id) },
|
||||||
shape = MaterialTheme.shapes.large,
|
accentColor = if (hasOverdue) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary,
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp),
|
showBlob = true,
|
||||||
colors = CardDefaults.cardColors(
|
blobVariation = residence.id % 3,
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
shadowIntensity = ShadowIntensity.Subtle
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(20.dp)
|
.padding(OrganicSpacing.cozy)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.cozy),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
// Pulsing circular house icon when overdue
|
// Pulsing organic icon container when overdue
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = if (hasOverdue) Modifier.scale(pulseScale) else Modifier
|
||||||
.size(56.dp)
|
|
||||||
.then(
|
|
||||||
if (hasOverdue) Modifier.scale(pulseScale) else Modifier
|
|
||||||
)
|
|
||||||
.clip(CircleShape)
|
|
||||||
.background(
|
|
||||||
if (hasOverdue) MaterialTheme.colorScheme.errorContainer
|
|
||||||
else MaterialTheme.colorScheme.primaryContainer
|
|
||||||
),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
OrganicIconContainer(
|
||||||
Icons.Default.Home,
|
icon = Icons.Default.Home,
|
||||||
contentDescription = null,
|
size = 56.dp,
|
||||||
tint = if (hasOverdue) MaterialTheme.colorScheme.onErrorContainer
|
iconScale = 0.5f,
|
||||||
else MaterialTheme.colorScheme.onPrimaryContainer,
|
backgroundColor = if (hasOverdue)
|
||||||
modifier = Modifier.size(28.dp)
|
MaterialTheme.colorScheme.errorContainer
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
iconColor = if (hasOverdue)
|
||||||
|
MaterialTheme.colorScheme.onErrorContainer
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,9 +577,9 @@ fun ResidencesScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.cozy))
|
||||||
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant)
|
OrganicDivider(color = MaterialTheme.colorScheme.textSecondary.copy(alpha = 0.15f))
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.cozy))
|
||||||
|
|
||||||
// Fully dynamic task summary from API - show first 3 categories
|
// Fully dynamic task summary from API - show first 3 categories
|
||||||
val displayCategories = residence.taskSummary.categories.take(3)
|
val displayCategories = residence.taskSummary.categories.take(3)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.example.casera.ui.screens
|
package com.example.casera.ui.screens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
@@ -21,6 +22,7 @@ import com.example.casera.viewmodel.TaskViewModel
|
|||||||
import com.example.casera.network.ApiResult
|
import com.example.casera.network.ApiResult
|
||||||
import com.example.casera.analytics.PostHogAnalytics
|
import com.example.casera.analytics.PostHogAnalytics
|
||||||
import com.example.casera.analytics.AnalyticsEvents
|
import com.example.casera.analytics.AnalyticsEvents
|
||||||
|
import com.example.casera.ui.theme.*
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@@ -66,7 +68,9 @@ fun TasksScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WarmGradientBackground {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
containerColor = androidx.compose.ui.graphics.Color.Transparent,
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(Res.string.tasks_title)) },
|
title = { Text(stringResource(Res.string.tasks_title)) },
|
||||||
@@ -74,7 +78,10 @@ fun TasksScreen(
|
|||||||
IconButton(onClick = onNavigateBack) {
|
IconButton(onClick = onNavigateBack) {
|
||||||
Icon(Icons.Default.ArrowBack, contentDescription = stringResource(Res.string.common_back))
|
Icon(Icons.Default.ArrowBack, contentDescription = stringResource(Res.string.common_back))
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = androidx.compose.ui.graphics.Color.Transparent
|
||||||
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
// No FAB on Tasks screen - tasks are added from within residences
|
// No FAB on Tasks screen - tasks are added from within residences
|
||||||
@@ -87,7 +94,9 @@ fun TasksScreen(
|
|||||||
.padding(paddingValues),
|
.padding(paddingValues),
|
||||||
contentAlignment = androidx.compose.ui.Alignment.Center
|
contentAlignment = androidx.compose.ui.Alignment.Center
|
||||||
) {
|
) {
|
||||||
CircularProgressIndicator()
|
CircularProgressIndicator(
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is ApiResult.Success -> {
|
is ApiResult.Success -> {
|
||||||
@@ -103,24 +112,26 @@ fun TasksScreen(
|
|||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally,
|
horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.cozy),
|
||||||
modifier = Modifier.padding(24.dp)
|
modifier = Modifier.padding(OrganicSpacing.comfortable)
|
||||||
) {
|
) {
|
||||||
Icon(
|
OrganicIconContainer(
|
||||||
Icons.Default.Assignment,
|
icon = Icons.Default.Assignment,
|
||||||
contentDescription = null,
|
size = 80.dp,
|
||||||
modifier = Modifier.size(80.dp),
|
iconScale = 0.6f,
|
||||||
tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f)
|
backgroundColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f),
|
||||||
|
iconColor = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
stringResource(Res.string.tasks_empty_title),
|
stringResource(Res.string.tasks_empty_title),
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
fontWeight = androidx.compose.ui.text.font.FontWeight.SemiBold
|
fontWeight = androidx.compose.ui.text.font.FontWeight.SemiBold,
|
||||||
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
stringResource(Res.string.tasks_empty_subtitle),
|
stringResource(Res.string.tasks_empty_subtitle),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.textSecondary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,18 +140,18 @@ fun TasksScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
contentPadding = PaddingValues(
|
contentPadding = PaddingValues(
|
||||||
top = paddingValues.calculateTopPadding() + 16.dp,
|
top = paddingValues.calculateTopPadding() + OrganicSpacing.cozy,
|
||||||
bottom = paddingValues.calculateBottomPadding() + 16.dp,
|
bottom = paddingValues.calculateBottomPadding() + OrganicSpacing.cozy,
|
||||||
start = 16.dp,
|
start = OrganicSpacing.cozy,
|
||||||
end = 16.dp
|
end = OrganicSpacing.cozy
|
||||||
),
|
),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
) {
|
) {
|
||||||
// Task summary pills - dynamically generated from all columns
|
// Task summary pills - dynamically generated from all columns
|
||||||
item {
|
item {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
) {
|
) {
|
||||||
taskData.columns.forEach { column ->
|
taskData.columns.forEach { column ->
|
||||||
TaskPill(
|
TaskPill(
|
||||||
@@ -162,7 +173,8 @@ fun TasksScreen(
|
|||||||
Text(
|
Text(
|
||||||
text = "${column.displayName} (${column.tasks.size})",
|
text = "${column.displayName} (${column.tasks.size})",
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
modifier = Modifier.padding(top = 8.dp)
|
color = MaterialTheme.colorScheme.textPrimary,
|
||||||
|
modifier = Modifier.padding(top = OrganicSpacing.compact)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,40 +195,47 @@ fun TasksScreen(
|
|||||||
val isExpanded = expandedColumns.contains(column.name)
|
val isExpanded = expandedColumns.contains(column.name)
|
||||||
|
|
||||||
item {
|
item {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier
|
||||||
onClick = {
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
expandedColumns = if (isExpanded) {
|
expandedColumns = if (isExpanded) {
|
||||||
expandedColumns - column.name
|
expandedColumns - column.name
|
||||||
} else {
|
} else {
|
||||||
expandedColumns + column.name
|
expandedColumns + column.name
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
showBlob = false,
|
||||||
|
shadowIntensity = ShadowIntensity.Subtle
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp),
|
.padding(OrganicSpacing.cozy),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
|
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.compact),
|
||||||
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
|
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
OrganicIconContainer(
|
||||||
getIconFromName(column.icons["android"] ?: "List"),
|
icon = getIconFromName(column.icons["android"] ?: "List"),
|
||||||
contentDescription = null,
|
size = 40.dp,
|
||||||
tint = hexToColor(column.color)
|
iconScale = 0.5f,
|
||||||
|
backgroundColor = hexToColor(column.color).copy(alpha = 0.2f),
|
||||||
|
iconColor = hexToColor(column.color)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "${column.displayName} (${column.tasks.size})",
|
text = "${column.displayName} (${column.tasks.size})",
|
||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Icon(
|
Icon(
|
||||||
if (isExpanded) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown,
|
if (isExpanded) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown,
|
||||||
contentDescription = if (isExpanded) "Collapse" else "Expand"
|
contentDescription = if (isExpanded) "Collapse" else "Expand",
|
||||||
|
tint = MaterialTheme.colorScheme.textSecondary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,6 +265,7 @@ fun TasksScreen(
|
|||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (showCompleteDialog && selectedTask != null) {
|
if (showCompleteDialog && selectedTask != null) {
|
||||||
CompleteTaskDialog(
|
CompleteTaskDialog(
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.example.casera.ui.screens
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -21,6 +20,7 @@ import com.example.casera.ui.components.auth.AuthHeader
|
|||||||
import com.example.casera.ui.components.common.ErrorCard
|
import com.example.casera.ui.components.common.ErrorCard
|
||||||
import com.example.casera.viewmodel.AuthViewModel
|
import com.example.casera.viewmodel.AuthViewModel
|
||||||
import com.example.casera.network.ApiResult
|
import com.example.casera.network.ApiResult
|
||||||
|
import com.example.casera.ui.theme.*
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@@ -89,46 +89,62 @@ fun VerifyEmailScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
|
WarmGradientBackground {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(24.dp),
|
.padding(OrganicSpacing.comfortable),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(20.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.cozy)
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.compact))
|
||||||
|
|
||||||
AuthHeader(
|
OrganicIconContainer(
|
||||||
icon = Icons.Default.MarkEmailRead,
|
icon = Icons.Default.MarkEmailRead,
|
||||||
title = stringResource(Res.string.auth_verify_title),
|
size = 80.dp,
|
||||||
subtitle = stringResource(Res.string.auth_verify_subtitle)
|
iconScale = 0.5f,
|
||||||
|
backgroundColor = MaterialTheme.colorScheme.primary,
|
||||||
|
iconColor = MaterialTheme.colorScheme.onPrimary
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Text(
|
||||||
|
text = stringResource(Res.string.auth_verify_title),
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.textPrimary,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
Card(
|
Text(
|
||||||
|
text = stringResource(Res.string.auth_verify_subtitle),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.textSecondary,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(OrganicSpacing.cozy))
|
||||||
|
|
||||||
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = CardDefaults.cardColors(
|
accentColor = MaterialTheme.colorScheme.error,
|
||||||
containerColor = MaterialTheme.colorScheme.errorContainer
|
showBlob = false
|
||||||
),
|
|
||||||
shape = RoundedCornerShape(12.dp)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(OrganicSpacing.cozy),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Info,
|
Icons.Default.Info,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.onErrorContainer
|
tint = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "Email verification is required. Check your inbox for a 6-digit code.",
|
text = "Email verification is required. Check your inbox for a 6-digit code.",
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onErrorContainer,
|
color = MaterialTheme.colorScheme.textPrimary,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.SemiBold
|
fontWeight = FontWeight.SemiBold
|
||||||
)
|
)
|
||||||
@@ -148,7 +164,6 @@ fun VerifyEmailScreen(
|
|||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
shape = RoundedCornerShape(12.dp),
|
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||||
placeholder = { Text("000000") }
|
placeholder = { Text("000000") }
|
||||||
)
|
)
|
||||||
@@ -159,7 +174,12 @@ fun VerifyEmailScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(
|
OrganicDivider(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
OrganicPrimaryButton(
|
||||||
|
text = stringResource(Res.string.auth_verify_button),
|
||||||
onClick = {
|
onClick = {
|
||||||
if (code.length == 6) {
|
if (code.length == 6) {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
@@ -168,40 +188,19 @@ fun VerifyEmailScreen(
|
|||||||
errorMessage = "Please enter a valid 6-digit code"
|
errorMessage = "Please enter a valid 6-digit code"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
enabled = !isLoading && code.length == 6,
|
||||||
.fillMaxWidth()
|
isLoading = isLoading
|
||||||
.height(56.dp),
|
|
||||||
shape = RoundedCornerShape(12.dp),
|
|
||||||
enabled = !isLoading && code.length == 6
|
|
||||||
) {
|
|
||||||
if (isLoading) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary
|
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Icon(Icons.Default.CheckCircle, contentDescription = null)
|
|
||||||
Text(
|
|
||||||
stringResource(Res.string.auth_verify_button),
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.compact))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Didn't receive the code? Check your spam folder or contact support.",
|
text = "Didn't receive the code? Check your spam folder or contact support.",
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.textSecondary,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.example.casera.ui.screens
|
package com.example.casera.ui.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
@@ -19,6 +17,7 @@ import com.example.casera.ui.components.auth.AuthHeader
|
|||||||
import com.example.casera.ui.components.common.ErrorCard
|
import com.example.casera.ui.components.common.ErrorCard
|
||||||
import com.example.casera.viewmodel.PasswordResetViewModel
|
import com.example.casera.viewmodel.PasswordResetViewModel
|
||||||
import com.example.casera.network.ApiResult
|
import com.example.casera.network.ApiResult
|
||||||
|
import com.example.casera.ui.theme.*
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@@ -70,69 +69,85 @@ fun VerifyResetCodeScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
|
WarmGradientBackground {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.padding(paddingValues),
|
.padding(paddingValues),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(0.9f)
|
.fillMaxWidth(0.9f)
|
||||||
.wrapContentHeight(),
|
.wrapContentHeight(),
|
||||||
shape = RoundedCornerShape(24.dp),
|
showBlob = true,
|
||||||
colors = CardDefaults.cardColors(
|
blobVariation = 1
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
|
||||||
),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(32.dp),
|
.padding(OrganicSpacing.spacious),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(20.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.cozy)
|
||||||
) {
|
) {
|
||||||
AuthHeader(
|
OrganicIconContainer(
|
||||||
icon = Icons.Default.MarkEmailRead,
|
icon = Icons.Default.MarkEmailRead,
|
||||||
title = "Check Your Email",
|
size = 80.dp,
|
||||||
subtitle = "We sent a 6-digit code to"
|
iconScale = 0.5f,
|
||||||
|
backgroundColor = MaterialTheme.colorScheme.primary,
|
||||||
|
iconColor = MaterialTheme.colorScheme.onPrimary
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Check Your Email",
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.textPrimary,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "We sent a 6-digit code to",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.textSecondary,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
email,
|
email,
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = MaterialTheme.colorScheme.textPrimary,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
|
|
||||||
Card(
|
OrganicCard(
|
||||||
colors = CardDefaults.cardColors(
|
modifier = Modifier.fillMaxWidth(),
|
||||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
accentColor = MaterialTheme.colorScheme.secondary,
|
||||||
)
|
showBlob = false
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp),
|
.padding(OrganicSpacing.cozy),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Timer,
|
Icons.Default.Timer,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.secondary
|
tint = MaterialTheme.colorScheme.secondary
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
|
||||||
Text(
|
Text(
|
||||||
"Code expires in 15 minutes",
|
"Code expires in 15 minutes",
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
fontWeight = FontWeight.SemiBold
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(OrganicSpacing.compact))
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = code,
|
value = code,
|
||||||
@@ -147,7 +162,6 @@ fun VerifyResetCodeScreen(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||||
shape = RoundedCornerShape(12.dp),
|
|
||||||
enabled = !isLoading,
|
enabled = !isLoading,
|
||||||
textStyle = MaterialTheme.typography.headlineMedium.copy(
|
textStyle = MaterialTheme.typography.headlineMedium.copy(
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
@@ -158,75 +172,60 @@ fun VerifyResetCodeScreen(
|
|||||||
Text(
|
Text(
|
||||||
"Enter the 6-digit code from your email",
|
"Enter the 6-digit code from your email",
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.textSecondary,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
|
|
||||||
ErrorCard(message = errorMessage)
|
ErrorCard(message = errorMessage)
|
||||||
|
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = CardDefaults.cardColors(
|
accentColor = MaterialTheme.colorScheme.primary,
|
||||||
containerColor = MaterialTheme.colorScheme.primaryContainer
|
showBlob = false
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp),
|
.padding(OrganicSpacing.cozy),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.CheckCircle,
|
Icons.Default.CheckCircle,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.primary
|
tint = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
|
||||||
Text(
|
Text(
|
||||||
"Code verified! Now set your new password",
|
"Code verified! Now set your new password",
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onPrimaryContainer
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(
|
OrganicDivider(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
OrganicPrimaryButton(
|
||||||
|
text = stringResource(Res.string.auth_verify_button),
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.verifyResetCode(email, code)
|
viewModel.verifyResetCode(email, code)
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(56.dp),
|
|
||||||
enabled = code.length == 6 && !isLoading,
|
enabled = code.length == 6 && !isLoading,
|
||||||
shape = RoundedCornerShape(12.dp)
|
isLoading = isLoading
|
||||||
) {
|
|
||||||
if (isLoading) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
|
||||||
strokeWidth = 2.dp
|
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
Icon(Icons.Default.CheckCircle, contentDescription = null)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(
|
|
||||||
stringResource(Res.string.auth_verify_button),
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
"Didn't receive the code?",
|
"Didn't receive the code?",
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.textSecondary
|
||||||
)
|
)
|
||||||
|
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
@@ -245,7 +244,7 @@ fun VerifyResetCodeScreen(
|
|||||||
Text(
|
Text(
|
||||||
"Check your spam folder if you don't see it",
|
"Check your spam folder if you don't see it",
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.textSecondary,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -254,3 +253,4 @@ fun VerifyResetCodeScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,10 +5,8 @@ import androidx.compose.animation.expandVertically
|
|||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.shrinkVertically
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -17,15 +15,12 @@ import androidx.compose.material3.*
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
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.Brush
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.example.casera.network.ApiResult
|
import com.example.casera.network.ApiResult
|
||||||
import com.example.casera.ui.theme.AppRadius
|
import com.example.casera.ui.theme.*
|
||||||
import com.example.casera.ui.theme.AppSpacing
|
|
||||||
import com.example.casera.viewmodel.OnboardingViewModel
|
import com.example.casera.viewmodel.OnboardingViewModel
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
@@ -65,35 +60,30 @@ fun OnboardingCreateAccountContent(
|
|||||||
password.isNotBlank() &&
|
password.isNotBlank() &&
|
||||||
password == confirmPassword
|
password == confirmPassword
|
||||||
|
|
||||||
|
WarmGradientBackground(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(horizontal = AppSpacing.xl)
|
.padding(horizontal = OrganicSpacing.xl)
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl))
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.sm)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.sm)
|
||||||
) {
|
) {
|
||||||
// Icon
|
// Icon
|
||||||
Box(
|
OrganicIconContainer(
|
||||||
modifier = Modifier
|
icon = Icons.Default.PersonAdd,
|
||||||
.size(80.dp)
|
size = 80.dp,
|
||||||
.clip(CircleShape)
|
iconSize = 40.dp,
|
||||||
.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)),
|
contentDescription = null
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.PersonAdd,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(40.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.onboarding_create_account_title),
|
text = stringResource(Res.string.onboarding_create_account_title),
|
||||||
@@ -111,7 +101,7 @@ fun OnboardingCreateAccountContent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl * 2))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl * 2))
|
||||||
|
|
||||||
// Create with Email section
|
// Create with Email section
|
||||||
if (!isFormExpanded) {
|
if (!isFormExpanded) {
|
||||||
@@ -121,14 +111,14 @@ fun OnboardingCreateAccountContent(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(56.dp),
|
.height(56.dp),
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
shape = RoundedCornerShape(OrganicRadius.md),
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f),
|
containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f),
|
||||||
contentColor = MaterialTheme.colorScheme.primary
|
contentColor = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Email, contentDescription = null)
|
Icon(Icons.Default.Email, contentDescription = null)
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
Spacer(modifier = Modifier.width(OrganicSpacing.sm))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.onboarding_create_with_email),
|
text = stringResource(Res.string.onboarding_create_with_email),
|
||||||
fontWeight = FontWeight.Medium
|
fontWeight = FontWeight.Medium
|
||||||
@@ -143,7 +133,7 @@ fun OnboardingCreateAccountContent(
|
|||||||
exit = fadeOut() + shrinkVertically()
|
exit = fadeOut() + shrinkVertically()
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.md)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
// Username
|
// Username
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
@@ -158,7 +148,7 @@ fun OnboardingCreateAccountContent(
|
|||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
shape = RoundedCornerShape(OrganicRadius.md),
|
||||||
enabled = !isLoading
|
enabled = !isLoading
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -175,7 +165,7 @@ fun OnboardingCreateAccountContent(
|
|||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
shape = RoundedCornerShape(OrganicRadius.md),
|
||||||
enabled = !isLoading
|
enabled = !isLoading
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -193,7 +183,7 @@ fun OnboardingCreateAccountContent(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
visualTransformation = PasswordVisualTransformation(),
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
shape = RoundedCornerShape(OrganicRadius.md),
|
||||||
enabled = !isLoading
|
enabled = !isLoading
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -211,7 +201,7 @@ fun OnboardingCreateAccountContent(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
visualTransformation = PasswordVisualTransformation(),
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
shape = RoundedCornerShape(OrganicRadius.md),
|
||||||
enabled = !isLoading,
|
enabled = !isLoading,
|
||||||
isError = confirmPassword.isNotEmpty() && password != confirmPassword,
|
isError = confirmPassword.isNotEmpty() && password != confirmPassword,
|
||||||
supportingText = if (confirmPassword.isNotEmpty() && password != confirmPassword) {
|
supportingText = if (confirmPassword.isNotEmpty() && password != confirmPassword) {
|
||||||
@@ -221,16 +211,14 @@ fun OnboardingCreateAccountContent(
|
|||||||
|
|
||||||
// Error message
|
// Error message
|
||||||
if (localErrorMessage != null) {
|
if (localErrorMessage != null) {
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = CardDefaults.cardColors(
|
accentColor = MaterialTheme.colorScheme.error,
|
||||||
containerColor = MaterialTheme.colorScheme.errorContainer
|
showBlob = false
|
||||||
),
|
|
||||||
shape = RoundedCornerShape(AppRadius.md)
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(AppSpacing.md),
|
modifier = Modifier.padding(OrganicSpacing.md),
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.sm),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.sm),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -247,10 +235,11 @@ fun OnboardingCreateAccountContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.sm))
|
Spacer(modifier = Modifier.height(OrganicSpacing.sm))
|
||||||
|
|
||||||
// Create Account button
|
// Create Account button
|
||||||
Button(
|
OrganicPrimaryButton(
|
||||||
|
text = stringResource(Res.string.auth_register_button),
|
||||||
onClick = {
|
onClick = {
|
||||||
if (password == confirmPassword) {
|
if (password == confirmPassword) {
|
||||||
viewModel.register(username, email, password)
|
viewModel.register(username, email, password)
|
||||||
@@ -258,30 +247,14 @@ fun OnboardingCreateAccountContent(
|
|||||||
localErrorMessage = "Passwords don't match"
|
localErrorMessage = "Passwords don't match"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.fillMaxWidth()
|
enabled = isFormValid && !isLoading,
|
||||||
.height(56.dp),
|
isLoading = isLoading
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
|
||||||
enabled = isFormValid && !isLoading
|
|
||||||
) {
|
|
||||||
if (isLoading) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
|
||||||
strokeWidth = 2.dp
|
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
text = stringResource(Res.string.auth_register_button),
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl))
|
||||||
|
|
||||||
// Already have an account
|
// Already have an account
|
||||||
Row(
|
Row(
|
||||||
@@ -303,7 +276,8 @@ fun OnboardingCreateAccountContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl * 2))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl * 2))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login dialog
|
// Login dialog
|
||||||
@@ -356,7 +330,7 @@ private fun OnboardingLoginDialog(
|
|||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.md)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = username,
|
value = username,
|
||||||
@@ -364,7 +338,7 @@ private fun OnboardingLoginDialog(
|
|||||||
label = { Text(stringResource(Res.string.auth_login_username_label)) },
|
label = { Text(stringResource(Res.string.auth_login_username_label)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
shape = RoundedCornerShape(OrganicRadius.md),
|
||||||
enabled = !isLoading
|
enabled = !isLoading
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -374,7 +348,7 @@ private fun OnboardingLoginDialog(
|
|||||||
label = { Text(stringResource(Res.string.auth_login_password_label)) },
|
label = { Text(stringResource(Res.string.auth_login_password_label)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
shape = RoundedCornerShape(OrganicRadius.md),
|
||||||
visualTransformation = PasswordVisualTransformation(),
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
enabled = !isLoading
|
enabled = !isLoading
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import com.example.casera.data.DataManager
|
import com.example.casera.data.DataManager
|
||||||
import com.example.casera.models.TaskCreateRequest
|
import com.example.casera.models.TaskCreateRequest
|
||||||
import com.example.casera.network.ApiResult
|
import com.example.casera.network.ApiResult
|
||||||
import com.example.casera.ui.theme.AppRadius
|
import com.example.casera.ui.theme.*
|
||||||
import com.example.casera.ui.theme.AppSpacing
|
|
||||||
import com.example.casera.viewmodel.OnboardingViewModel
|
import com.example.casera.viewmodel.OnboardingViewModel
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import com.example.casera.util.DateUtils
|
import com.example.casera.util.DateUtils
|
||||||
@@ -163,7 +162,7 @@ fun OnboardingFirstTaskContent(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f),
|
.weight(1f),
|
||||||
contentPadding = PaddingValues(horizontal = AppSpacing.lg, vertical = AppSpacing.md)
|
contentPadding = PaddingValues(horizontal = OrganicSpacing.lg, vertical = OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
// Header
|
// Header
|
||||||
item {
|
item {
|
||||||
@@ -171,30 +170,19 @@ fun OnboardingFirstTaskContent(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
// Celebration icon
|
// Celebration icon using OrganicIconContainer
|
||||||
Box(
|
OrganicIconContainer(
|
||||||
modifier = Modifier
|
icon = Icons.Default.Celebration,
|
||||||
.size(80.dp)
|
size = 80.dp,
|
||||||
.clip(CircleShape)
|
iconSize = 40.dp,
|
||||||
.background(
|
gradientColors = listOf(
|
||||||
Brush.linearGradient(
|
|
||||||
colors = listOf(
|
|
||||||
MaterialTheme.colorScheme.primary,
|
MaterialTheme.colorScheme.primary,
|
||||||
MaterialTheme.colorScheme.secondary
|
MaterialTheme.colorScheme.secondary
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
contentAlignment = Alignment.Center
|
contentDescription = null
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Celebration,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(40.dp),
|
|
||||||
tint = Color.White
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.lg))
|
Spacer(modifier = Modifier.height(OrganicSpacing.lg))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.onboarding_tasks_title),
|
text = stringResource(Res.string.onboarding_tasks_title),
|
||||||
@@ -203,7 +191,7 @@ fun OnboardingFirstTaskContent(
|
|||||||
color = MaterialTheme.colorScheme.onBackground
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.sm))
|
Spacer(modifier = Modifier.height(OrganicSpacing.sm))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.onboarding_tasks_subtitle),
|
text = stringResource(Res.string.onboarding_tasks_subtitle),
|
||||||
@@ -212,11 +200,11 @@ fun OnboardingFirstTaskContent(
|
|||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.lg))
|
Spacer(modifier = Modifier.height(OrganicSpacing.lg))
|
||||||
|
|
||||||
// Selection counter
|
// Selection counter
|
||||||
Surface(
|
Surface(
|
||||||
shape = RoundedCornerShape(AppRadius.xl),
|
shape = RoundedCornerShape(OrganicRadius.xl),
|
||||||
color = if (isAtMaxSelection) {
|
color = if (isAtMaxSelection) {
|
||||||
MaterialTheme.colorScheme.tertiary.copy(alpha = 0.1f)
|
MaterialTheme.colorScheme.tertiary.copy(alpha = 0.1f)
|
||||||
} else {
|
} else {
|
||||||
@@ -224,8 +212,8 @@ fun OnboardingFirstTaskContent(
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(horizontal = AppSpacing.lg, vertical = AppSpacing.sm),
|
modifier = Modifier.padding(horizontal = OrganicSpacing.lg, vertical = OrganicSpacing.sm),
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.sm),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.sm),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -243,7 +231,7 @@ fun OnboardingFirstTaskContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,7 +255,7 @@ fun OnboardingFirstTaskContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.md))
|
Spacer(modifier = Modifier.height(OrganicSpacing.md))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add popular tasks button
|
// Add popular tasks button
|
||||||
@@ -291,7 +279,7 @@ fun OnboardingFirstTaskContent(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(56.dp),
|
.height(56.dp),
|
||||||
shape = RoundedCornerShape(AppRadius.lg),
|
shape = RoundedCornerShape(OrganicRadius.lg),
|
||||||
border = ButtonDefaults.outlinedButtonBorder.copy(
|
border = ButtonDefaults.outlinedButtonBorder.copy(
|
||||||
brush = Brush.linearGradient(
|
brush = Brush.linearGradient(
|
||||||
colors = listOf(
|
colors = listOf(
|
||||||
@@ -302,7 +290,7 @@ fun OnboardingFirstTaskContent(
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.AutoAwesome, contentDescription = null)
|
Icon(Icons.Default.AutoAwesome, contentDescription = null)
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
Spacer(modifier = Modifier.width(OrganicSpacing.sm))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.onboarding_tasks_add_popular),
|
text = stringResource(Res.string.onboarding_tasks_add_popular),
|
||||||
fontWeight = FontWeight.Medium
|
fontWeight = FontWeight.Medium
|
||||||
@@ -318,9 +306,14 @@ fun OnboardingFirstTaskContent(
|
|||||||
shadowElevation = 8.dp
|
shadowElevation = 8.dp
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(AppSpacing.lg)
|
modifier = Modifier.padding(OrganicSpacing.lg)
|
||||||
) {
|
) {
|
||||||
Button(
|
OrganicPrimaryButton(
|
||||||
|
text = if (selectedCount > 0) {
|
||||||
|
"Add $selectedCount Task${if (selectedCount == 1) "" else "s"} & Continue"
|
||||||
|
} else {
|
||||||
|
stringResource(Res.string.onboarding_tasks_skip)
|
||||||
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
if (selectedTaskIds.isEmpty()) {
|
if (selectedTaskIds.isEmpty()) {
|
||||||
onTasksAdded()
|
onTasksAdded()
|
||||||
@@ -360,32 +353,11 @@ fun OnboardingFirstTaskContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.fillMaxWidth()
|
enabled = !isCreatingTasks,
|
||||||
.height(56.dp),
|
isLoading = isCreatingTasks,
|
||||||
shape = RoundedCornerShape(AppRadius.lg),
|
icon = Icons.Default.ArrowForward
|
||||||
enabled = !isCreatingTasks
|
|
||||||
) {
|
|
||||||
if (isCreatingTasks) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
|
||||||
strokeWidth = 2.dp
|
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
text = if (selectedCount > 0) {
|
|
||||||
"Add $selectedCount Task${if (selectedCount == 1) "" else "s"} & Continue"
|
|
||||||
} else {
|
|
||||||
stringResource(Res.string.onboarding_tasks_skip)
|
|
||||||
},
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
|
||||||
Icon(Icons.Default.ArrowForward, contentDescription = null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -402,39 +374,32 @@ private fun TaskCategorySection(
|
|||||||
) {
|
) {
|
||||||
val selectedInCategory = category.tasks.count { it.id in selectedTaskIds }
|
val selectedInCategory = category.tasks.count { it.id in selectedTaskIds }
|
||||||
|
|
||||||
|
OrganicCard(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
accentColor = category.color,
|
||||||
|
showBlob = false
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth()
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(RoundedCornerShape(AppRadius.lg))
|
|
||||||
) {
|
) {
|
||||||
// Header
|
// Header
|
||||||
Surface(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable { onToggleExpand() },
|
.clickable { onToggleExpand() }
|
||||||
color = MaterialTheme.colorScheme.surfaceVariant
|
.padding(OrganicSpacing.md),
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.padding(AppSpacing.md),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
// Category icon
|
// Category icon
|
||||||
Box(
|
OrganicIconContainer(
|
||||||
modifier = Modifier
|
icon = category.icon,
|
||||||
.size(44.dp)
|
size = 44.dp,
|
||||||
.clip(CircleShape)
|
iconSize = 24.dp,
|
||||||
.background(category.color),
|
gradientColors = listOf(category.color),
|
||||||
contentAlignment = Alignment.Center
|
contentDescription = null
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = category.icon,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = Color.White,
|
|
||||||
modifier = Modifier.size(24.dp)
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.md))
|
Spacer(modifier = Modifier.width(OrganicSpacing.md))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = category.name,
|
text = category.name,
|
||||||
@@ -459,7 +424,7 @@ private fun TaskCategorySection(
|
|||||||
color = Color.White
|
color = Color.White
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
Spacer(modifier = Modifier.width(OrganicSpacing.sm))
|
||||||
}
|
}
|
||||||
|
|
||||||
Icon(
|
Icon(
|
||||||
@@ -468,7 +433,6 @@ private fun TaskCategorySection(
|
|||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Expanded content
|
// Expanded content
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
@@ -477,9 +441,7 @@ private fun TaskCategorySection(
|
|||||||
exit = fadeOut() + shrinkVertically()
|
exit = fadeOut() + shrinkVertically()
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth()
|
||||||
.fillMaxWidth()
|
|
||||||
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f))
|
|
||||||
) {
|
) {
|
||||||
category.tasks.forEachIndexed { index, task ->
|
category.tasks.forEachIndexed { index, task ->
|
||||||
val isSelected = task.id in selectedTaskIds
|
val isSelected = task.id in selectedTaskIds
|
||||||
@@ -494,7 +456,7 @@ private fun TaskCategorySection(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (index < category.tasks.lastIndex) {
|
if (index < category.tasks.lastIndex) {
|
||||||
Divider(
|
OrganicDivider(
|
||||||
modifier = Modifier.padding(start = 60.dp),
|
modifier = Modifier.padding(start = 60.dp),
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.2f)
|
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.2f)
|
||||||
)
|
)
|
||||||
@@ -504,6 +466,7 @@ private fun TaskCategorySection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun TaskTemplateRow(
|
private fun TaskTemplateRow(
|
||||||
@@ -517,7 +480,7 @@ private fun TaskTemplateRow(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(enabled = !isDisabled) { onClick() }
|
.clickable(enabled = !isDisabled) { onClick() }
|
||||||
.padding(horizontal = AppSpacing.md, vertical = AppSpacing.sm),
|
.padding(horizontal = OrganicSpacing.md, vertical = OrganicSpacing.sm),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
// Checkbox
|
// Checkbox
|
||||||
@@ -541,7 +504,7 @@ private fun TaskTemplateRow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.md))
|
Spacer(modifier = Modifier.width(OrganicSpacing.md))
|
||||||
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.example.casera.ui.screens.onboarding
|
package com.example.casera.ui.screens.onboarding
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -11,14 +9,12 @@ import androidx.compose.material3.*
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
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.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.example.casera.network.ApiResult
|
import com.example.casera.network.ApiResult
|
||||||
import com.example.casera.ui.theme.AppRadius
|
import com.example.casera.ui.theme.*
|
||||||
import com.example.casera.ui.theme.AppSpacing
|
|
||||||
import com.example.casera.viewmodel.OnboardingViewModel
|
import com.example.casera.viewmodel.OnboardingViewModel
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
@@ -48,10 +44,13 @@ fun OnboardingJoinResidenceContent(
|
|||||||
val isLoading = joinState is ApiResult.Loading
|
val isLoading = joinState is ApiResult.Loading
|
||||||
val isCodeValid = shareCode.length == 6
|
val isCodeValid = shareCode.length == 6
|
||||||
|
|
||||||
|
WarmGradientBackground(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(horizontal = AppSpacing.xl),
|
.padding(horizontal = OrganicSpacing.xl),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
@@ -59,28 +58,20 @@ fun OnboardingJoinResidenceContent(
|
|||||||
// Header
|
// Header
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.lg)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.lg)
|
||||||
) {
|
) {
|
||||||
// Icon
|
// Icon
|
||||||
Box(
|
OrganicIconContainer(
|
||||||
modifier = Modifier
|
icon = Icons.Default.GroupAdd,
|
||||||
.size(100.dp)
|
size = 100.dp,
|
||||||
.clip(CircleShape)
|
iconSize = 50.dp,
|
||||||
.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)),
|
contentDescription = null
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.GroupAdd,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(50.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// Title and subtitle
|
// Title and subtitle
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.sm)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.sm)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.onboarding_join_title),
|
text = stringResource(Res.string.onboarding_join_title),
|
||||||
@@ -98,7 +89,7 @@ fun OnboardingJoinResidenceContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl * 2))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl * 2))
|
||||||
|
|
||||||
// Share code input
|
// Share code input
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
@@ -123,7 +114,7 @@ fun OnboardingJoinResidenceContent(
|
|||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
),
|
),
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
shape = RoundedCornerShape(OrganicRadius.md),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
capitalization = KeyboardCapitalization.Characters
|
capitalization = KeyboardCapitalization.Characters
|
||||||
@@ -133,17 +124,15 @@ fun OnboardingJoinResidenceContent(
|
|||||||
|
|
||||||
// Error message
|
// Error message
|
||||||
if (localErrorMessage != null) {
|
if (localErrorMessage != null) {
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.md))
|
Spacer(modifier = Modifier.height(OrganicSpacing.md))
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = CardDefaults.cardColors(
|
accentColor = MaterialTheme.colorScheme.error,
|
||||||
containerColor = MaterialTheme.colorScheme.errorContainer
|
showBlob = false
|
||||||
),
|
|
||||||
shape = RoundedCornerShape(AppRadius.md)
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(AppSpacing.md),
|
modifier = Modifier.padding(OrganicSpacing.md),
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.sm),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.sm),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -162,9 +151,9 @@ fun OnboardingJoinResidenceContent(
|
|||||||
|
|
||||||
// Loading indicator
|
// Loading indicator
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.md))
|
Spacer(modifier = Modifier.height(OrganicSpacing.md))
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.sm),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.sm),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
@@ -182,29 +171,15 @@ fun OnboardingJoinResidenceContent(
|
|||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
// Join button
|
// Join button
|
||||||
Button(
|
OrganicPrimaryButton(
|
||||||
onClick = { viewModel.joinResidence(shareCode) },
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(56.dp),
|
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
|
||||||
enabled = isCodeValid && !isLoading
|
|
||||||
) {
|
|
||||||
if (isLoading) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
|
||||||
strokeWidth = 2.dp
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
text = stringResource(Res.string.onboarding_join_button),
|
text = stringResource(Res.string.onboarding_join_button),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
onClick = { viewModel.joinResidence(shareCode) },
|
||||||
fontWeight = FontWeight.SemiBold
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
enabled = isCodeValid && !isLoading,
|
||||||
|
isLoading = isLoading
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl * 2))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl * 2))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.example.casera.ui.screens.onboarding
|
package com.example.casera.ui.screens.onboarding
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowForward
|
import androidx.compose.material.icons.filled.ArrowForward
|
||||||
@@ -11,14 +9,10 @@ import androidx.compose.material3.*
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
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.draw.shadow
|
|
||||||
import androidx.compose.ui.graphics.Brush
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.example.casera.ui.theme.AppRadius
|
import com.example.casera.ui.theme.*
|
||||||
import com.example.casera.ui.theme.AppSpacing
|
|
||||||
import com.example.casera.viewmodel.OnboardingViewModel
|
import com.example.casera.viewmodel.OnboardingViewModel
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
@@ -31,10 +25,13 @@ fun OnboardingNameResidenceContent(
|
|||||||
val residenceName by viewModel.residenceName.collectAsState()
|
val residenceName by viewModel.residenceName.collectAsState()
|
||||||
var localName by remember { mutableStateOf(residenceName) }
|
var localName by remember { mutableStateOf(residenceName) }
|
||||||
|
|
||||||
|
WarmGradientBackground(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(horizontal = AppSpacing.xl),
|
.padding(horizontal = OrganicSpacing.xl),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
@@ -42,40 +39,20 @@ fun OnboardingNameResidenceContent(
|
|||||||
// Header with icon
|
// Header with icon
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.lg)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.lg)
|
||||||
) {
|
) {
|
||||||
// Icon with gradient background
|
// Icon with OrganicIconContainer
|
||||||
Box(
|
OrganicIconContainer(
|
||||||
modifier = Modifier
|
icon = Icons.Default.Home,
|
||||||
.size(100.dp)
|
size = 100.dp,
|
||||||
.shadow(
|
iconSize = 50.dp,
|
||||||
elevation = 16.dp,
|
contentDescription = null
|
||||||
shape = CircleShape,
|
|
||||||
spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.4f)
|
|
||||||
)
|
)
|
||||||
.clip(CircleShape)
|
|
||||||
.background(
|
|
||||||
Brush.linearGradient(
|
|
||||||
colors = listOf(
|
|
||||||
MaterialTheme.colorScheme.primary,
|
|
||||||
MaterialTheme.colorScheme.primary.copy(alpha = 0.8f)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Home,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(50.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.onPrimary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Title and subtitle
|
// Title and subtitle
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.sm)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.sm)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.onboarding_name_residence_title),
|
text = stringResource(Res.string.onboarding_name_residence_title),
|
||||||
@@ -93,7 +70,7 @@ fun OnboardingNameResidenceContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl * 2))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl * 2))
|
||||||
|
|
||||||
// Name input
|
// Name input
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
@@ -106,7 +83,7 @@ fun OnboardingNameResidenceContent(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
shape = RoundedCornerShape(OrganicRadius.md),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
colors = OutlinedTextFieldDefaults.colors(
|
colors = OutlinedTextFieldDefaults.colors(
|
||||||
focusedBorderColor = MaterialTheme.colorScheme.primary,
|
focusedBorderColor = MaterialTheme.colorScheme.primary,
|
||||||
@@ -114,7 +91,7 @@ fun OnboardingNameResidenceContent(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.sm))
|
Spacer(modifier = Modifier.height(OrganicSpacing.sm))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.onboarding_name_residence_hint),
|
text = stringResource(Res.string.onboarding_name_residence_hint),
|
||||||
@@ -125,26 +102,18 @@ fun OnboardingNameResidenceContent(
|
|||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
// Continue button
|
// Continue button
|
||||||
Button(
|
OrganicPrimaryButton(
|
||||||
|
text = stringResource(Res.string.onboarding_continue),
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.setResidenceName(localName)
|
viewModel.setResidenceName(localName)
|
||||||
onContinue()
|
onContinue()
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.fillMaxWidth()
|
enabled = localName.isNotBlank(),
|
||||||
.height(56.dp),
|
icon = Icons.Default.ArrowForward
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
|
||||||
enabled = localName.isNotBlank()
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(Res.string.onboarding_continue),
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
|
||||||
Icon(Icons.Default.ArrowForward, contentDescription = null)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl * 2))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl * 2))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.example.casera.ui.theme.AppSpacing
|
import com.example.casera.ui.theme.*
|
||||||
import com.example.casera.viewmodel.OnboardingStep
|
import com.example.casera.viewmodel.OnboardingStep
|
||||||
import com.example.casera.viewmodel.OnboardingViewModel
|
import com.example.casera.viewmodel.OnboardingViewModel
|
||||||
import com.example.casera.viewmodel.OnboardingIntent
|
import com.example.casera.viewmodel.OnboardingIntent
|
||||||
@@ -189,7 +189,7 @@ private fun OnboardingNavigationBar(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = AppSpacing.lg, vertical = AppSpacing.md),
|
.padding(horizontal = OrganicSpacing.lg, vertical = OrganicSpacing.md),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
// Back button
|
// Back button
|
||||||
@@ -240,7 +240,7 @@ fun OnboardingProgressIndicator(
|
|||||||
totalSteps: Int
|
totalSteps: Int
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.xs),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.xs),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
repeat(totalSteps) { index ->
|
repeat(totalSteps) { index ->
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.example.casera.ui.theme.AppRadius
|
import com.example.casera.ui.theme.*
|
||||||
import com.example.casera.ui.theme.AppSpacing
|
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@@ -117,16 +116,19 @@ fun OnboardingSubscriptionContent(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
WarmGradientBackground(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(horizontal = AppSpacing.xl),
|
modifier = Modifier.padding(horizontal = OrganicSpacing.xl),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.lg))
|
Spacer(modifier = Modifier.height(OrganicSpacing.lg))
|
||||||
|
|
||||||
// Crown header with animation
|
// Crown header with animation
|
||||||
Box(
|
Box(
|
||||||
@@ -149,38 +151,27 @@ fun OnboardingSubscriptionContent(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Crown icon
|
// Crown icon using OrganicIconContainer
|
||||||
Box(
|
OrganicIconContainer(
|
||||||
modifier = Modifier
|
icon = Icons.Default.EmojiEvents,
|
||||||
.size(100.dp)
|
size = 100.dp,
|
||||||
.clip(CircleShape)
|
iconSize = 50.dp,
|
||||||
.background(
|
gradientColors = listOf(
|
||||||
Brush.linearGradient(
|
|
||||||
colors = listOf(
|
|
||||||
MaterialTheme.colorScheme.tertiary,
|
MaterialTheme.colorScheme.tertiary,
|
||||||
Color(0xFFFF9500)
|
Color(0xFFFF9500)
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
contentAlignment = Alignment.Center
|
contentDescription = null
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.EmojiEvents,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(50.dp),
|
|
||||||
tint = Color.White
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// PRO badge
|
// PRO badge
|
||||||
Surface(
|
Surface(
|
||||||
shape = RoundedCornerShape(AppRadius.full),
|
shape = RoundedCornerShape(OrganicRadius.full),
|
||||||
color = MaterialTheme.colorScheme.tertiary.copy(alpha = 0.15f)
|
color = MaterialTheme.colorScheme.tertiary.copy(alpha = 0.15f)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(horizontal = AppSpacing.lg, vertical = AppSpacing.sm),
|
modifier = Modifier.padding(horizontal = OrganicSpacing.lg, vertical = OrganicSpacing.sm),
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.xs),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.xs),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -204,7 +195,7 @@ fun OnboardingSubscriptionContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.md))
|
Spacer(modifier = Modifier.height(OrganicSpacing.md))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.onboarding_subscription_subtitle),
|
text = stringResource(Res.string.onboarding_subscription_subtitle),
|
||||||
@@ -214,11 +205,11 @@ fun OnboardingSubscriptionContent(
|
|||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.sm))
|
Spacer(modifier = Modifier.height(OrganicSpacing.sm))
|
||||||
|
|
||||||
// Social proof
|
// Social proof
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.xs),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.xs),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
repeat(5) {
|
repeat(5) {
|
||||||
@@ -236,18 +227,18 @@ fun OnboardingSubscriptionContent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl))
|
||||||
|
|
||||||
// Benefits list
|
// Benefits list
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.sm)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.sm)
|
||||||
) {
|
) {
|
||||||
benefits.forEach { benefit ->
|
benefits.forEach { benefit ->
|
||||||
BenefitRow(benefit = benefit)
|
BenefitRow(benefit = benefit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl))
|
||||||
|
|
||||||
// Plan selection
|
// Plan selection
|
||||||
Text(
|
Text(
|
||||||
@@ -257,7 +248,7 @@ fun OnboardingSubscriptionContent(
|
|||||||
color = MaterialTheme.colorScheme.onBackground
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.md))
|
Spacer(modifier = Modifier.height(OrganicSpacing.md))
|
||||||
|
|
||||||
// Yearly plan
|
// Yearly plan
|
||||||
PlanCard(
|
PlanCard(
|
||||||
@@ -266,7 +257,7 @@ fun OnboardingSubscriptionContent(
|
|||||||
onClick = { selectedPlan = SubscriptionPlan.YEARLY }
|
onClick = { selectedPlan = SubscriptionPlan.YEARLY }
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.sm))
|
Spacer(modifier = Modifier.height(OrganicSpacing.sm))
|
||||||
|
|
||||||
// Monthly plan
|
// Monthly plan
|
||||||
PlanCard(
|
PlanCard(
|
||||||
@@ -275,42 +266,23 @@ fun OnboardingSubscriptionContent(
|
|||||||
onClick = { selectedPlan = SubscriptionPlan.MONTHLY }
|
onClick = { selectedPlan = SubscriptionPlan.MONTHLY }
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl))
|
||||||
|
|
||||||
// Start trial button
|
// Start trial button
|
||||||
Button(
|
OrganicPrimaryButton(
|
||||||
|
text = stringResource(Res.string.onboarding_subscription_start_trial),
|
||||||
onClick = {
|
onClick = {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
// Simulate subscription flow
|
// Simulate subscription flow
|
||||||
onSubscribe()
|
onSubscribe()
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.fillMaxWidth()
|
enabled = !isLoading,
|
||||||
.height(56.dp),
|
isLoading = isLoading,
|
||||||
shape = RoundedCornerShape(AppRadius.lg),
|
icon = Icons.Default.ArrowForward
|
||||||
colors = ButtonDefaults.buttonColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.tertiary
|
|
||||||
),
|
|
||||||
enabled = !isLoading
|
|
||||||
) {
|
|
||||||
if (isLoading) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onTertiary,
|
|
||||||
strokeWidth = 2.dp
|
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
text = stringResource(Res.string.onboarding_subscription_start_trial),
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
|
||||||
Icon(Icons.Default.ArrowForward, contentDescription = null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.md))
|
Spacer(modifier = Modifier.height(OrganicSpacing.md))
|
||||||
|
|
||||||
// Continue free button
|
// Continue free button
|
||||||
TextButton(onClick = onSkip) {
|
TextButton(onClick = onSkip) {
|
||||||
@@ -322,7 +294,7 @@ fun OnboardingSubscriptionContent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.sm))
|
Spacer(modifier = Modifier.height(OrganicSpacing.sm))
|
||||||
|
|
||||||
// Legal text
|
// Legal text
|
||||||
Text(
|
Text(
|
||||||
@@ -337,7 +309,8 @@ fun OnboardingSubscriptionContent(
|
|||||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl * 2))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl * 2))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,28 +320,19 @@ private fun BenefitRow(benefit: SubscriptionBenefit) {
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = AppSpacing.md, vertical = AppSpacing.sm),
|
.padding(horizontal = OrganicSpacing.md, vertical = OrganicSpacing.sm),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
// Gradient icon
|
// Gradient icon using OrganicIconContainer
|
||||||
Box(
|
OrganicIconContainer(
|
||||||
modifier = Modifier
|
icon = benefit.icon,
|
||||||
.size(44.dp)
|
size = 44.dp,
|
||||||
.clip(CircleShape)
|
iconSize = 24.dp,
|
||||||
.background(
|
gradientColors = benefit.gradientColors,
|
||||||
Brush.linearGradient(benefit.gradientColors)
|
contentDescription = null
|
||||||
),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = benefit.icon,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
tint = Color.White
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.md))
|
Spacer(modifier = Modifier.width(OrganicSpacing.md))
|
||||||
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Text(
|
||||||
@@ -402,26 +366,15 @@ private fun PlanCard(
|
|||||||
) {
|
) {
|
||||||
val isYearly = plan == SubscriptionPlan.YEARLY
|
val isYearly = plan == SubscriptionPlan.YEARLY
|
||||||
|
|
||||||
Surface(
|
OrganicCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable { onClick() },
|
.clickable { onClick() },
|
||||||
shape = RoundedCornerShape(AppRadius.lg),
|
accentColor = if (isSelected) MaterialTheme.colorScheme.tertiary else MaterialTheme.colorScheme.surfaceVariant,
|
||||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
showBlob = isSelected
|
||||||
border = if (isSelected) {
|
|
||||||
ButtonDefaults.outlinedButtonBorder.copy(
|
|
||||||
brush = Brush.linearGradient(
|
|
||||||
colors = listOf(
|
|
||||||
MaterialTheme.colorScheme.tertiary,
|
|
||||||
Color(0xFFFF9500)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
width = 2.dp
|
|
||||||
)
|
|
||||||
} else null
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(AppSpacing.lg),
|
modifier = Modifier.padding(OrganicSpacing.lg),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
// Selection indicator
|
// Selection indicator
|
||||||
@@ -446,11 +399,11 @@ private fun PlanCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.md))
|
Spacer(modifier = Modifier.width(OrganicSpacing.md))
|
||||||
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.sm),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.sm),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@@ -466,7 +419,7 @@ private fun PlanCard(
|
|||||||
|
|
||||||
if (isYearly) {
|
if (isYearly) {
|
||||||
Surface(
|
Surface(
|
||||||
shape = RoundedCornerShape(AppRadius.full),
|
shape = RoundedCornerShape(OrganicRadius.full),
|
||||||
color = Color(0xFF34C759)
|
color = Color(0xFF34C759)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@@ -474,7 +427,7 @@ private fun PlanCard(
|
|||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
modifier = Modifier.padding(horizontal = AppSpacing.sm, vertical = 2.dp)
|
modifier = Modifier.padding(horizontal = OrganicSpacing.sm, vertical = 2.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.example.casera.ui.theme.AppRadius
|
import com.example.casera.ui.theme.*
|
||||||
import com.example.casera.ui.theme.AppSpacing
|
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
@@ -111,10 +110,13 @@ fun OnboardingValuePropsContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WarmGradientBackground(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(horizontal = AppSpacing.xl),
|
.padding(horizontal = OrganicSpacing.xl),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.weight(0.5f))
|
Spacer(modifier = Modifier.weight(0.5f))
|
||||||
@@ -129,11 +131,11 @@ fun OnboardingValuePropsContent(
|
|||||||
FeatureCard(feature = features[page])
|
FeatureCard(feature = features[page])
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl))
|
||||||
|
|
||||||
// Page indicators
|
// Page indicators
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.sm),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.sm),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
repeat(features.size) { index ->
|
repeat(features.size) { index ->
|
||||||
@@ -155,23 +157,15 @@ fun OnboardingValuePropsContent(
|
|||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
// Continue button
|
// Continue button
|
||||||
Button(
|
OrganicPrimaryButton(
|
||||||
onClick = onContinue,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(56.dp),
|
|
||||||
shape = RoundedCornerShape(AppRadius.md)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(Res.string.onboarding_continue),
|
text = stringResource(Res.string.onboarding_continue),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
onClick = onContinue,
|
||||||
fontWeight = FontWeight.SemiBold
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
icon = Icons.Default.ArrowForward
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
|
||||||
Icon(Icons.Default.ArrowForward, contentDescription = null)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl * 2))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl * 2))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,29 +174,20 @@ private fun FeatureCard(feature: FeatureItem) {
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = AppSpacing.md),
|
.padding(horizontal = OrganicSpacing.md),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
// Icon with gradient background
|
// Icon with gradient background using OrganicIconContainer
|
||||||
Box(
|
OrganicIconContainer(
|
||||||
modifier = Modifier
|
icon = feature.icon,
|
||||||
.size(120.dp)
|
size = 120.dp,
|
||||||
.clip(CircleShape)
|
iconSize = 60.dp,
|
||||||
.background(
|
gradientColors = feature.gradientColors,
|
||||||
Brush.linearGradient(feature.gradientColors)
|
contentDescription = null
|
||||||
),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = feature.icon,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(60.dp),
|
|
||||||
tint = Color.White
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl * 2))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl * 2))
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
Text(
|
Text(
|
||||||
@@ -213,7 +198,7 @@ private fun FeatureCard(feature: FeatureItem) {
|
|||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.md))
|
Spacer(modifier = Modifier.height(OrganicSpacing.md))
|
||||||
|
|
||||||
// Description
|
// Description
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.example.casera.ui.screens.onboarding
|
package com.example.casera.ui.screens.onboarding
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -11,15 +9,12 @@ import androidx.compose.material3.*
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
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.Brush
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.example.casera.network.ApiResult
|
import com.example.casera.network.ApiResult
|
||||||
import com.example.casera.ui.theme.AppRadius
|
import com.example.casera.ui.theme.*
|
||||||
import com.example.casera.ui.theme.AppSpacing
|
|
||||||
import com.example.casera.viewmodel.OnboardingViewModel
|
import com.example.casera.viewmodel.OnboardingViewModel
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
@@ -56,10 +51,13 @@ fun OnboardingVerifyEmailContent(
|
|||||||
|
|
||||||
val isLoading = verifyState is ApiResult.Loading
|
val isLoading = verifyState is ApiResult.Loading
|
||||||
|
|
||||||
|
WarmGradientBackground(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(horizontal = AppSpacing.xl),
|
.padding(horizontal = OrganicSpacing.xl),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
@@ -67,28 +65,20 @@ fun OnboardingVerifyEmailContent(
|
|||||||
// Header
|
// Header
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.lg)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.lg)
|
||||||
) {
|
) {
|
||||||
// Icon with gradient background
|
// Icon with OrganicIconContainer
|
||||||
Box(
|
OrganicIconContainer(
|
||||||
modifier = Modifier
|
icon = Icons.Default.MarkEmailRead,
|
||||||
.size(100.dp)
|
size = 100.dp,
|
||||||
.clip(CircleShape)
|
iconSize = 50.dp,
|
||||||
.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)),
|
contentDescription = null
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.MarkEmailRead,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(50.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// Title and subtitle
|
// Title and subtitle
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.sm)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.sm)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.onboarding_verify_email_title),
|
text = stringResource(Res.string.onboarding_verify_email_title),
|
||||||
@@ -106,7 +96,7 @@ fun OnboardingVerifyEmailContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl * 2))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl * 2))
|
||||||
|
|
||||||
// Code input
|
// Code input
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
@@ -133,7 +123,7 @@ fun OnboardingVerifyEmailContent(
|
|||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
),
|
),
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
shape = RoundedCornerShape(OrganicRadius.md),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||||
enabled = !isLoading
|
enabled = !isLoading
|
||||||
@@ -141,17 +131,15 @@ fun OnboardingVerifyEmailContent(
|
|||||||
|
|
||||||
// Error message
|
// Error message
|
||||||
if (localErrorMessage != null) {
|
if (localErrorMessage != null) {
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.md))
|
Spacer(modifier = Modifier.height(OrganicSpacing.md))
|
||||||
Card(
|
OrganicCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = CardDefaults.cardColors(
|
accentColor = MaterialTheme.colorScheme.error,
|
||||||
containerColor = MaterialTheme.colorScheme.errorContainer
|
showBlob = false
|
||||||
),
|
|
||||||
shape = RoundedCornerShape(AppRadius.md)
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(AppSpacing.md),
|
modifier = Modifier.padding(OrganicSpacing.md),
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.sm),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.sm),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -170,9 +158,9 @@ fun OnboardingVerifyEmailContent(
|
|||||||
|
|
||||||
// Loading indicator
|
// Loading indicator
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.md))
|
Spacer(modifier = Modifier.height(OrganicSpacing.md))
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(AppSpacing.sm),
|
horizontalArrangement = Arrangement.spacedBy(OrganicSpacing.sm),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
@@ -187,7 +175,7 @@ fun OnboardingVerifyEmailContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.lg))
|
Spacer(modifier = Modifier.height(OrganicSpacing.lg))
|
||||||
|
|
||||||
// Hint text
|
// Hint text
|
||||||
Text(
|
Text(
|
||||||
@@ -200,29 +188,15 @@ fun OnboardingVerifyEmailContent(
|
|||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
// Verify button
|
// Verify button
|
||||||
Button(
|
OrganicPrimaryButton(
|
||||||
onClick = { viewModel.verifyEmail(code) },
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(56.dp),
|
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
|
||||||
enabled = code.length == 6 && !isLoading
|
|
||||||
) {
|
|
||||||
if (isLoading) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
|
||||||
strokeWidth = 2.dp
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
text = stringResource(Res.string.auth_verify_button),
|
text = stringResource(Res.string.auth_verify_button),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
onClick = { viewModel.verifyEmail(code) },
|
||||||
fontWeight = FontWeight.SemiBold
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
enabled = code.length == 6 && !isLoading,
|
||||||
|
isLoading = isLoading
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl * 2))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl * 2))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.example.casera.ui.screens.onboarding
|
package com.example.casera.ui.screens.onboarding
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -11,16 +10,11 @@ import androidx.compose.material3.*
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
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.draw.shadow
|
|
||||||
import androidx.compose.ui.graphics.Brush
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.example.casera.ui.theme.AppRadius
|
import com.example.casera.ui.theme.*
|
||||||
import com.example.casera.ui.theme.AppSpacing
|
|
||||||
import com.example.casera.viewmodel.AuthViewModel
|
import com.example.casera.viewmodel.AuthViewModel
|
||||||
import com.example.casera.network.ApiResult
|
import com.example.casera.network.ApiResult
|
||||||
import casera.composeapp.generated.resources.*
|
import casera.composeapp.generated.resources.*
|
||||||
@@ -35,10 +29,13 @@ fun OnboardingWelcomeContent(
|
|||||||
) {
|
) {
|
||||||
var showLoginDialog by remember { mutableStateOf(false) }
|
var showLoginDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
WarmGradientBackground(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(horizontal = AppSpacing.xl),
|
.padding(horizontal = OrganicSpacing.xl),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
@@ -47,34 +44,20 @@ fun OnboardingWelcomeContent(
|
|||||||
// Hero section
|
// Hero section
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.xl)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.xl)
|
||||||
) {
|
) {
|
||||||
// App icon with shadow
|
// App icon with OrganicIconContainer
|
||||||
Box(
|
OrganicIconContainer(
|
||||||
modifier = Modifier
|
icon = Icons.Default.Home,
|
||||||
.size(120.dp)
|
size = 120.dp,
|
||||||
.shadow(
|
iconSize = 80.dp,
|
||||||
elevation = 20.dp,
|
contentDescription = null
|
||||||
shape = RoundedCornerShape(AppRadius.xxl),
|
|
||||||
spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.3f)
|
|
||||||
)
|
)
|
||||||
.clip(RoundedCornerShape(AppRadius.xxl))
|
|
||||||
.background(MaterialTheme.colorScheme.surface)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Home,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(80.dp)
|
|
||||||
.align(Alignment.Center),
|
|
||||||
tint = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Welcome text
|
// Welcome text
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.sm)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.sm)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.onboarding_welcome_title),
|
text = stringResource(Res.string.onboarding_welcome_title),
|
||||||
@@ -97,31 +80,15 @@ fun OnboardingWelcomeContent(
|
|||||||
// Action buttons
|
// Action buttons
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.md)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
// Primary CTA - Start Fresh
|
// Primary CTA - Start Fresh
|
||||||
Button(
|
OrganicPrimaryButton(
|
||||||
onClick = onStartFresh,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(56.dp),
|
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
|
||||||
colors = ButtonDefaults.buttonColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Home,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(24.dp)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
|
||||||
Text(
|
|
||||||
text = stringResource(Res.string.onboarding_start_fresh),
|
text = stringResource(Res.string.onboarding_start_fresh),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
onClick = onStartFresh,
|
||||||
fontWeight = FontWeight.SemiBold
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
icon = Icons.Default.Home
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// Secondary CTA - Join Existing
|
// Secondary CTA - Join Existing
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
@@ -129,7 +96,7 @@ fun OnboardingWelcomeContent(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(56.dp),
|
.height(56.dp),
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
shape = RoundedCornerShape(OrganicRadius.md),
|
||||||
colors = ButtonDefaults.outlinedButtonColors(
|
colors = ButtonDefaults.outlinedButtonColors(
|
||||||
contentColor = MaterialTheme.colorScheme.primary
|
contentColor = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
@@ -139,7 +106,7 @@ fun OnboardingWelcomeContent(
|
|||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(24.dp)
|
modifier = Modifier.size(24.dp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
Spacer(modifier = Modifier.width(OrganicSpacing.sm))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.onboarding_join_existing),
|
text = stringResource(Res.string.onboarding_join_existing),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
@@ -160,7 +127,8 @@ fun OnboardingWelcomeContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.xl * 2))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl * 2))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login dialog
|
// Login dialog
|
||||||
@@ -212,7 +180,7 @@ private fun LoginDialog(
|
|||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(AppSpacing.md)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.md)
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = username,
|
value = username,
|
||||||
@@ -220,7 +188,7 @@ private fun LoginDialog(
|
|||||||
label = { Text(stringResource(Res.string.auth_login_username_label)) },
|
label = { Text(stringResource(Res.string.auth_login_username_label)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
shape = RoundedCornerShape(OrganicRadius.md),
|
||||||
enabled = !isLoading
|
enabled = !isLoading
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -230,7 +198,7 @@ private fun LoginDialog(
|
|||||||
label = { Text(stringResource(Res.string.auth_login_password_label)) },
|
label = { Text(stringResource(Res.string.auth_login_password_label)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(AppRadius.md),
|
shape = RoundedCornerShape(OrganicRadius.md),
|
||||||
visualTransformation = androidx.compose.ui.text.input.PasswordVisualTransformation(),
|
visualTransformation = androidx.compose.ui.text.input.PasswordVisualTransformation(),
|
||||||
enabled = !isLoading
|
enabled = !isLoading
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,745 @@
|
|||||||
|
package com.example.casera.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.*
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Eco
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.blur
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.drawBehind
|
||||||
|
import androidx.compose.ui.draw.rotate
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
|
import androidx.compose.ui.graphics.*
|
||||||
|
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||||
|
import androidx.compose.ui.graphics.drawscope.clipRect
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.sin
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
// MARK: - Organic Design System
|
||||||
|
// Warm, natural aesthetic with soft shapes, subtle textures, and flowing layouts
|
||||||
|
|
||||||
|
// MARK: - Organic Shapes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Soft organic blob shape for backgrounds
|
||||||
|
* Matches iOS OrganicBlobShape
|
||||||
|
*/
|
||||||
|
class OrganicBlobShape(private val variation: Int = 0) : Shape {
|
||||||
|
override fun createOutline(
|
||||||
|
size: Size,
|
||||||
|
layoutDirection: androidx.compose.ui.unit.LayoutDirection,
|
||||||
|
density: androidx.compose.ui.unit.Density
|
||||||
|
): Outline {
|
||||||
|
val path = Path()
|
||||||
|
val w = size.width
|
||||||
|
val h = size.height
|
||||||
|
|
||||||
|
when (variation % 3) {
|
||||||
|
0 -> {
|
||||||
|
// Soft cloud-like blob
|
||||||
|
path.moveTo(w * 0.1f, h * 0.5f)
|
||||||
|
path.cubicTo(
|
||||||
|
w * 0.0f, h * 0.1f,
|
||||||
|
w * 0.25f, h * 0.0f,
|
||||||
|
w * 0.5f, h * 0.05f
|
||||||
|
)
|
||||||
|
path.cubicTo(
|
||||||
|
w * 0.75f, h * 0.1f,
|
||||||
|
w * 1.0f, h * 0.25f,
|
||||||
|
w * 0.95f, h * 0.45f
|
||||||
|
)
|
||||||
|
path.cubicTo(
|
||||||
|
w * 0.9f, h * 0.7f,
|
||||||
|
w * 0.8f, h * 0.95f,
|
||||||
|
w * 0.55f, h * 0.95f
|
||||||
|
)
|
||||||
|
path.cubicTo(
|
||||||
|
w * 0.25f, h * 0.95f,
|
||||||
|
w * 0.05f, h * 0.75f,
|
||||||
|
w * 0.1f, h * 0.5f
|
||||||
|
)
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
// Pebble shape
|
||||||
|
path.moveTo(w * 0.15f, h * 0.4f)
|
||||||
|
path.cubicTo(
|
||||||
|
w * 0.1f, h * 0.15f,
|
||||||
|
w * 0.35f, h * 0.05f,
|
||||||
|
w * 0.6f, h * 0.08f
|
||||||
|
)
|
||||||
|
path.cubicTo(
|
||||||
|
w * 0.85f, h * 0.12f,
|
||||||
|
w * 0.95f, h * 0.35f,
|
||||||
|
w * 0.9f, h * 0.55f
|
||||||
|
)
|
||||||
|
path.cubicTo(
|
||||||
|
w * 0.85f, h * 0.8f,
|
||||||
|
w * 0.65f, h * 0.95f,
|
||||||
|
w * 0.45f, h * 0.92f
|
||||||
|
)
|
||||||
|
path.cubicTo(
|
||||||
|
w * 0.2f, h * 0.88f,
|
||||||
|
w * 0.08f, h * 0.65f,
|
||||||
|
w * 0.15f, h * 0.4f
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// Leaf-like shape
|
||||||
|
path.moveTo(w * 0.05f, h * 0.5f)
|
||||||
|
path.cubicTo(
|
||||||
|
w * 0.05f, h * 0.2f,
|
||||||
|
w * 0.25f, h * 0.02f,
|
||||||
|
w * 0.5f, h * 0.02f
|
||||||
|
)
|
||||||
|
path.cubicTo(
|
||||||
|
w * 0.75f, h * 0.02f,
|
||||||
|
w * 0.95f, h * 0.2f,
|
||||||
|
w * 0.95f, h * 0.5f
|
||||||
|
)
|
||||||
|
path.cubicTo(
|
||||||
|
w * 0.95f, h * 0.8f,
|
||||||
|
w * 0.75f, h * 0.98f,
|
||||||
|
w * 0.5f, h * 0.98f
|
||||||
|
)
|
||||||
|
path.cubicTo(
|
||||||
|
w * 0.25f, h * 0.98f,
|
||||||
|
w * 0.05f, h * 0.8f,
|
||||||
|
w * 0.05f, h * 0.5f
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path.close()
|
||||||
|
return Outline.Generic(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Super soft rounded rectangle for organic cards
|
||||||
|
* Uses continuous corner radius matching iOS .continuous style
|
||||||
|
*/
|
||||||
|
val OrganicRoundedShape = RoundedCornerShape(28.dp)
|
||||||
|
|
||||||
|
// MARK: - Grain Texture Overlay
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grain texture overlay for natural feel
|
||||||
|
* Matches iOS GrainTexture
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun GrainTexture(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
opacity: Float = 0.03f
|
||||||
|
) {
|
||||||
|
val grainColor = Color.Black.copy(alpha = opacity)
|
||||||
|
|
||||||
|
Canvas(modifier = modifier.fillMaxSize()) {
|
||||||
|
val density = size.width * size.height / 50
|
||||||
|
val random = Random(42) // Fixed seed for consistent pattern
|
||||||
|
|
||||||
|
repeat(density.toInt().coerceAtMost(5000)) {
|
||||||
|
val x = random.nextFloat() * size.width
|
||||||
|
val y = random.nextFloat() * size.height
|
||||||
|
val grainOpacity = random.nextFloat() * 0.7f + 0.3f
|
||||||
|
|
||||||
|
drawCircle(
|
||||||
|
color = grainColor.copy(alpha = grainColor.alpha * grainOpacity),
|
||||||
|
radius = 0.5f,
|
||||||
|
center = Offset(x, y)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Organic Card Background
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Organic card background with subtle accent blob and grain texture
|
||||||
|
* Matches iOS OrganicCardBackground
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun OrganicCardBackground(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
accentColor: Color = MaterialTheme.colorScheme.primary,
|
||||||
|
showBlob: Boolean = true,
|
||||||
|
blobVariation: Int = 0
|
||||||
|
) {
|
||||||
|
Box(modifier = modifier) {
|
||||||
|
// Main card fill
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.clip(OrganicRoundedShape)
|
||||||
|
.background(MaterialTheme.colorScheme.backgroundSecondary)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Subtle accent blob in corner
|
||||||
|
if (showBlob) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.clip(OrganicRoundedShape)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(0.6f)
|
||||||
|
.fillMaxHeight(0.7f)
|
||||||
|
.offset(x = 80.dp, y = (-20).dp)
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.clip(OrganicBlobShape(blobVariation))
|
||||||
|
.background(
|
||||||
|
brush = Brush.linearGradient(
|
||||||
|
colors = listOf(
|
||||||
|
accentColor.copy(alpha = 0.08f),
|
||||||
|
accentColor.copy(alpha = 0.02f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.blur(20.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grain texture
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.clip(OrganicRoundedShape)
|
||||||
|
) {
|
||||||
|
GrainTexture(opacity = 0.015f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Natural Shadow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shadow intensity levels matching iOS NaturalShadow
|
||||||
|
*/
|
||||||
|
enum class ShadowIntensity {
|
||||||
|
Subtle,
|
||||||
|
Medium,
|
||||||
|
Pronounced;
|
||||||
|
|
||||||
|
val elevation: Dp
|
||||||
|
get() = when (this) {
|
||||||
|
Subtle -> 4.dp
|
||||||
|
Medium -> 8.dp
|
||||||
|
Pronounced -> 12.dp
|
||||||
|
}
|
||||||
|
|
||||||
|
val ambientAlpha: Float
|
||||||
|
get() = when (this) {
|
||||||
|
Subtle -> 0.04f
|
||||||
|
Medium -> 0.08f
|
||||||
|
Pronounced -> 0.12f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Natural shadow modifier matching iOS NaturalShadow
|
||||||
|
*/
|
||||||
|
fun Modifier.naturalShadow(
|
||||||
|
intensity: ShadowIntensity = ShadowIntensity.Medium,
|
||||||
|
shape: Shape = OrganicRoundedShape
|
||||||
|
): Modifier = this
|
||||||
|
.shadow(
|
||||||
|
elevation = intensity.elevation,
|
||||||
|
shape = shape,
|
||||||
|
ambientColor = Color.Black.copy(alpha = intensity.ambientAlpha),
|
||||||
|
spotColor = Color.Black.copy(alpha = intensity.ambientAlpha * 0.5f)
|
||||||
|
)
|
||||||
|
|
||||||
|
// MARK: - Organic Icon Container
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon with soft organic background and inner glow
|
||||||
|
* Matches iOS OrganicIconContainer
|
||||||
|
*
|
||||||
|
* @param icon The icon to display (ImageVector)
|
||||||
|
* @param modifier Modifier for the container
|
||||||
|
* @param size The size of the container (default 48.dp)
|
||||||
|
* @param iconScale Scale of the icon relative to container (default 0.5f)
|
||||||
|
* @param iconSize Optional explicit icon size (overrides iconScale if provided)
|
||||||
|
* @param backgroundColor Background color for the container
|
||||||
|
* @param iconColor Tint color for the icon
|
||||||
|
* @param containerColor Alias for backgroundColor (for compatibility)
|
||||||
|
* @param iconTint Alias for iconColor (for compatibility)
|
||||||
|
* @param gradientColors Optional custom gradient colors (overrides backgroundColor)
|
||||||
|
* @param contentDescription Accessibility description
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun OrganicIconContainer(
|
||||||
|
icon: ImageVector,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
size: Dp = 48.dp,
|
||||||
|
iconScale: Float = 0.5f,
|
||||||
|
iconSize: Dp? = null,
|
||||||
|
backgroundColor: Color? = null,
|
||||||
|
iconColor: Color? = null,
|
||||||
|
containerColor: Color? = null,
|
||||||
|
iconTint: Color? = null,
|
||||||
|
gradientColors: List<Color>? = null,
|
||||||
|
contentDescription: String? = null
|
||||||
|
) {
|
||||||
|
// Resolve colors with fallback chain: explicit param -> alias -> default
|
||||||
|
val resolvedBackgroundColor = backgroundColor ?: containerColor ?: MaterialTheme.colorScheme.primary
|
||||||
|
val resolvedIconColor = iconColor ?: iconTint ?: MaterialTheme.colorScheme.onPrimary
|
||||||
|
|
||||||
|
val actualGradientColors = gradientColors ?: listOf(
|
||||||
|
resolvedBackgroundColor,
|
||||||
|
resolvedBackgroundColor.copy(alpha = 0.85f)
|
||||||
|
)
|
||||||
|
val actualIconSize = iconSize ?: (size * iconScale)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.size(size)
|
||||||
|
.naturalShadow(ShadowIntensity.Subtle, CircleShape),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
// Soft organic background with radial gradient
|
||||||
|
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||||
|
drawCircle(
|
||||||
|
brush = Brush.radialGradient(
|
||||||
|
colors = actualGradientColors,
|
||||||
|
center = Offset(this.size.width * 0.3f, this.size.height * 0.3f),
|
||||||
|
radius = this.size.maxDimension
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inner glow
|
||||||
|
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||||
|
drawCircle(
|
||||||
|
brush = Brush.radialGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color.White.copy(alpha = 0.2f),
|
||||||
|
Color.Transparent
|
||||||
|
),
|
||||||
|
center = Offset(this.size.width * 0.3f, this.size.height * 0.3f),
|
||||||
|
radius = this.size.maxDimension * 0.5f
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
modifier = Modifier.size(actualIconSize),
|
||||||
|
tint = resolvedIconColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Organic Stat Pill
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stat display pill with icon and label
|
||||||
|
* Matches iOS OrganicStatPill
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun OrganicStatPill(
|
||||||
|
icon: ImageVector,
|
||||||
|
value: String,
|
||||||
|
label: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
color: Color = MaterialTheme.colorScheme.primary
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.background(
|
||||||
|
color = MaterialTheme.colorScheme.backgroundSecondary,
|
||||||
|
shape = RoundedCornerShape(50)
|
||||||
|
)
|
||||||
|
.drawBehind {
|
||||||
|
// Border
|
||||||
|
drawRoundRect(
|
||||||
|
color = color.copy(alpha = 0.15f),
|
||||||
|
style = androidx.compose.ui.graphics.drawscope.Stroke(width = 1.dp.toPx()),
|
||||||
|
cornerRadius = androidx.compose.ui.geometry.CornerRadius(50.dp.toPx())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.padding(horizontal = 14.dp, vertical = 10.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
// Icon with soft background
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(32.dp)
|
||||||
|
.background(
|
||||||
|
color = color.copy(alpha = 0.12f),
|
||||||
|
shape = CircleShape
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(13.dp),
|
||||||
|
tint = color
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(1.dp)) {
|
||||||
|
Text(
|
||||||
|
text = value,
|
||||||
|
style = MaterialTheme.typography.labelLarge.copy(
|
||||||
|
fontSize = 15.sp,
|
||||||
|
fontWeight = androidx.compose.ui.text.font.FontWeight.Bold
|
||||||
|
),
|
||||||
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
style = MaterialTheme.typography.labelSmall.copy(
|
||||||
|
fontSize = 11.sp,
|
||||||
|
fontWeight = androidx.compose.ui.text.font.FontWeight.Medium
|
||||||
|
),
|
||||||
|
color = MaterialTheme.colorScheme.textSecondary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Organic Divider
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gradient divider that fades at edges
|
||||||
|
* Matches iOS OrganicDivider
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun OrganicDivider(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
color: Color = MaterialTheme.colorScheme.textSecondary.copy(alpha = 0.15f),
|
||||||
|
height: Dp = 1.dp,
|
||||||
|
horizontalPadding: Dp = 0.dp,
|
||||||
|
vertical: Boolean = false
|
||||||
|
) {
|
||||||
|
if (vertical) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.width(1.dp)
|
||||||
|
.background(
|
||||||
|
brush = Brush.verticalGradient(
|
||||||
|
colors = listOf(
|
||||||
|
color.copy(alpha = 0f),
|
||||||
|
color,
|
||||||
|
color,
|
||||||
|
color.copy(alpha = 0f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.padding(horizontal = horizontalPadding)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(height)
|
||||||
|
.background(
|
||||||
|
brush = Brush.horizontalGradient(
|
||||||
|
colors = listOf(
|
||||||
|
color.copy(alpha = 0f),
|
||||||
|
color,
|
||||||
|
color,
|
||||||
|
color.copy(alpha = 0f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Warm Gradient Background
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Screen background with subtle warm gradient and grain
|
||||||
|
* Matches iOS WarmGradientBackground
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun WarmGradientBackground(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
content: @Composable BoxScope.() -> Unit = {}
|
||||||
|
) {
|
||||||
|
val isDark = isSystemInDarkTheme()
|
||||||
|
val primaryColor = MaterialTheme.colorScheme.primary
|
||||||
|
val backgroundColor = MaterialTheme.colorScheme.backgroundPrimary
|
||||||
|
|
||||||
|
Box(modifier = modifier.fillMaxSize()) {
|
||||||
|
// Base background
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(backgroundColor)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Subtle warm gradient overlay
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(
|
||||||
|
brush = Brush.linearGradient(
|
||||||
|
colors = listOf(
|
||||||
|
primaryColor.copy(alpha = if (isDark) 0.05f else 0.03f),
|
||||||
|
Color.Transparent
|
||||||
|
),
|
||||||
|
start = Offset.Zero,
|
||||||
|
end = Offset.Infinite
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Grain for natural feel
|
||||||
|
GrainTexture(opacity = 0.02f)
|
||||||
|
|
||||||
|
// Content
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Organic Card Modifier
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Organic card styling modifier
|
||||||
|
* Matches iOS organicCard view modifier
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun Modifier.organicCard(
|
||||||
|
accentColor: Color = MaterialTheme.colorScheme.primary,
|
||||||
|
showBlob: Boolean = true,
|
||||||
|
shadowIntensity: ShadowIntensity = ShadowIntensity.Medium,
|
||||||
|
blobVariation: Int = 0
|
||||||
|
): Modifier = this
|
||||||
|
.naturalShadow(shadowIntensity)
|
||||||
|
.clip(OrganicRoundedShape)
|
||||||
|
.drawBehind {
|
||||||
|
// Main fill
|
||||||
|
drawRoundRect(
|
||||||
|
color = Color.Transparent, // Background handled by OrganicCardBackground
|
||||||
|
cornerRadius = androidx.compose.ui.geometry.CornerRadius(28.dp.toPx())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composable wrapper for organic card styling
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun OrganicCard(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
accentColor: Color = MaterialTheme.colorScheme.primary,
|
||||||
|
showBlob: Boolean = true,
|
||||||
|
shadowIntensity: ShadowIntensity = ShadowIntensity.Medium,
|
||||||
|
blobVariation: Int = 0,
|
||||||
|
content: @Composable BoxScope.() -> Unit
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier.naturalShadow(shadowIntensity, OrganicRoundedShape)
|
||||||
|
) {
|
||||||
|
OrganicCardBackground(
|
||||||
|
accentColor = accentColor,
|
||||||
|
showBlob = showBlob,
|
||||||
|
blobVariation = blobVariation
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(OrganicRoundedShape),
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Organic Spacing
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Organic spacing constants matching iOS OrganicSpacing
|
||||||
|
* Provides both semantic names (compact, cozy, etc.) and standard names (xs, sm, etc.)
|
||||||
|
*/
|
||||||
|
object OrganicSpacing {
|
||||||
|
// Semantic names (original)
|
||||||
|
val compact = 8.dp
|
||||||
|
val cozy = 20.dp
|
||||||
|
val comfortable = 24.dp
|
||||||
|
val spacious = 32.dp
|
||||||
|
val airy = 40.dp
|
||||||
|
|
||||||
|
// Standard size names (for compatibility)
|
||||||
|
val xs = 4.dp
|
||||||
|
val sm = 8.dp
|
||||||
|
val md = 12.dp
|
||||||
|
val lg = 16.dp
|
||||||
|
val xl = 24.dp
|
||||||
|
val xxl = 32.dp
|
||||||
|
|
||||||
|
// Additional aliases
|
||||||
|
val extraSmall = xs
|
||||||
|
val small = sm
|
||||||
|
val medium = md
|
||||||
|
val large = lg
|
||||||
|
val extraLarge = xl
|
||||||
|
|
||||||
|
// Extended aliases (matching iOS naming)
|
||||||
|
val minimal = 2.dp
|
||||||
|
val generous = 48.dp
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Organic radius constants for rounded corners
|
||||||
|
* Alias for AppRadius with organic naming
|
||||||
|
*/
|
||||||
|
object OrganicRadius {
|
||||||
|
val xs = 4.dp
|
||||||
|
val sm = 8.dp
|
||||||
|
val md = 12.dp
|
||||||
|
val lg = 16.dp
|
||||||
|
val xl = 20.dp
|
||||||
|
val xxl = 24.dp
|
||||||
|
val full = 50.dp
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Organic shapes for consistency
|
||||||
|
*/
|
||||||
|
object OrganicShapes {
|
||||||
|
val extraSmall = RoundedCornerShape(OrganicRadius.xs)
|
||||||
|
val small = RoundedCornerShape(OrganicRadius.sm)
|
||||||
|
val medium = RoundedCornerShape(OrganicRadius.md)
|
||||||
|
val large = RoundedCornerShape(OrganicRadius.lg)
|
||||||
|
val extraLarge = RoundedCornerShape(OrganicRadius.xl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Floating Leaf Decoration
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animated floating leaf decoration
|
||||||
|
* Matches iOS FloatingLeaf
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun FloatingLeaf(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
delay: Int = 0,
|
||||||
|
size: Dp = 20.dp,
|
||||||
|
color: Color = MaterialTheme.colorScheme.primary
|
||||||
|
) {
|
||||||
|
val infiniteTransition = rememberInfiniteTransition(label = "leaf")
|
||||||
|
|
||||||
|
val rotation by infiniteTransition.animateFloat(
|
||||||
|
initialValue = 0f,
|
||||||
|
targetValue = 15f,
|
||||||
|
animationSpec = infiniteRepeatable(
|
||||||
|
animation = tween(
|
||||||
|
durationMillis = 4000,
|
||||||
|
delayMillis = delay,
|
||||||
|
easing = FastOutSlowInEasing
|
||||||
|
),
|
||||||
|
repeatMode = RepeatMode.Reverse
|
||||||
|
),
|
||||||
|
label = "leafRotation"
|
||||||
|
)
|
||||||
|
|
||||||
|
val offsetY by infiniteTransition.animateFloat(
|
||||||
|
initialValue = 0f,
|
||||||
|
targetValue = 8f,
|
||||||
|
animationSpec = infiniteRepeatable(
|
||||||
|
animation = tween(
|
||||||
|
durationMillis = 4000,
|
||||||
|
delayMillis = delay,
|
||||||
|
easing = FastOutSlowInEasing
|
||||||
|
),
|
||||||
|
repeatMode = RepeatMode.Reverse
|
||||||
|
),
|
||||||
|
label = "leafOffset"
|
||||||
|
)
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Eco,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = modifier
|
||||||
|
.size(size)
|
||||||
|
.rotate(rotation)
|
||||||
|
.offset(y = offsetY.dp),
|
||||||
|
tint = color.copy(alpha = 0.15f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Organic Button
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primary button with organic gradient styling
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun OrganicPrimaryButton(
|
||||||
|
text: String,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
isLoading: Boolean = false,
|
||||||
|
icon: ImageVector? = null
|
||||||
|
) {
|
||||||
|
val primaryColor = MaterialTheme.colorScheme.primary
|
||||||
|
|
||||||
|
androidx.compose.material3.Button(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(56.dp),
|
||||||
|
enabled = enabled && !isLoading,
|
||||||
|
shape = RoundedCornerShape(28.dp),
|
||||||
|
colors = androidx.compose.material3.ButtonDefaults.buttonColors(
|
||||||
|
containerColor = primaryColor,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
disabledContainerColor = primaryColor.copy(alpha = 0.5f),
|
||||||
|
disabledContentColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.7f)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
androidx.compose.material3.CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.labelLarge.copy(
|
||||||
|
fontWeight = androidx.compose.ui.text.font.FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (icon != null) {
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
iosApp/.claude/settings.local.json
Normal file
6
iosApp/.claude/settings.local.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"enabledMcpjsonServers": [
|
||||||
|
"ios-simulator"
|
||||||
|
],
|
||||||
|
"enableAllProjectMcpServers": true
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user