This commit is contained in:
Trey t
2025-11-06 09:25:21 -06:00
parent e272e45689
commit e24d1d8559
29 changed files with 1806 additions and 103 deletions

View File

@@ -23,7 +23,8 @@ fun TaskCard(
onCompleteClick: (() -> Unit)?,
onEditClick: () -> Unit,
onCancelClick: (() -> Unit)?,
onUncancelClick: (() -> Unit)?
onUncancelClick: (() -> Unit)?,
onMarkInProgressClick: (() -> Unit)? = null
) {
Card(
modifier = Modifier.fillMaxWidth(),
@@ -240,25 +241,57 @@ fun TaskCard(
}
}
// Show complete task button based on API logic
if (task.showCompletedButton && onCompleteClick != null) {
// Show complete task button and mark in progress button
if ((task.showCompletedButton && onCompleteClick != null) || (onMarkInProgressClick != null && task.status?.name != "in_progress")) {
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = onCompleteClick,
Row(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp)
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
Icons.Default.CheckCircle,
contentDescription = null,
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
"Complete Task",
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.SemiBold
)
// Mark In Progress button
if (onMarkInProgressClick != null && task.status?.name != "in_progress") {
Button(
onClick = onMarkInProgressClick,
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.tertiary
)
) {
Icon(
Icons.Default.PlayArrow,
contentDescription = null,
modifier = Modifier.size(18.dp)
)
Spacer(modifier = Modifier.width(4.dp))
Text(
"In Progress",
style = MaterialTheme.typography.labelLarge,
fontWeight = FontWeight.SemiBold
)
}
}
// Complete Task button
if (task.showCompletedButton && onCompleteClick != null) {
Button(
onClick = onCompleteClick,
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(12.dp)
) {
Icon(
Icons.Default.CheckCircle,
contentDescription = null,
modifier = Modifier.size(18.dp)
)
Spacer(modifier = Modifier.width(4.dp))
Text(
"Complete",
style = MaterialTheme.typography.labelLarge,
fontWeight = FontWeight.SemiBold
)
}
}
}
}

View File

