- Add AllTasksView and PlantEditView components - Update CoreDataStack CloudKit container ID - Improve CameraView and IdentificationViewModel - Update MainTabView, RoomsListView, UpcomingTasksSection - Minor fixes to PlantGuideApp and SettingsViewModel Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
151 lines
4.3 KiB
Swift
151 lines
4.3 KiB
Swift
//
|
|
// PlantEditView.swift
|
|
// PlantGuide
|
|
//
|
|
// Created on 2026-01-31.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
// MARK: - PlantEditView
|
|
|
|
/// A view for editing plant details like custom name and notes.
|
|
@MainActor
|
|
struct PlantEditView: View {
|
|
|
|
// MARK: - Properties
|
|
|
|
/// The plant being edited
|
|
let plant: Plant
|
|
|
|
/// Callback when save is tapped
|
|
let onSave: (String?, String?) async -> Void
|
|
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
@State private var customName: String
|
|
@State private var notes: String
|
|
@State private var isSaving = false
|
|
|
|
// MARK: - Initialization
|
|
|
|
init(plant: Plant, onSave: @escaping (String?, String?) async -> Void) {
|
|
self.plant = plant
|
|
self.onSave = onSave
|
|
_customName = State(initialValue: plant.customName ?? plant.commonNames.first ?? plant.scientificName)
|
|
_notes = State(initialValue: plant.notes ?? "")
|
|
}
|
|
|
|
// MARK: - Computed Properties
|
|
|
|
private var isValid: Bool {
|
|
!customName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
}
|
|
|
|
// MARK: - Body
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
Form {
|
|
Section {
|
|
TextField("Plant Name", text: $customName)
|
|
.autocorrectionDisabled()
|
|
} header: {
|
|
Text("Name")
|
|
} footer: {
|
|
Text("Give your plant a custom name to easily identify it.")
|
|
}
|
|
|
|
Section {
|
|
TextEditor(text: $notes)
|
|
.frame(minHeight: 100)
|
|
} header: {
|
|
Text("Notes")
|
|
} footer: {
|
|
Text("Add any notes about your plant, such as where you got it or special care instructions.")
|
|
}
|
|
|
|
Section {
|
|
plantInfoRow
|
|
} header: {
|
|
Text("Plant Info")
|
|
}
|
|
}
|
|
.navigationTitle("Edit Plant")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) {
|
|
Button("Cancel") {
|
|
dismiss()
|
|
}
|
|
}
|
|
ToolbarItem(placement: .confirmationAction) {
|
|
Button("Save") {
|
|
isSaving = true
|
|
Task {
|
|
let trimmedName = customName.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let trimmedNotes = notes.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
await onSave(
|
|
trimmedName.isEmpty ? nil : trimmedName,
|
|
trimmedNotes.isEmpty ? nil : trimmedNotes
|
|
)
|
|
dismiss()
|
|
}
|
|
}
|
|
.disabled(!isValid || isSaving)
|
|
}
|
|
}
|
|
.interactiveDismissDisabled(isSaving)
|
|
}
|
|
}
|
|
|
|
// MARK: - Plant Info Row
|
|
|
|
private var plantInfoRow: some View {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
HStack {
|
|
Text("Scientific Name")
|
|
Spacer()
|
|
Text(plant.scientificName)
|
|
.foregroundStyle(.secondary)
|
|
.italic()
|
|
}
|
|
|
|
if !plant.commonNames.isEmpty {
|
|
HStack {
|
|
Text("Common Name")
|
|
Spacer()
|
|
Text(plant.commonNames.first ?? "")
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
|
|
HStack {
|
|
Text("Family")
|
|
Spacer()
|
|
Text(plant.family)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Preview
|
|
|
|
#Preview {
|
|
PlantEditView(
|
|
plant: Plant(
|
|
scientificName: "Monstera deliciosa",
|
|
commonNames: ["Swiss Cheese Plant"],
|
|
family: "Araceae",
|
|
genus: "Monstera",
|
|
imageURLs: [],
|
|
identificationSource: .onDeviceML,
|
|
notes: "Got this from the local nursery",
|
|
customName: "My Monstera"
|
|
)
|
|
) { name, notes in
|
|
print("Save: \(name ?? "nil"), \(notes ?? "nil")")
|
|
}
|
|
}
|