Add task template suggestions for quick task creation
- Add TaskTemplate model with category grouping support - Add TaskTemplateApi for fetching templates from backend - Add TaskSuggestionDropdown component for Android task form - Add TaskTemplatesBrowserSheet for browsing all templates - Add TaskSuggestionsView and TaskTemplatesBrowserView for iOS - Update DataManager to cache task templates - Update APILayer with template fetch and search methods - Update TaskFormView (iOS) with template suggestions - Update AddTaskDialog (Android) with template suggestions - Update onboarding task view to use templates 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,21 @@
|
||||
package com.example.casera.ui.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ChevronRight
|
||||
import androidx.compose.material.icons.filled.List
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.casera.data.DataManager
|
||||
import com.example.casera.models.TaskTemplate
|
||||
import com.example.casera.repository.LookupsRepository
|
||||
import com.example.casera.models.MyResidencesResponse
|
||||
import com.example.casera.models.TaskCategory
|
||||
@@ -54,10 +61,54 @@ fun AddTaskDialog(
|
||||
var dueDateError by remember { mutableStateOf(false) }
|
||||
var residenceError by remember { mutableStateOf(false) }
|
||||
|
||||
// Get data from LookupsRepository
|
||||
// Template suggestions state
|
||||
var showTemplatesBrowser by remember { mutableStateOf(false) }
|
||||
var showSuggestions by remember { mutableStateOf(false) }
|
||||
|
||||
// Get data from LookupsRepository and DataManager
|
||||
val frequencies by LookupsRepository.taskFrequencies.collectAsState()
|
||||
val priorities by LookupsRepository.taskPriorities.collectAsState()
|
||||
val categories by LookupsRepository.taskCategories.collectAsState()
|
||||
val allTemplates by DataManager.taskTemplates.collectAsState()
|
||||
|
||||
// Search templates locally
|
||||
val filteredSuggestions = remember(title, allTemplates) {
|
||||
if (title.length >= 2) {
|
||||
DataManager.searchTaskTemplates(title)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to apply a task template
|
||||
fun selectTaskTemplate(template: TaskTemplate) {
|
||||
title = template.title
|
||||
description = template.description
|
||||
|
||||
// Auto-select matching category by ID or name
|
||||
template.categoryId?.let { catId ->
|
||||
categories.find { it.id == catId }?.let {
|
||||
category = it
|
||||
}
|
||||
} ?: template.category?.let { cat ->
|
||||
categories.find { it.name.equals(cat.name, ignoreCase = true) }?.let {
|
||||
category = it
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-select matching frequency by ID or name
|
||||
template.frequencyId?.let { freqId ->
|
||||
frequencies.find { it.id == freqId }?.let {
|
||||
frequency = it
|
||||
}
|
||||
} ?: template.frequency?.let { freq ->
|
||||
frequencies.find { it.name.equals(freq.name, ignoreCase = true) }?.let {
|
||||
frequency = it
|
||||
}
|
||||
}
|
||||
|
||||
showSuggestions = false
|
||||
}
|
||||
|
||||
// Set defaults when data loads
|
||||
LaunchedEffect(frequencies) {
|
||||
@@ -121,21 +172,77 @@ fun AddTaskDialog(
|
||||
}
|
||||
}
|
||||
|
||||
// Title
|
||||
OutlinedTextField(
|
||||
value = title,
|
||||
onValueChange = {
|
||||
title = it
|
||||
titleError = false
|
||||
},
|
||||
label = { Text("Title *") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = titleError,
|
||||
supportingText = if (titleError) {
|
||||
{ Text("Title is required") }
|
||||
} else null,
|
||||
singleLine = true
|
||||
)
|
||||
// Browse Templates Button
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { showTemplatesBrowser = true },
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.List,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = "Browse Task Templates",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Text(
|
||||
text = "${allTemplates.size} common tasks",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
imageVector = Icons.Default.ChevronRight,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))
|
||||
|
||||
// Title with inline suggestions
|
||||
Column {
|
||||
OutlinedTextField(
|
||||
value = title,
|
||||
onValueChange = {
|
||||
title = it
|
||||
titleError = false
|
||||
showSuggestions = it.length >= 2 && filteredSuggestions.isNotEmpty()
|
||||
},
|
||||
label = { Text("Title *") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = titleError,
|
||||
supportingText = if (titleError) {
|
||||
{ Text("Title is required") }
|
||||
} else null,
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
// Inline suggestions dropdown
|
||||
if (showSuggestions && filteredSuggestions.isNotEmpty()) {
|
||||
TaskSuggestionDropdown(
|
||||
suggestions = filteredSuggestions,
|
||||
onSelect = { template ->
|
||||
selectTaskTemplate(template)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Description
|
||||
OutlinedTextField(
|
||||
@@ -361,6 +468,17 @@ fun AddTaskDialog(
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Templates browser sheet
|
||||
if (showTemplatesBrowser) {
|
||||
TaskTemplatesBrowserSheet(
|
||||
onDismiss = { showTemplatesBrowser = false },
|
||||
onSelect = { template ->
|
||||
selectTaskTemplate(template)
|
||||
showTemplatesBrowser = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to validate date format
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
package com.example.casera.ui.components
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
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.ChevronRight
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.casera.models.TaskTemplate
|
||||
|
||||
/**
|
||||
* Dropdown showing filtered task suggestions based on user input.
|
||||
* Uses TaskTemplate from backend API.
|
||||
*/
|
||||
@Composable
|
||||
fun TaskSuggestionDropdown(
|
||||
suggestions: List<TaskTemplate>,
|
||||
onSelect: (TaskTemplate) -> Unit,
|
||||
maxSuggestions: Int = 5,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = suggestions.isNotEmpty(),
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically(),
|
||||
modifier = modifier
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||
),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.heightIn(max = 250.dp)
|
||||
) {
|
||||
items(
|
||||
items = suggestions.take(maxSuggestions),
|
||||
key = { it.id }
|
||||
) { template ->
|
||||
TaskSuggestionItem(
|
||||
template = template,
|
||||
onClick = { onSelect(template) }
|
||||
)
|
||||
if (template != suggestions.take(maxSuggestions).last()) {
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(start = 52.dp),
|
||||
color = MaterialTheme.colorScheme.outlineVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TaskSuggestionItem(
|
||||
template: TaskTemplate,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 12.dp, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
// Category-colored icon placeholder
|
||||
Surface(
|
||||
modifier = Modifier.size(28.dp),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = getCategoryColor(template.categoryName.lowercase())
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Text(
|
||||
text = template.title.first().toString(),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Task info
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
Text(
|
||||
text = template.title,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Medium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = template.categoryName,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Text(
|
||||
text = "•",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Text(
|
||||
text = template.frequencyDisplay,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Chevron
|
||||
Icon(
|
||||
imageVector = Icons.Default.ChevronRight,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun getCategoryColor(category: String): androidx.compose.ui.graphics.Color {
|
||||
return when (category.lowercase()) {
|
||||
"plumbing" -> MaterialTheme.colorScheme.secondary
|
||||
"safety", "electrical" -> MaterialTheme.colorScheme.error
|
||||
"hvac" -> MaterialTheme.colorScheme.primary
|
||||
"appliances" -> MaterialTheme.colorScheme.tertiary
|
||||
"exterior", "lawn & garden" -> androidx.compose.ui.graphics.Color(0xFF34C759)
|
||||
"interior" -> androidx.compose.ui.graphics.Color(0xFFAF52DE)
|
||||
"general", "seasonal" -> androidx.compose.ui.graphics.Color(0xFFFF9500)
|
||||
else -> MaterialTheme.colorScheme.primary
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
package com.example.casera.ui.components
|
||||
|
||||
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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.casera.data.DataManager
|
||||
import com.example.casera.models.TaskTemplate
|
||||
import com.example.casera.models.TaskTemplateCategoryGroup
|
||||
|
||||
/**
|
||||
* Bottom sheet for browsing all task templates from backend.
|
||||
* Uses DataManager to access cached templates.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TaskTemplatesBrowserSheet(
|
||||
onDismiss: () -> Unit,
|
||||
onSelect: (TaskTemplate) -> Unit
|
||||
) {
|
||||
var searchText by remember { mutableStateOf("") }
|
||||
var expandedCategories by remember { mutableStateOf(setOf<String>()) }
|
||||
|
||||
// Get templates from DataManager
|
||||
val groupedTemplates by DataManager.taskTemplatesGrouped.collectAsState()
|
||||
val allTemplates by DataManager.taskTemplates.collectAsState()
|
||||
|
||||
val filteredTemplates = remember(searchText, allTemplates) {
|
||||
if (searchText.isBlank()) emptyList()
|
||||
else DataManager.searchTaskTemplates(searchText)
|
||||
}
|
||||
|
||||
val isSearching = searchText.isNotBlank()
|
||||
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight(0.9f)
|
||||
) {
|
||||
// Header
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "Task Templates",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text("Done")
|
||||
}
|
||||
}
|
||||
|
||||
// Search bar
|
||||
OutlinedTextField(
|
||||
value = searchText,
|
||||
onValueChange = { searchText = it },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
placeholder = { Text("Search templates...") },
|
||||
leadingIcon = {
|
||||
Icon(Icons.Default.Search, contentDescription = null)
|
||||
},
|
||||
trailingIcon = {
|
||||
if (searchText.isNotEmpty()) {
|
||||
IconButton(onClick = { searchText = "" }) {
|
||||
Icon(Icons.Default.Clear, contentDescription = "Clear")
|
||||
}
|
||||
}
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
// Content
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = 32.dp)
|
||||
) {
|
||||
if (isSearching) {
|
||||
// Search results
|
||||
if (filteredTemplates.isEmpty()) {
|
||||
item {
|
||||
EmptySearchState()
|
||||
}
|
||||
} else {
|
||||
item {
|
||||
Text(
|
||||
text = "${filteredTemplates.size} ${if (filteredTemplates.size == 1) "result" else "results"}",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
}
|
||||
items(filteredTemplates, key = { it.id }) { template ->
|
||||
TaskTemplateItem(
|
||||
template = template,
|
||||
onClick = {
|
||||
onSelect(template)
|
||||
onDismiss()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Browse by category
|
||||
val categories = groupedTemplates?.categories ?: emptyList()
|
||||
|
||||
if (categories.isEmpty()) {
|
||||
item {
|
||||
EmptyTemplatesState()
|
||||
}
|
||||
} else {
|
||||
categories.forEach { categoryGroup ->
|
||||
val categoryKey = categoryGroup.categoryName
|
||||
val isExpanded = expandedCategories.contains(categoryKey)
|
||||
|
||||
item(key = "category_$categoryKey") {
|
||||
CategoryHeader(
|
||||
categoryGroup = categoryGroup,
|
||||
isExpanded = isExpanded,
|
||||
onClick = {
|
||||
expandedCategories = if (isExpanded) {
|
||||
expandedCategories - categoryKey
|
||||
} else {
|
||||
expandedCategories + categoryKey
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (isExpanded) {
|
||||
items(categoryGroup.templates, key = { it.id }) { template ->
|
||||
TaskTemplateItem(
|
||||
template = template,
|
||||
onClick = {
|
||||
onSelect(template)
|
||||
onDismiss()
|
||||
},
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CategoryHeader(
|
||||
categoryGroup: TaskTemplateCategoryGroup,
|
||||
isExpanded: Boolean,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick),
|
||||
color = MaterialTheme.colorScheme.surface
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
// Category icon
|
||||
Surface(
|
||||
modifier = Modifier.size(32.dp),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = getCategoryColor(categoryGroup.categoryName.lowercase())
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Icon(
|
||||
imageVector = getCategoryIcon(categoryGroup.categoryName.lowercase()),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp),
|
||||
tint = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Category name
|
||||
Text(
|
||||
text = categoryGroup.categoryName,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
// Count badge
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = MaterialTheme.colorScheme.surfaceVariant
|
||||
) {
|
||||
Text(
|
||||
text = categoryGroup.count.toString(),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
)
|
||||
}
|
||||
|
||||
// Expand/collapse indicator
|
||||
Icon(
|
||||
imageVector = if (isExpanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore,
|
||||
contentDescription = if (isExpanded) "Collapse" else "Expand",
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TaskTemplateItem(
|
||||
template: TaskTemplate,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick),
|
||||
color = MaterialTheme.colorScheme.surface
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
// Icon placeholder
|
||||
Surface(
|
||||
modifier = Modifier.size(24.dp),
|
||||
shape = MaterialTheme.shapes.extraSmall,
|
||||
color = getCategoryColor(template.categoryName.lowercase()).copy(alpha = 0.2f)
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Text(
|
||||
text = template.title.first().toString(),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = getCategoryColor(template.categoryName.lowercase())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Task info
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
Text(
|
||||
text = template.title,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = template.frequencyDisplay,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
// Add indicator
|
||||
Icon(
|
||||
imageVector = Icons.Default.AddCircleOutline,
|
||||
contentDescription = "Add",
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmptySearchState() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 48.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Search,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(48.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
|
||||
)
|
||||
Text(
|
||||
text = "No Templates Found",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
Text(
|
||||
text = "Try a different search term",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmptyTemplatesState() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 48.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Checklist,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(48.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
|
||||
)
|
||||
Text(
|
||||
text = "No Templates Available",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
Text(
|
||||
text = "Templates will appear here once loaded",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getCategoryIcon(category: String): androidx.compose.ui.graphics.vector.ImageVector {
|
||||
return when (category.lowercase()) {
|
||||
"plumbing" -> Icons.Default.Water
|
||||
"safety" -> Icons.Default.Shield
|
||||
"electrical" -> Icons.Default.ElectricBolt
|
||||
"hvac" -> Icons.Default.Thermostat
|
||||
"appliances" -> Icons.Default.Kitchen
|
||||
"exterior" -> Icons.Default.Home
|
||||
"lawn & garden" -> Icons.Default.Park
|
||||
"interior" -> Icons.Default.Weekend
|
||||
"general", "seasonal" -> Icons.Default.CalendarMonth
|
||||
else -> Icons.Default.Checklist
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user