wip
This commit is contained in:
@@ -44,6 +44,7 @@ data class TaskCreateRequest(
|
||||
val frequency: Int,
|
||||
@SerialName("interval_days") val intervalDays: Int? = null,
|
||||
val priority: Int,
|
||||
val status: Int? = null,
|
||||
@SerialName("due_date") val dueDate: String,
|
||||
@SerialName("estimated_cost") val estimatedCost: String? = null
|
||||
)
|
||||
@@ -106,3 +107,21 @@ data class TaskCancelResponse(
|
||||
val message: String,
|
||||
val task: TaskDetail
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TaskColumn(
|
||||
val name: String,
|
||||
@SerialName("display_name") val displayName: String,
|
||||
@SerialName("button_types") val buttonTypes: List<String>,
|
||||
val icons: Map<String, String>,
|
||||
val color: String,
|
||||
val tasks: List<TaskDetail>,
|
||||
val count: Int
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TaskColumnsResponse(
|
||||
val columns: List<TaskColumn>,
|
||||
@SerialName("days_threshold") val daysThreshold: Int? = null,
|
||||
@SerialName("residence_id") val residenceId: String? = null
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
suspend fun getTasks(
|
||||
token: String,
|
||||
days: Int = 30
|
||||
): ApiResult<AllTasksResponse> {
|
||||
): ApiResult<TaskColumnsResponse> {
|
||||
return try {
|
||||
val response = client.get("$baseUrl/tasks/") {
|
||||
header("Authorization", "Token $token")
|
||||
@@ -101,7 +101,7 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
token: String,
|
||||
residenceId: Int,
|
||||
days: Int = 30
|
||||
): ApiResult<TasksByResidenceResponse> {
|
||||
): ApiResult<TaskColumnsResponse> {
|
||||
return try {
|
||||
val response = client.get("$baseUrl/tasks/by-residence/$residenceId/") {
|
||||
header("Authorization", "Token $token")
|
||||
|
||||
@@ -273,6 +273,7 @@ fun AddNewTaskDialog(
|
||||
frequency = frequency.id,
|
||||
intervalDays = intervalDays.toIntOrNull(),
|
||||
priority = priority.id,
|
||||
status = null,
|
||||
dueDate = dueDate,
|
||||
estimatedCost = estimatedCost.ifBlank { null }
|
||||
)
|
||||
|
||||
@@ -330,6 +330,7 @@ fun AddNewTaskWithResidenceDialog(
|
||||
frequency = frequency.id,
|
||||
intervalDays = intervalDays.toIntOrNull(),
|
||||
priority = priority.id,
|
||||
status = null,
|
||||
dueDate = dueDate,
|
||||
estimatedCost = estimatedCost.ifBlank { null }
|
||||
)
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
package com.mycrib.android.ui.components.task
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.mycrib.android.viewmodel.TaskViewModel
|
||||
|
||||
// MARK: - Edit Task Button
|
||||
@Composable
|
||||
fun EditTaskButton(
|
||||
taskId: Int,
|
||||
onCompletion: () -> Unit,
|
||||
onError: (String) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
// Edit navigates to edit screen - handled by parent
|
||||
onCompletion()
|
||||
},
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
contentColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Edit,
|
||||
contentDescription = "Edit",
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text("Edit", style = MaterialTheme.typography.labelLarge)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Cancel Task Button
|
||||
@Composable
|
||||
fun CancelTaskButton(
|
||||
taskId: Int,
|
||||
onCompletion: () -> Unit,
|
||||
onError: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: TaskViewModel = viewModel { TaskViewModel() }
|
||||
) {
|
||||
OutlinedButton(
|
||||
onClick = {
|
||||
viewModel.cancelTask(taskId) { success ->
|
||||
if (success) {
|
||||
onCompletion()
|
||||
} else {
|
||||
onError("Failed to cancel task")
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.outlinedButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.error
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Cancel,
|
||||
contentDescription = "Cancel",
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text("Cancel", style = MaterialTheme.typography.labelLarge)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Uncancel (Restore) Task Button
|
||||
@Composable
|
||||
fun UncancelTaskButton(
|
||||
taskId: Int,
|
||||
onCompletion: () -> Unit,
|
||||
onError: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: TaskViewModel = viewModel { TaskViewModel() }
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
viewModel.uncancelTask(taskId) { success ->
|
||||
if (success) {
|
||||
onCompletion()
|
||||
} else {
|
||||
onError("Failed to restore task")
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Undo,
|
||||
contentDescription = "Restore",
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text("Restore", style = MaterialTheme.typography.labelLarge)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mark In Progress Button
|
||||
@Composable
|
||||
fun MarkInProgressButton(
|
||||
taskId: Int,
|
||||
onCompletion: () -> Unit,
|
||||
onError: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: TaskViewModel = viewModel { TaskViewModel() }
|
||||
) {
|
||||
OutlinedButton(
|
||||
onClick = {
|
||||
viewModel.markInProgress(taskId) { success ->
|
||||
if (success) {
|
||||
onCompletion()
|
||||
} else {
|
||||
onError("Failed to mark task in progress")
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.outlinedButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.tertiary
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.PlayCircle,
|
||||
contentDescription = "Mark In Progress",
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text("In Progress", style = MaterialTheme.typography.labelLarge)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Complete Task Button
|
||||
@Composable
|
||||
fun CompleteTaskButton(
|
||||
taskId: Int,
|
||||
onCompletion: () -> Unit,
|
||||
onError: (String) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
// Complete shows dialog - handled by parent
|
||||
onCompletion()
|
||||
},
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.CheckCircle,
|
||||
contentDescription = "Complete",
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text("Complete", style = MaterialTheme.typography.labelLarge)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Archive Task Button
|
||||
@Composable
|
||||
fun ArchiveTaskButton(
|
||||
taskId: Int,
|
||||
onCompletion: () -> Unit,
|
||||
onError: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: TaskViewModel = viewModel { TaskViewModel() }
|
||||
) {
|
||||
OutlinedButton(
|
||||
onClick = {
|
||||
viewModel.archiveTask(taskId) { success ->
|
||||
if (success) {
|
||||
onCompletion()
|
||||
} else {
|
||||
onError("Failed to archive task")
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.outlinedButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.outline
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Archive,
|
||||
contentDescription = "Archive",
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text("Archive", style = MaterialTheme.typography.labelLarge)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Unarchive Task Button
|
||||
@Composable
|
||||
fun UnarchiveTaskButton(
|
||||
taskId: Int,
|
||||
onCompletion: () -> Unit,
|
||||
onError: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: TaskViewModel = viewModel { TaskViewModel() }
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
viewModel.unarchiveTask(taskId) { success ->
|
||||
if (success) {
|
||||
onCompletion()
|
||||
} else {
|
||||
onError("Failed to unarchive task")
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
contentColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Unarchive,
|
||||
contentDescription = "Unarchive",
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text("Unarchive", style = MaterialTheme.typography.labelLarge)
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
@Composable
|
||||
fun TaskCard(
|
||||
task: TaskDetail,
|
||||
buttonTypes: List<String> = emptyList(),
|
||||
onCompleteClick: (() -> Unit)?,
|
||||
onEditClick: () -> Unit,
|
||||
onCancelClick: (() -> Unit)?,
|
||||
@@ -243,161 +244,87 @@ fun TaskCard(
|
||||
}
|
||||
}
|
||||
|
||||
// Show complete task button and mark in progress button
|
||||
if ((task.showCompletedButton && onCompleteClick != null) || (onMarkInProgressClick != null && task.status?.name != "in_progress")) {
|
||||
// Render buttons based on buttonTypes array
|
||||
if (buttonTypes.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
// 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
|
||||
)
|
||||
buttonTypes.forEach { buttonType ->
|
||||
when (buttonType) {
|
||||
"mark_in_progress" -> {
|
||||
onMarkInProgressClick?.let {
|
||||
MarkInProgressButton(
|
||||
taskId = task.id,
|
||||
onCompletion = it,
|
||||
onError = { error -> println("Error: $error") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Action buttons row
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
// Edit button
|
||||
OutlinedButton(
|
||||
onClick = onEditClick,
|
||||
modifier = Modifier.weight(1f),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Edit,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("Edit")
|
||||
}
|
||||
|
||||
// Cancel or Uncancel button
|
||||
when {
|
||||
onCancelClick != null -> {
|
||||
OutlinedButton(
|
||||
onClick = onCancelClick,
|
||||
modifier = Modifier.weight(1f),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
colors = ButtonDefaults.outlinedButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.error
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Cancel,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("Cancel")
|
||||
"complete" -> {
|
||||
onCompleteClick?.let {
|
||||
CompleteTaskButton(
|
||||
taskId = task.id,
|
||||
onCompletion = it,
|
||||
onError = { error -> println("Error: $error") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
onUncancelClick != null -> {
|
||||
Button(
|
||||
onClick = onUncancelClick,
|
||||
modifier = Modifier.weight(1f),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondary
|
||||
"edit" -> {
|
||||
EditTaskButton(
|
||||
taskId = task.id,
|
||||
onCompletion = onEditClick,
|
||||
onError = { error -> println("Error: $error") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Undo,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("Restore")
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
"cancel" -> {
|
||||
onCancelClick?.let {
|
||||
CancelTaskButton(
|
||||
taskId = task.id,
|
||||
onCompletion = it,
|
||||
onError = { error -> println("Error: $error") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
"uncancel" -> {
|
||||
onUncancelClick?.let {
|
||||
UncancelTaskButton(
|
||||
taskId = task.id,
|
||||
onCompletion = it,
|
||||
onError = { error -> println("Error: $error") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
"archive" -> {
|
||||
onArchiveClick?.let {
|
||||
ArchiveTaskButton(
|
||||
taskId = task.id,
|
||||
onCompletion = it,
|
||||
onError = { error -> println("Error: $error") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
"unarchive" -> {
|
||||
onUnarchiveClick?.let {
|
||||
UnarchiveTaskButton(
|
||||
taskId = task.id,
|
||||
onCompletion = it,
|
||||
onError = { error -> println("Error: $error") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Archive/Unarchive button row
|
||||
if (task.archived) {
|
||||
if (onUnarchiveClick != null) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedButton(
|
||||
onClick = onUnarchiveClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
colors = ButtonDefaults.outlinedButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Unarchive,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("Unarchive")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (onArchiveClick != null) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedButton(
|
||||
onClick = onArchiveClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
colors = ButtonDefaults.outlinedButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.outline
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Archive,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("Archive")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.mycrib.shared.models.AllTasksResponse
|
||||
import com.mycrib.shared.models.TaskColumn
|
||||
import com.mycrib.shared.models.TaskDetail
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@@ -227,3 +227,210 @@ private fun TaskColumn(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic Task Kanban View that creates columns based on API response
|
||||
*/
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun DynamicTaskKanbanView(
|
||||
columns: List<TaskColumn>,
|
||||
onCompleteTask: (TaskDetail) -> Unit,
|
||||
onEditTask: (TaskDetail) -> Unit,
|
||||
onCancelTask: ((TaskDetail) -> Unit)?,
|
||||
onUncancelTask: ((TaskDetail) -> Unit)?,
|
||||
onMarkInProgress: ((TaskDetail) -> Unit)?,
|
||||
onArchiveTask: ((TaskDetail) -> Unit)?,
|
||||
onUnarchiveTask: ((TaskDetail) -> Unit)?
|
||||
) {
|
||||
val pagerState = rememberPagerState(pageCount = { columns.size })
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
pageSpacing = 16.dp,
|
||||
contentPadding = PaddingValues(horizontal = 16.dp)
|
||||
) { page ->
|
||||
val column = columns[page]
|
||||
DynamicTaskColumn(
|
||||
column = column,
|
||||
onCompleteTask = onCompleteTask,
|
||||
onEditTask = onEditTask,
|
||||
onCancelTask = onCancelTask,
|
||||
onUncancelTask = onUncancelTask,
|
||||
onMarkInProgress = onMarkInProgress,
|
||||
onArchiveTask = onArchiveTask,
|
||||
onUnarchiveTask = onUnarchiveTask
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic Task Column that adapts based on column configuration
|
||||
*/
|
||||
@Composable
|
||||
private fun DynamicTaskColumn(
|
||||
column: TaskColumn,
|
||||
onCompleteTask: (TaskDetail) -> Unit,
|
||||
onEditTask: (TaskDetail) -> Unit,
|
||||
onCancelTask: ((TaskDetail) -> Unit)?,
|
||||
onUncancelTask: ((TaskDetail) -> Unit)?,
|
||||
onMarkInProgress: ((TaskDetail) -> Unit)?,
|
||||
onArchiveTask: ((TaskDetail) -> Unit)?,
|
||||
onUnarchiveTask: ((TaskDetail) -> Unit)?
|
||||
) {
|
||||
// Get icon from API response, with fallback
|
||||
val columnIcon = getIconFromName(column.icons["android"] ?: "List")
|
||||
|
||||
val columnColor = hexToColor(column.color)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(
|
||||
MaterialTheme.colorScheme.surface,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
) {
|
||||
// Header
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = columnIcon,
|
||||
contentDescription = null,
|
||||
tint = columnColor,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
Text(
|
||||
text = column.displayName,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = columnColor
|
||||
)
|
||||
}
|
||||
|
||||
Surface(
|
||||
color = columnColor,
|
||||
shape = CircleShape
|
||||
) {
|
||||
Text(
|
||||
text = column.count.toString(),
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.surface
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Tasks List
|
||||
if (column.tasks.isEmpty()) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(32.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = columnIcon,
|
||||
contentDescription = null,
|
||||
tint = columnColor.copy(alpha = 0.3f),
|
||||
modifier = Modifier.size(48.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = "No tasks",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
items(column.tasks, key = { it.id }) { task ->
|
||||
// Use existing TaskCard component with buttonTypes array
|
||||
TaskCard(
|
||||
task = task,
|
||||
buttonTypes = column.buttonTypes,
|
||||
onCompleteClick = { onCompleteTask(task) },
|
||||
onEditClick = { onEditTask(task) },
|
||||
onCancelClick = onCancelTask?.let { { it(task) } },
|
||||
onUncancelClick = onUncancelTask?.let { { it(task) } },
|
||||
onMarkInProgressClick = onMarkInProgress?.let { { it(task) } },
|
||||
onArchiveClick = onArchiveTask?.let { { it(task) } },
|
||||
onUnarchiveClick = onUnarchiveTask?.let { { it(task) } }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to convert icon name string to ImageVector
|
||||
*/
|
||||
private fun getIconFromName(iconName: String): ImageVector {
|
||||
return when (iconName) {
|
||||
"CalendarToday" -> Icons.Default.CalendarToday
|
||||
"PlayCircle" -> Icons.Default.PlayCircle
|
||||
"CheckCircle" -> Icons.Default.CheckCircle
|
||||
"Archive" -> Icons.Default.Archive
|
||||
"List" -> Icons.Default.List
|
||||
"PlayArrow" -> Icons.Default.PlayArrow
|
||||
"Unarchive" -> Icons.Default.Unarchive
|
||||
else -> Icons.Default.List // Default fallback
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to convert hex color string to Color
|
||||
* Supports formats: #RGB, #RRGGBB, #AARRGGBB
|
||||
* Platform-independent implementation
|
||||
*/
|
||||
private fun hexToColor(hex: String): Color {
|
||||
val cleanHex = hex.removePrefix("#")
|
||||
return try {
|
||||
when (cleanHex.length) {
|
||||
3 -> {
|
||||
// RGB format - expand to RRGGBB
|
||||
val r = cleanHex[0].toString().repeat(2).toInt(16)
|
||||
val g = cleanHex[1].toString().repeat(2).toInt(16)
|
||||
val b = cleanHex[2].toString().repeat(2).toInt(16)
|
||||
Color(red = r / 255f, green = g / 255f, blue = b / 255f)
|
||||
}
|
||||
6 -> {
|
||||
// RRGGBB format
|
||||
val r = cleanHex.substring(0, 2).toInt(16)
|
||||
val g = cleanHex.substring(2, 4).toInt(16)
|
||||
val b = cleanHex.substring(4, 6).toInt(16)
|
||||
Color(red = r / 255f, green = g / 255f, blue = b / 255f)
|
||||
}
|
||||
8 -> {
|
||||
// AARRGGBB format
|
||||
val a = cleanHex.substring(0, 2).toInt(16)
|
||||
val r = cleanHex.substring(2, 4).toInt(16)
|
||||
val g = cleanHex.substring(4, 6).toInt(16)
|
||||
val b = cleanHex.substring(6, 8).toInt(16)
|
||||
Color(red = r / 255f, green = g / 255f, blue = b / 255f, alpha = a / 255f)
|
||||
}
|
||||
else -> Color.Gray // Default fallback
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Color.Gray // Fallback on parse error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.mycrib.android.ui.components.AddNewTaskWithResidenceDialog
|
||||
import com.mycrib.android.ui.components.CompleteTaskDialog
|
||||
import com.mycrib.android.ui.components.task.TaskCard
|
||||
import com.mycrib.android.ui.components.task.TaskKanbanView
|
||||
import com.mycrib.android.ui.components.task.DynamicTaskKanbanView
|
||||
import com.mycrib.android.viewmodel.ResidenceViewModel
|
||||
import com.mycrib.android.viewmodel.TaskCompletionViewModel
|
||||
import com.mycrib.android.viewmodel.TaskViewModel
|
||||
@@ -149,10 +149,7 @@ fun AllTasksScreen(
|
||||
}
|
||||
is ApiResult.Success -> {
|
||||
val taskData = (tasksState as ApiResult.Success).data
|
||||
val hasNoTasks = taskData.upcomingTasks.isEmpty() &&
|
||||
taskData.inProgressTasks.isEmpty() &&
|
||||
taskData.doneTasks.isEmpty() &&
|
||||
taskData.archivedTasks.isEmpty()
|
||||
val hasNoTasks = taskData.columns.all { it.tasks.isEmpty() }
|
||||
|
||||
if (hasNoTasks) {
|
||||
Box(
|
||||
@@ -219,11 +216,8 @@ fun AllTasksScreen(
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
TaskKanbanView(
|
||||
upcomingTasks = taskData.upcomingTasks,
|
||||
inProgressTasks = taskData.inProgressTasks,
|
||||
doneTasks = taskData.doneTasks,
|
||||
archivedTasks = taskData.archivedTasks,
|
||||
DynamicTaskKanbanView(
|
||||
columns = taskData.columns,
|
||||
onCompleteTask = { task ->
|
||||
selectedTask = task
|
||||
showCompleteDialog = true
|
||||
|
||||
@@ -300,6 +300,7 @@ fun EditTaskScreen(
|
||||
category = selectedCategory!!.id,
|
||||
frequency = selectedFrequency!!.id,
|
||||
priority = selectedPriority!!.id,
|
||||
status = selectedStatus!!.id,
|
||||
dueDate = dueDate,
|
||||
estimatedCost = estimatedCost.ifBlank { null }
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@ import com.mycrib.android.ui.components.common.InfoCard
|
||||
import com.mycrib.android.ui.components.residence.PropertyDetailItem
|
||||
import com.mycrib.android.ui.components.residence.DetailRow
|
||||
import com.mycrib.android.ui.components.task.TaskCard
|
||||
import com.mycrib.android.ui.components.task.TaskKanbanView
|
||||
import com.mycrib.android.ui.components.task.DynamicTaskKanbanView
|
||||
import com.mycrib.android.viewmodel.ResidenceViewModel
|
||||
import com.mycrib.android.viewmodel.TaskCompletionViewModel
|
||||
import com.mycrib.android.viewmodel.TaskViewModel
|
||||
@@ -393,7 +393,8 @@ fun ResidenceDetailScreen(
|
||||
}
|
||||
is ApiResult.Success -> {
|
||||
val taskData = (tasksState as ApiResult.Success).data
|
||||
if (taskData.upcomingTasks.isEmpty() && taskData.inProgressTasks.isEmpty() && taskData.doneTasks.isEmpty() && taskData.archivedTasks.isEmpty()) {
|
||||
val allTasksEmpty = taskData.columns.all { it.tasks.isEmpty() }
|
||||
if (allTasksEmpty) {
|
||||
item {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -432,11 +433,8 @@ fun ResidenceDetailScreen(
|
||||
.fillMaxWidth()
|
||||
.height(500.dp)
|
||||
) {
|
||||
TaskKanbanView(
|
||||
upcomingTasks = taskData.upcomingTasks,
|
||||
inProgressTasks = taskData.inProgressTasks,
|
||||
doneTasks = taskData.doneTasks,
|
||||
archivedTasks = taskData.archivedTasks,
|
||||
DynamicTaskKanbanView(
|
||||
columns = taskData.columns,
|
||||
onCompleteTask = { task ->
|
||||
selectedTask = task
|
||||
showCompleteDialog = true
|
||||
|
||||
@@ -26,8 +26,7 @@ fun TasksScreen(
|
||||
) {
|
||||
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 expandedColumns by remember { mutableStateOf(setOf<String>()) }
|
||||
var showCompleteDialog by remember { mutableStateOf(false) }
|
||||
var selectedTask by remember { mutableStateOf<com.mycrib.shared.models.TaskDetail?>(null) }
|
||||
|
||||
@@ -93,9 +92,7 @@ fun TasksScreen(
|
||||
}
|
||||
is ApiResult.Success -> {
|
||||
val taskData = (tasksState as ApiResult.Success).data
|
||||
val hasNoTasks = taskData.upcomingTasks.isEmpty() &&
|
||||
taskData.inProgressTasks.isEmpty() &&
|
||||
taskData.doneTasks.isEmpty()
|
||||
val hasNoTasks = taskData.columns.all { it.tasks.isEmpty() }
|
||||
|
||||
if (hasNoTasks) {
|
||||
Box(
|
||||
@@ -140,152 +137,106 @@ fun TasksScreen(
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
// Task summary pills
|
||||
// Task summary pills - dynamically generated from all columns
|
||||
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 = {}
|
||||
taskData.columns.forEach { column ->
|
||||
TaskPill(
|
||||
count = column.count,
|
||||
label = column.displayName,
|
||||
color = hexToColor(column.color)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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"
|
||||
// Dynamically render all columns
|
||||
taskData.columns.forEachIndexed { index, column ->
|
||||
if (column.tasks.isNotEmpty()) {
|
||||
// First column (index 0) expanded by default, others collapsible
|
||||
if (index == 0) {
|
||||
// First column - always expanded, show tasks directly
|
||||
item {
|
||||
Text(
|
||||
text = "${column.displayName} (${column.tasks.size})",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showDoneTasks) {
|
||||
items(taskData.doneTasks) { task ->
|
||||
TaskCard(
|
||||
task = task,
|
||||
onCompleteClick = { /* TODO */ },
|
||||
onEditClick = { /* TODO */ },
|
||||
onUncancelClick = {},
|
||||
onCancelClick = {}
|
||||
)
|
||||
items(column.tasks) { task ->
|
||||
TaskCard(
|
||||
task = task,
|
||||
onCompleteClick = {
|
||||
selectedTask = task
|
||||
showCompleteDialog = true
|
||||
},
|
||||
onEditClick = { },
|
||||
onCancelClick = { },
|
||||
onUncancelClick = { }
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Other columns - collapsible
|
||||
val isExpanded = expandedColumns.contains(column.name)
|
||||
|
||||
item {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = {
|
||||
expandedColumns = if (isExpanded) {
|
||||
expandedColumns - column.name
|
||||
} else {
|
||||
expandedColumns + column.name
|
||||
}
|
||||
}
|
||||
) {
|
||||
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(
|
||||
getIconFromName(column.icons["android"] ?: "List"),
|
||||
contentDescription = null,
|
||||
tint = hexToColor(column.color)
|
||||
)
|
||||
Text(
|
||||
text = "${column.displayName} (${column.tasks.size})",
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
if (isExpanded) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown,
|
||||
contentDescription = if (isExpanded) "Collapse" else "Expand"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isExpanded) {
|
||||
items(column.tasks) { task ->
|
||||
TaskCard(
|
||||
task = task,
|
||||
onCompleteClick = {
|
||||
selectedTask = task
|
||||
showCompleteDialog = true
|
||||
},
|
||||
onEditClick = { },
|
||||
onCancelClick = { },
|
||||
onUncancelClick = { }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,3 +300,79 @@ private fun TaskPill(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getColumnColor(columnName: String): androidx.compose.ui.graphics.Color {
|
||||
return when (columnName) {
|
||||
"upcoming_tasks" -> MaterialTheme.colorScheme.primary
|
||||
"in_progress_tasks" -> MaterialTheme.colorScheme.tertiary
|
||||
"done_tasks" -> MaterialTheme.colorScheme.secondary
|
||||
"archived_tasks" -> MaterialTheme.colorScheme.outline
|
||||
else -> MaterialTheme.colorScheme.primary // Default color for unknown columns
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getColumnIcon(columnName: String): androidx.compose.ui.graphics.vector.ImageVector {
|
||||
return when (columnName) {
|
||||
"upcoming_tasks" -> Icons.Default.CalendarToday
|
||||
"in_progress_tasks" -> Icons.Default.PlayArrow
|
||||
"done_tasks" -> Icons.Default.CheckCircle
|
||||
"archived_tasks" -> Icons.Default.Archive
|
||||
else -> Icons.Default.List // Default icon for unknown columns
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to convert icon name string to ImageVector
|
||||
*/
|
||||
private fun getIconFromName(iconName: String): androidx.compose.ui.graphics.vector.ImageVector {
|
||||
return when (iconName) {
|
||||
"CalendarToday" -> Icons.Default.CalendarToday
|
||||
"PlayCircle" -> Icons.Default.PlayCircle
|
||||
"PlayArrow" -> Icons.Default.PlayArrow
|
||||
"CheckCircle" -> Icons.Default.CheckCircle
|
||||
"Archive" -> Icons.Default.Archive
|
||||
"List" -> Icons.Default.List
|
||||
"Unarchive" -> Icons.Default.Unarchive
|
||||
else -> Icons.Default.List // Default fallback
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to convert hex color string to Color
|
||||
* Supports formats: #RGB, #RRGGBB, #AARRGGBB
|
||||
* Platform-independent implementation
|
||||
*/
|
||||
private fun hexToColor(hex: String): androidx.compose.ui.graphics.Color {
|
||||
val cleanHex = hex.removePrefix("#")
|
||||
return try {
|
||||
when (cleanHex.length) {
|
||||
3 -> {
|
||||
// RGB format - expand to RRGGBB
|
||||
val r = cleanHex[0].toString().repeat(2).toInt(16)
|
||||
val g = cleanHex[1].toString().repeat(2).toInt(16)
|
||||
val b = cleanHex[2].toString().repeat(2).toInt(16)
|
||||
androidx.compose.ui.graphics.Color(red = r / 255f, green = g / 255f, blue = b / 255f)
|
||||
}
|
||||
6 -> {
|
||||
// RRGGBB format
|
||||
val r = cleanHex.substring(0, 2).toInt(16)
|
||||
val g = cleanHex.substring(2, 4).toInt(16)
|
||||
val b = cleanHex.substring(4, 6).toInt(16)
|
||||
androidx.compose.ui.graphics.Color(red = r / 255f, green = g / 255f, blue = b / 255f)
|
||||
}
|
||||
8 -> {
|
||||
// AARRGGBB format
|
||||
val a = cleanHex.substring(0, 2).toInt(16)
|
||||
val r = cleanHex.substring(2, 4).toInt(16)
|
||||
val g = cleanHex.substring(4, 6).toInt(16)
|
||||
val b = cleanHex.substring(6, 8).toInt(16)
|
||||
androidx.compose.ui.graphics.Color(red = r / 255f, green = g / 255f, blue = b / 255f, alpha = a / 255f)
|
||||
}
|
||||
else -> androidx.compose.ui.graphics.Color.Gray // Default fallback
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
androidx.compose.ui.graphics.Color.Gray // Fallback on parse error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import com.mycrib.shared.models.Residence
|
||||
import com.mycrib.shared.models.ResidenceCreateRequest
|
||||
import com.mycrib.shared.models.ResidenceSummaryResponse
|
||||
import com.mycrib.shared.models.MyResidencesResponse
|
||||
import com.mycrib.shared.models.TasksByResidenceResponse
|
||||
import com.mycrib.shared.models.TaskColumnsResponse
|
||||
import com.mycrib.shared.network.ApiResult
|
||||
import com.mycrib.shared.network.ResidenceApi
|
||||
import com.mycrib.shared.network.TaskApi
|
||||
@@ -31,8 +31,8 @@ class ResidenceViewModel : ViewModel() {
|
||||
private val _updateResidenceState = MutableStateFlow<ApiResult<Residence>>(ApiResult.Idle)
|
||||
val updateResidenceState: StateFlow<ApiResult<Residence>> = _updateResidenceState
|
||||
|
||||
private val _residenceTasksState = MutableStateFlow<ApiResult<TasksByResidenceResponse>>(ApiResult.Idle)
|
||||
val residenceTasksState: StateFlow<ApiResult<TasksByResidenceResponse>> = _residenceTasksState
|
||||
private val _residenceTasksState = MutableStateFlow<ApiResult<TaskColumnsResponse>>(ApiResult.Idle)
|
||||
val residenceTasksState: StateFlow<ApiResult<TaskColumnsResponse>> = _residenceTasksState
|
||||
|
||||
private val _myResidencesState = MutableStateFlow<ApiResult<MyResidencesResponse>>(ApiResult.Idle)
|
||||
val myResidencesState: StateFlow<ApiResult<MyResidencesResponse>> = _myResidencesState
|
||||
|
||||
@@ -2,10 +2,9 @@ package com.mycrib.android.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.mycrib.shared.models.AllTasksResponse
|
||||
import com.mycrib.shared.models.TaskColumnsResponse
|
||||
import com.mycrib.shared.models.CustomTask
|
||||
import com.mycrib.shared.models.TaskCreateRequest
|
||||
import com.mycrib.shared.models.TasksByResidenceResponse
|
||||
import com.mycrib.shared.network.ApiResult
|
||||
import com.mycrib.shared.network.TaskApi
|
||||
import com.mycrib.storage.TokenStorage
|
||||
@@ -16,11 +15,11 @@ import kotlinx.coroutines.launch
|
||||
class TaskViewModel : ViewModel() {
|
||||
private val taskApi = TaskApi()
|
||||
|
||||
private val _tasksState = MutableStateFlow<ApiResult<AllTasksResponse>>(ApiResult.Idle)
|
||||
val tasksState: StateFlow<ApiResult<AllTasksResponse>> = _tasksState
|
||||
private val _tasksState = MutableStateFlow<ApiResult<TaskColumnsResponse>>(ApiResult.Idle)
|
||||
val tasksState: StateFlow<ApiResult<TaskColumnsResponse>> = _tasksState
|
||||
|
||||
private val _tasksByResidenceState = MutableStateFlow<ApiResult<TasksByResidenceResponse>>(ApiResult.Idle)
|
||||
val tasksByResidenceState: StateFlow<ApiResult<TasksByResidenceResponse>> = _tasksByResidenceState
|
||||
private val _tasksByResidenceState = MutableStateFlow<ApiResult<TaskColumnsResponse>>(ApiResult.Idle)
|
||||
val tasksByResidenceState: StateFlow<ApiResult<TaskColumnsResponse>> = _tasksByResidenceState
|
||||
|
||||
private val _taskAddNewCustomTaskState = MutableStateFlow<ApiResult<CustomTask>>(ApiResult.Idle)
|
||||
val taskAddNewCustomTaskState: StateFlow<ApiResult<CustomTask>> = _taskAddNewCustomTaskState
|
||||
@@ -74,6 +73,48 @@ class TaskViewModel : ViewModel() {
|
||||
_taskAddNewCustomTaskState.value = ApiResult.Idle
|
||||
}
|
||||
|
||||
fun cancelTask(taskId: Int, onComplete: (Boolean) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
when (val result = taskApi.cancelTask(token, taskId)) {
|
||||
is ApiResult.Success -> {
|
||||
onComplete(true)
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
onComplete(false)
|
||||
}
|
||||
else -> {
|
||||
onComplete(false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
onComplete(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun uncancelTask(taskId: Int, onComplete: (Boolean) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
when (val result = taskApi.uncancelTask(token, taskId)) {
|
||||
is ApiResult.Success -> {
|
||||
onComplete(true)
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
onComplete(false)
|
||||
}
|
||||
else -> {
|
||||
onComplete(false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
onComplete(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun markInProgress(taskId: Int, onComplete: (Boolean) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
val token = TokenStorage.getToken()
|
||||
|
||||
Reference in New Issue
Block a user