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:
Trey t
2025-12-05 09:06:58 -06:00
parent fd8f6d612c
commit 771f5d2bd3
15 changed files with 1585 additions and 83 deletions

View File

@@ -91,6 +91,11 @@ struct TaskFormView: View {
// Error alert state
@State private var errorAlert: ErrorAlertInfo? = nil
// Template suggestions
@State private var showingTemplatesBrowser = false
@State private var filteredSuggestions: [TaskTemplate] = []
@State private var showSuggestions = false
var body: some View {
NavigationStack {
ZStack {
@@ -120,9 +125,60 @@ struct TaskFormView: View {
.listRowBackground(Color.appBackgroundSecondary)
}
// Browse Templates Button (only for new tasks)
if !isEditMode {
Section {
Button {
showingTemplatesBrowser = true
} label: {
HStack {
Image(systemName: "list.bullet.rectangle")
.font(.system(size: 18))
.foregroundColor(Color.appPrimary)
.frame(width: 28)
Text("Browse Task Templates")
.foregroundColor(Color.appTextPrimary)
Spacer()
Text("\(dataManager.taskTemplateCount) tasks")
.font(.caption)
.foregroundColor(Color.appTextSecondary)
Image(systemName: "chevron.right")
.font(.caption)
.foregroundColor(Color.appTextSecondary)
}
}
} header: {
Text("Quick Start")
} footer: {
Text("Choose from common home maintenance tasks or create your own below")
.font(.caption)
.foregroundColor(Color.appTextSecondary)
}
.listRowBackground(Color.appBackgroundSecondary)
}
Section {
TextField(L10n.Tasks.titleLabel, text: $title)
.focused($focusedField, equals: .title)
VStack(alignment: .leading, spacing: 8) {
TextField(L10n.Tasks.titleLabel, text: $title)
.focused($focusedField, equals: .title)
.onChange(of: title) { newValue in
updateSuggestions(query: newValue)
}
// Inline suggestions dropdown
if showSuggestions && !filteredSuggestions.isEmpty && focusedField == .title {
TaskSuggestionsView(
suggestions: filteredSuggestions,
onSelect: { template in
selectTaskTemplate(template)
}
)
}
}
if !titleError.isEmpty {
Text(titleError)
@@ -286,9 +342,53 @@ struct TaskFormView: View {
errorAlert = nil
}
)
.sheet(isPresented: $showingTemplatesBrowser) {
TaskTemplatesBrowserView { template in
selectTaskTemplate(template)
}
}
}
}
// MARK: - Suggestions Helpers
private func updateSuggestions(query: String) {
if query.count >= 2 {
filteredSuggestions = dataManager.searchTaskTemplates(query: query)
showSuggestions = !filteredSuggestions.isEmpty
} else {
filteredSuggestions = []
showSuggestions = false
}
}
private func selectTaskTemplate(_ template: TaskTemplate) {
// Fill in the title
title = template.title
// Fill in description if available
description = template.description_
// Auto-select matching category by ID or name
if let categoryId = template.categoryId {
selectedCategory = taskCategories.first(where: { $0.id == Int(categoryId.int32Value) })
} else if let category = template.category {
selectedCategory = taskCategories.first(where: { $0.name.lowercased() == category.name.lowercased() })
}
// Auto-select matching frequency by ID or name
if let frequencyId = template.frequencyId {
selectedFrequency = taskFrequencies.first(where: { $0.id == Int(frequencyId.int32Value) })
} else if let frequency = template.frequency {
selectedFrequency = taskFrequencies.first(where: { $0.name.lowercased() == frequency.name.lowercased() })
}
// Clear suggestions and dismiss keyboard
showSuggestions = false
filteredSuggestions = []
focusedField = nil
}
private func setDefaults() {
// Set default values if not already set
if selectedCategory == nil && !taskCategories.isEmpty {