@@ -0,0 +1,388 @@
package com.mycrib.android.ui.screens
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.mycrib.android.ui.components.CompleteTaskDialog
import com.mycrib.android.ui.components.task.TaskCard
import com.mycrib.android.viewmodel.TaskCompletionViewModel
import com.mycrib.android.viewmodel.TaskViewModel
import com.mycrib.shared.models.TaskDetail
import com.mycrib.shared.network.ApiResult
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AllTasksScreen(
onNavigateToEditTask: (TaskDetail) -> Unit,
viewModel: TaskViewModel = viewModel { TaskViewModel() },
taskCompletionViewModel: TaskCompletionViewModel = viewModel { TaskCompletionViewModel() }
) {
val tasksState by viewModel.tasksState.collectAsState()
val completionState by taskCompletionViewModel.createCompletionState.collectAsState()
var showInProgressTasks by remember { mutableStateOf(false) }
var showDoneTasks by remember { mutableStateOf(false) }
var showCompleteDialog by remember { mutableStateOf(false) }
var selectedTask by remember { mutableStateOf<TaskDetail?>(null) }
LaunchedEffect(Unit) {
viewModel.loadTasks()
}
// Handle completion success
LaunchedEffect(completionState) {
when (completionState) {
is ApiResult.Success -> {
showCompleteDialog = false
selectedTask = null
taskCompletionViewModel.resetCreateState()
viewModel.loadTasks()
}
else -> {}
}
}
Scaffold(
topBar = {
TopAppBar(
title = {
Text(
"All Tasks",
fontWeight = FontWeight.Bold
)
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surface
)
)
}
) { paddingValues ->
when (tasksState) {
is ApiResult.Loading -> {
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues),
contentAlignment = androidx.compose.ui.Alignment.Center
) {
CircularProgressIndicator()
}
}
is ApiResult.Error -> {
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues),
contentAlignment = androidx.compose.ui.Alignment.Center
) {
Column(
horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Icon(
Icons.Default.Error,
contentDescription = null,
modifier = Modifier.size(64.dp),
tint = MaterialTheme.colorScheme.error
)
Text(
text = "Error: ${(tasksState as ApiResult.Error).message}",
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodyLarge
)
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = { viewModel.loadTasks() }) {
Text("Retry")
}
}
}
}
is ApiResult.Success -> {
val taskData = (tasksState as ApiResult.Success).data
val hasNoTasks = taskData.upcomingTasks.isEmpty() &&
taskData.inProgressTasks.isEmpty() &&
taskData.doneTasks.isEmpty()
if (hasNoTasks) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues),
contentAlignment = androidx.compose.ui.Alignment.Center
) {
Column(
horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Icon(
Icons.Default.CheckCircle,
contentDescription = null,
modifier = Modifier.size(64.dp),
tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f)
)
Text(
"No tasks yet",
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
"Add a task to a residence to get started",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)
)
}
}
} else {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues),
contentPadding = PaddingValues(
start = 16.dp,
end = 16.dp,
top = 16.dp,
bottom = 96.dp
),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
// Task summary pills
item {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
TaskSummaryPill(
count = taskData.summary.upcoming,
label = "Upcoming",
color = MaterialTheme.colorScheme.primary
)
TaskSummaryPill(
count = taskData.summary.inProgress,
label = "In Progress",
color = MaterialTheme.colorScheme.tertiary
)
TaskSummaryPill(
count = taskData.summary.done,
label = "Done",
color = MaterialTheme.colorScheme.secondary
)
}
}
// Upcoming tasks header
if (taskData.upcomingTasks.isNotEmpty()) {
item {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
) {
Icon(
Icons.Default.CalendarToday,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary
)
Text(
text = "Upcoming (${taskData.upcomingTasks.size})",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(top = 8.dp)
)
}
}
}
// Upcoming tasks
items(taskData.upcomingTasks) { task ->
TaskCard(
task = task,
onCompleteClick = {
selectedTask = task
showCompleteDialog = true
},
onEditClick = { onNavigateToEditTask(task) },
onCancelClick = { /* TODO */ },
onUncancelClick = null,
onMarkInProgressClick = {
viewModel.markInProgress(task.id) { success ->
if (success) {
viewModel.loadTasks()
}
}
}
)
}
// In Progress section (collapsible)
if (taskData.inProgressTasks.isNotEmpty()) {
item {
Card(
modifier = Modifier.fillMaxWidth(),
onClick = { showInProgressTasks = !showInProgressTasks }
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
) {
Icon(
Icons.Default.PlayArrow,
contentDescription = null,
tint = MaterialTheme.colorScheme.tertiary
)
Text(
text = "In Progress (${taskData.inProgressTasks.size})",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold
)
}
Icon(
if (showInProgressTasks) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown,
contentDescription = if (showInProgressTasks) "Collapse" else "Expand"
)
}
}
}
if (showInProgressTasks) {
items(taskData.inProgressTasks) { task ->
TaskCard(
task = task,
onCompleteClick = {
selectedTask = task
showCompleteDialog = true
},
onEditClick = { onNavigateToEditTask(task) },
onCancelClick = { /* TODO */ },
onUncancelClick = null,
onMarkInProgressClick = null
)
}
}
}
// Done section (collapsible)
if (taskData.doneTasks.isNotEmpty()) {
item {
Card(
modifier = Modifier.fillMaxWidth(),
onClick = { showDoneTasks = !showDoneTasks }
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
) {
Icon(
Icons.Default.CheckCircle,
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary
)
Text(
text = "Done (${taskData.doneTasks.size})",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold
)
}
Icon(
if (showDoneTasks) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown,
contentDescription = if (showDoneTasks) "Collapse" else "Expand"
)
}
}
}
if (showDoneTasks) {
items(taskData.doneTasks) { task ->
TaskCard(
task = task,
onCompleteClick = null,
onEditClick = { onNavigateToEditTask(task) },
onCancelClick = null,
onUncancelClick = null,
onMarkInProgressClick = null
)
}
}
}
}
}
}
else -> {}
}
}
if (showCompleteDialog && selectedTask != null) {
CompleteTaskDialog(
taskId = selectedTask!!.id,
taskTitle = selectedTask!!.title,
onDismiss = {
showCompleteDialog = false
selectedTask = null
taskCompletionViewModel.resetCreateState()
},
onComplete = { request, images ->
if (images.isNotEmpty()) {
taskCompletionViewModel.createTaskCompletionWithImages(
request = request,
images = images.map { it.bytes },
imageFileNames = images.map { it.fileName }
)
} else {
taskCompletionViewModel.createTaskCompletion(request)
}
}
)
}
}
@Composable
private fun TaskSummaryPill(
count: Int,
label: String,
color: androidx.compose.ui.graphics.Color
) {
Surface(
color = color.copy(alpha = 0.1f),
shape = MaterialTheme.shapes.small
) {
Row(
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
) {
Text(
text = count.toString(),
style = MaterialTheme.typography.labelLarge,
color = color,
fontWeight = FontWeight.Bold
)
Text(
text = label,
style = MaterialTheme.typography.labelMedium,
color = color
)
}
}
}

