Andromida/Andromida/App/Views/Settings/CategoryEditSheet.swift

157 lines
5.8 KiB
Swift

import SwiftUI
import Bedrock
/// Sheet for creating or editing a category
struct CategoryEditSheet: View {
@Bindable var store: CategoryStore
let category: Category?
@Environment(\.dismiss) private var dismiss
@State private var name: String = ""
@State private var selectedColorName: String = "gray"
@State private var showingDeleteConfirmation = false
private var isEditing: Bool { category != nil }
private var isPreset: Bool { category?.isPreset ?? false }
private var canSave: Bool {
let trimmedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
if isPreset {
// Presets can always save (just color change)
return true
}
// New/user categories need a valid unique name
return !trimmedName.isEmpty && store.isNameAvailable(trimmedName, excluding: category)
}
var body: some View {
NavigationStack {
Form {
Section {
if isPreset {
// Preset name is read-only
HStack {
Text(String(localized: "Name"))
Spacer()
Text(name)
.foregroundStyle(AppTextColors.secondary)
}
} else {
TextField(String(localized: "Category Name"), text: $name)
}
} header: {
Text(String(localized: "Name"))
}
.listRowBackground(AppSurface.card)
Section {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 44))], spacing: Design.Spacing.medium) {
ForEach(Category.availableColors, id: \.name) { colorOption in
Button {
selectedColorName = colorOption.name
} label: {
Circle()
.fill(colorOption.color)
.frame(width: 40, height: 40)
.overlay(
Circle()
.strokeBorder(Color.white, lineWidth: selectedColorName == colorOption.name ? 3 : 0)
)
.shadow(color: selectedColorName == colorOption.name ? colorOption.color.opacity(0.5) : .clear, radius: 4)
}
.buttonStyle(.plain)
}
}
.padding(.vertical, Design.Spacing.small)
} header: {
Text(String(localized: "Color"))
}
.listRowBackground(AppSurface.card)
// Delete button for user categories
if isEditing && !isPreset {
Section {
Button(role: .destructive) {
showingDeleteConfirmation = true
} label: {
HStack {
Spacer()
Text(String(localized: "Delete Category"))
Spacer()
}
}
}
.listRowBackground(AppSurface.card)
}
}
.scrollContentBackground(.hidden)
.background(AppSurface.primary)
.navigationTitle(isEditing ? String(localized: "Edit Category") : String(localized: "New Category"))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button(String(localized: "Cancel")) {
dismiss()
}
}
ToolbarItem(placement: .confirmationAction) {
Button(String(localized: "Save")) {
save()
dismiss()
}
.disabled(!canSave)
}
}
.onAppear {
loadCategory()
}
.alert(String(localized: "Delete Category?"), isPresented: $showingDeleteConfirmation) {
Button(String(localized: "Cancel"), role: .cancel) {}
Button(String(localized: "Delete"), role: .destructive) {
if let category {
store.deleteCategory(category)
}
dismiss()
}
} message: {
Text(String(localized: "Rituals using this category will be set to no category."))
}
}
.presentationDetents([.medium])
}
private func loadCategory() {
if let category {
name = category.name
selectedColorName = category.colorName
} else {
name = ""
selectedColorName = "blue" // Default for new categories
}
}
private func save() {
if let category {
// Update existing
if isPreset {
store.updateCategory(category, colorName: selectedColorName)
} else {
store.updateCategory(category, name: name, colorName: selectedColorName)
}
} else {
// Create new
store.addCategory(name: name, colorName: selectedColorName)
}
}
}
#Preview("New Category") {
CategoryEditSheet(store: CategoryStore.preview, category: nil)
}
#Preview("Edit Preset") {
CategoryEditSheet(store: CategoryStore.preview, category: Category.defaultPresets.first!)
}