Files
honeyDueKMP/iosApp/iosApp/Task/TaskSuggestionsView.swift
Trey t 9c574c4343 Harden iOS app with audit fixes, UI consistency, and sheet race condition fixes
Applies verified fixes from deep audit (concurrency, performance, security,
accessibility), standardizes CRUD form buttons to Add/Save pattern, removes
.drawingGroup() that broke search bar TextFields, and converts vulnerable
.sheet(isPresented:) + if-let patterns to safe presentation to prevent
blank white modals.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 09:59:56 -06:00

91 lines
3.2 KiB
Swift

import SwiftUI
import ComposeApp
/// Inline dropdown showing filtered task template suggestions
struct TaskSuggestionsView: View {
let suggestions: [TaskTemplate]
let onSelect: (TaskTemplate) -> Void
let maxSuggestions: Int
init(
suggestions: [TaskTemplate],
onSelect: @escaping (TaskTemplate) -> Void,
maxSuggestions: Int = 5
) {
self.suggestions = suggestions
self.onSelect = onSelect
self.maxSuggestions = maxSuggestions
}
var body: some View {
VStack(spacing: 0) {
ForEach(Array(suggestions.prefix(maxSuggestions).enumerated()), id: \.element.id) { index, template in
Button {
onSelect(template)
} label: {
HStack(spacing: 12) {
// Category-colored icon
Image(systemName: template.iconIos.isEmpty ? "checklist" : template.iconIos)
.font(.system(size: 18))
.foregroundColor(Color.taskCategoryColor(for: template.categoryName))
.frame(width: 28, height: 28)
// Task info
VStack(alignment: .leading, spacing: 2) {
Text(template.title)
.font(.subheadline)
.fontWeight(.medium)
.foregroundColor(Color.appTextPrimary)
.lineLimit(1)
HStack(spacing: 4) {
Text(template.categoryName)
.font(.caption)
.foregroundColor(Color.appTextSecondary)
Text("")
.font(.caption)
.foregroundColor(Color.appTextSecondary)
Text(template.frequencyDisplay)
.font(.caption)
.foregroundColor(Color.appTextSecondary)
}
}
Spacer()
// Chevron
Image(systemName: "chevron.right")
.font(.caption)
.foregroundColor(Color.appTextSecondary)
}
.padding(.horizontal, 12)
.padding(.vertical, 10)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
// Divider between items (not after last item)
if index < min(suggestions.count, maxSuggestions) - 1 {
Divider()
.padding(.leading, 52)
}
}
}
.background(Color.appBackgroundSecondary)
.clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
.naturalShadow(.medium)
}
}
#Preview {
VStack(spacing: 20) {
Text("Preview requires backend templates to be loaded")
.foregroundColor(.secondary)
Spacer()
}
.background(Color.appBackgroundPrimary)
}