View File

@@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.mycrib.android.ui.components.auth.AuthHeader
@@ -27,6 +28,7 @@ fun LoginScreen(
) {
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var passwordVisible by remember { mutableStateOf(false) }
val loginState by viewModel.loginState.collectAsState()
// Handle login state changes
@@ -97,9 +99,17 @@ fun LoginScreen(
leadingIcon = {
Icon(Icons.Default.Lock, contentDescription = null)
},
trailingIcon = {
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(
imageVector = if (passwordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff,
contentDescription = if (passwordVisible) "Hide password" else "Show password"
)
}
},
modifier = Modifier.fillMaxWidth(),
singleLine = true,
visualTransformation = PasswordVisualTransformation(),
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
shape = RoundedCornerShape(12.dp)
)

View File

@@ -0,0 +1,149 @@
package com.mycrib.android.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
import com.mycrib.navigation.*
import com.mycrib.repository.LookupsRepository
import com.mycrib.shared.models.Residence
import com.mycrib.storage.TokenStorage
@Composable
fun MainScreen(
onLogout: () -> Unit,
onResidenceClick: (Int) -> Unit,
onAddResidence: () -> Unit,
onNavigateToEditResidence: (Residence) -> Unit,
onNavigateToEditTask: (com.mycrib.shared.models.TaskDetail) -> Unit
) {
var selectedTab by remember { mutableStateOf(0) }
val navController = rememberNavController()
Scaffold(
bottomBar = {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp),
contentAlignment = Alignment.Center
) {
NavigationBar(
modifier = Modifier
.widthIn(max = 500.dp)
.shadow(
elevation = 4.dp,
shape = RoundedCornerShape(20.dp)
),
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
tonalElevation = 0.dp
) {
NavigationBarItem(
icon = { Icon(Icons.Default.Home, contentDescription = "Residences") },
label = { Text("Residences") },
selected = selectedTab == 0,
onClick = {
selectedTab = 0
navController.navigate(MainTabResidencesRoute) {
popUpTo(MainTabResidencesRoute) { inclusive = true }
}
},
colors = NavigationBarItemDefaults.colors(
selectedIconColor = MaterialTheme.colorScheme.onSecondaryContainer,
selectedTextColor = MaterialTheme.colorScheme.onSecondaryContainer,
indicatorColor = MaterialTheme.colorScheme.secondaryContainer,
unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
)
)
NavigationBarItem(
icon = { Icon(Icons.Default.CheckCircle, contentDescription = "Tasks") },
label = { Text("Tasks") },
selected = selectedTab == 1,
onClick = {
selectedTab = 1
navController.navigate(MainTabTasksRoute) {
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
)
)
NavigationBarItem(
icon = { Icon(Icons.Default.Person, contentDescription = "Profile") },
label = { Text("Profile") },
selected = selectedTab == 2,
onClick = {
selectedTab = 2
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 ->
NavHost(
navController = navController,
startDestination = MainTabResidencesRoute,
modifier = Modifier.fillMaxSize()
) {
composable<MainTabResidencesRoute> {
Box(modifier = Modifier.fillMaxSize()) {
ResidencesScreen(
onResidenceClick = onResidenceClick,
onAddResidence = onAddResidence,
onLogout = onLogout,
onNavigateToProfile = {
selectedTab = 2
navController.navigate(MainTabProfileRoute)
}
)
}
}
composable<MainTabTasksRoute> {
Box(modifier = Modifier.fillMaxSize()) {
AllTasksScreen(
onNavigateToEditTask = onNavigateToEditTask
)
}
}
composable<MainTabProfileRoute> {
Box(modifier = Modifier.fillMaxSize()) {
ProfileScreen(
onNavigateBack = {
selectedTab = 0
navController.navigate(MainTabResidencesRoute)
},
onLogout = onLogout
)
}
}
}
}
}

View File

@@ -116,7 +116,7 @@ fun ProfileScreen(
.fillMaxSize()
.padding(paddingValues)
.verticalScroll(rememberScrollState())
.padding(24.dp),
.padding(start = 24.dp, end = 24.dp, top = 24.dp, bottom = 96.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(20.dp)
) {

View File

@@ -441,7 +441,14 @@ fun ResidenceDetailScreen(
onCancelClick = {
residenceViewModel.cancelTask(task.id)
},
onUncancelClick = null
onUncancelClick = null,
onMarkInProgressClick = {
taskViewModel.markInProgress(task.id) { success ->
if (success) {
residenceViewModel.loadResidenceTasks(residenceId)
}
}
}
)
}
@@ -494,7 +501,8 @@ fun ResidenceDetailScreen(
onCancelClick = {
residenceViewModel.cancelTask(task.id)
},
onUncancelClick = null
onUncancelClick = null,
onMarkInProgressClick = null
)
}
}
@@ -546,7 +554,8 @@ fun ResidenceDetailScreen(
onCancelClick = null,
onUncancelClick = {
residenceViewModel.uncancelTask(task.id)
}
},
onMarkInProgressClick = null
)
}
}

View File

@@ -144,7 +144,12 @@ fun ResidencesScreen(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues),
contentPadding = PaddingValues(16.dp),
contentPadding = PaddingValues(
start = 16.dp,
end = 16.dp,
top = 16.dp,
bottom = 96.dp
),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// Summary Card

View File

@@ -10,7 +10,9 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.mycrib.android.ui.components.task.SimpleTaskListItem
import com.mycrib.android.ui.components.CompleteTaskDialog
import com.mycrib.android.ui.components.task.TaskCard
import com.mycrib.android.viewmodel.TaskCompletionViewModel
import com.mycrib.android.viewmodel.TaskViewModel
import com.mycrib.shared.network.ApiResult
@@ -18,18 +20,37 @@ import com.mycrib.shared.network.ApiResult
@Composable
fun TasksScreen(
onNavigateBack: () -> Unit,
viewModel: TaskViewModel = viewModel { TaskViewModel() }
viewModel: TaskViewModel = viewModel { TaskViewModel() },
taskCompletionViewModel: TaskCompletionViewModel = viewModel { TaskCompletionViewModel() }
) {
val tasksState by viewModel.tasksState.collectAsState()
val completionState by taskCompletionViewModel.createCompletionState.collectAsState()
var showInProgressTasks by remember { mutableStateOf(false) }
var showDoneTasks by remember { mutableStateOf(false) }
var showCompleteDialog by remember { mutableStateOf(false) }
var selectedTask by remember { mutableStateOf<com.mycrib.shared.models.TaskDetail?>(null) }
LaunchedEffect(Unit) {
viewModel.loadTasks()
}
// Handle completion success
LaunchedEffect(completionState) {
when (completionState) {
is ApiResult.Success -> {
showCompleteDialog = false
selectedTask = null
taskCompletionViewModel.resetCreateState()
viewModel.loadTasks()
}
else -> {}
}
}
Scaffold(
topBar = {
TopAppBar(
title = { Text("Tasks") },
title = { Text("All Tasks") },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
@@ -74,8 +95,12 @@ fun TasksScreen(
}
}
is ApiResult.Success -> {
val tasks = (tasksState as ApiResult.Success).data
if (tasks.isEmpty()) {
val taskData = (tasksState as ApiResult.Success).data
val hasNoTasks = taskData.upcomingTasks.isEmpty() &&
taskData.inProgressTasks.isEmpty() &&
taskData.doneTasks.isEmpty()
if (hasNoTasks) {
Box(
modifier = Modifier
.fillMaxSize()
@@ -90,18 +115,157 @@ fun TasksScreen(
.fillMaxSize()
.padding(paddingValues),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(tasks) { task ->
SimpleTaskListItem(
title = task.title,
description = task.description,
priority = task.priority,
status = task.status,
dueDate = task.dueDate,
isOverdue = task.isOverdue == true
// Task summary pills
item {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
TaskPill(
count = taskData.summary.upcoming,
label = "Upcoming",
color = MaterialTheme.colorScheme.primary
)
TaskPill(
count = taskData.summary.inProgress,
label = "In Progress",
color = MaterialTheme.colorScheme.tertiary
)
TaskPill(
count = taskData.summary.done,
label = "Done",
color = MaterialTheme.colorScheme.secondary
)
}
}
// Upcoming tasks header
if (taskData.upcomingTasks.isNotEmpty()) {
item {
Text(
text = "Upcoming (${taskData.upcomingTasks.size})",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(top = 8.dp)
)
}
}
// Upcoming tasks
items(taskData.upcomingTasks) { task ->
TaskCard(
task = task,
onCompleteClick = {
selectedTask = task
showCompleteDialog = true
},
onEditClick = { },
onCancelClick = { },
onUncancelClick = { }
)
}
// In Progress section (collapsible)
if (taskData.inProgressTasks.isNotEmpty()) {
item {
Card(
modifier = Modifier.fillMaxWidth(),
onClick = { showInProgressTasks = !showInProgressTasks }
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
) {
Icon(
Icons.Default.PlayArrow,
contentDescription = null,
tint = MaterialTheme.colorScheme.tertiary
)
Text(
text = "In Progress (${taskData.inProgressTasks.size})",
style = MaterialTheme.typography.titleMedium
)
}
Icon(
if (showInProgressTasks) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown,
contentDescription = if (showInProgressTasks) "Collapse" else "Expand"
)
}
}
}
if (showInProgressTasks) {
items(taskData.inProgressTasks) { task ->
TaskCard(
task = task,
onCompleteClick = {
selectedTask = task
showCompleteDialog = true
},
onEditClick = { /* TODO */ },
onCancelClick = {},
onUncancelClick = {}
)
}
}
}
// Done section (collapsible)
if (taskData.doneTasks.isNotEmpty()) {
item {
Card(
modifier = Modifier.fillMaxWidth(),
onClick = { showDoneTasks = !showDoneTasks }
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
) {
Icon(
Icons.Default.CheckCircle,
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary
)
Text(
text = "Done (${taskData.doneTasks.size})",
style = MaterialTheme.typography.titleMedium
)
}
Icon(
if (showDoneTasks) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown,
contentDescription = if (showDoneTasks) "Collapse" else "Expand"
)
}
}
}
if (showDoneTasks) {
items(taskData.doneTasks) { task ->
TaskCard(
task = task,
onCompleteClick = { /* TODO */ },
onEditClick = { /* TODO */ },
onUncancelClick = {},
onCancelClick = {}
)
}
}
}
}
}
}
@@ -109,4 +273,56 @@ fun TasksScreen(
else -> {}
}
}
if (showCompleteDialog && selectedTask != null) {
CompleteTaskDialog(
taskId = selectedTask!!.id,
taskTitle = selectedTask!!.title,
onDismiss = {
showCompleteDialog = false
selectedTask = null
taskCompletionViewModel.resetCreateState()
},
onComplete = { request, images ->
if (images.isNotEmpty()) {
taskCompletionViewModel.createTaskCompletionWithImages(
request = request,
images = images.map { it.bytes },
imageFileNames = images.map { it.fileName }
)
} else {
taskCompletionViewModel.createTaskCompletion(request)
}
}
)
}
}
@Composable
private fun TaskPill(
count: Int,
label: String,
color: androidx.compose.ui.graphics.Color
) {
Surface(
color = color.copy(alpha = 0.1f),
shape = MaterialTheme.shapes.small
) {
Row(
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
) {
Text(
text = count.toString(),
style = MaterialTheme.typography.labelLarge,
color = color
)
Text(
text = label,
style = MaterialTheme.typography.labelMedium,
color = color
)
}
}
}