mycode/myApp/HaruDanim/IOS/Views/ActionTabView.swift

184 lines
6.2 KiB
Swift

//
// ActionTabView.swift
// HaruDanim
//
// Lists all `Action`s and hosts a sheet for creating new ones.
//
import SwiftUI
import SwiftData
struct ActionTabView: View {
@Environment(\.modelContext) private var modelContext
@Query(sort: \Action.createdAt, order: .reverse) private var actions: [Action]
@State private var isAddSheetPresented = false
var body: some View {
NavigationStack {
List {
ForEach(actions) { action in
HStack(spacing: 12) {
Image(systemName: action.iconName)
.frame(width: 28)
.foregroundStyle(Color.brandPrimary)
VStack(alignment: .leading, spacing: 4) {
Text(action.name)
if !action.tags.isEmpty {
Text(action.tags.map(\.name).joined(separator: ", "))
.font(.caption)
.foregroundStyle(.secondary)
}
}
Spacer()
Text(action.type == .time ? "시간" : "횟수")
.font(.caption)
.foregroundStyle(.secondary)
}
}
.onDelete(perform: deleteActions)
}
.navigationTitle("행동")
.overlay {
if actions.isEmpty {
ContentUnavailableView(
"행동이 없어요",
systemImage: "bolt",
description: Text("오른쪽 위 + 버튼으로 행동을 추가하세요.")
)
}
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
isAddSheetPresented = true
} label: {
Label("행동 추가", systemImage: "plus")
}
}
}
.sheet(isPresented: $isAddSheetPresented) {
AddActionSheet()
}
}
}
private func deleteActions(at offsets: IndexSet) {
for index in offsets {
modelContext.delete(actions[index])
}
}
}
// MARK: - Add Action Sheet
private struct AddActionSheet: View {
@Environment(\.modelContext) private var modelContext
@Environment(\.dismiss) private var dismiss
@Query(sort: \Tag.createdAt, order: .reverse) private var allTags: [Tag]
@State private var name = ""
@State private var iconName = "star.fill"
@State private var type: ActionType = .time
@State private var selectedTagIDs: Set<UUID> = []
private var trimmedName: String {
name.trimmingCharacters(in: .whitespacesAndNewlines)
}
var body: some View {
NavigationStack {
Form {
Section("이름") {
TextField("행동 이름", text: $name)
}
Section("아이콘 (SF Symbol)") {
HStack {
Image(systemName: iconName.isEmpty ? "questionmark" : iconName)
.foregroundStyle(Color.brandPrimary)
TextField("star.fill", text: $iconName)
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
}
}
Section("유형") {
Picker("유형", selection: $type) {
Text("시간").tag(ActionType.time)
Text("횟수").tag(ActionType.count)
}
.pickerStyle(.segmented)
}
Section("꼬리표") {
if allTags.isEmpty {
Text("먼저 꼬리표를 추가하세요.")
.foregroundStyle(.secondary)
} else {
ForEach(allTags) { tag in
Button {
toggle(tag)
} label: {
HStack(spacing: 12) {
Circle()
.fill(Color(hex: tag.colorHex))
.frame(width: 16, height: 16)
Text(tag.name)
.foregroundStyle(.primary)
Spacer()
if selectedTagIDs.contains(tag.id) {
Image(systemName: "checkmark")
.foregroundStyle(Color.brandPrimary)
}
}
}
}
}
}
}
.navigationTitle("새 행동")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("취소") { dismiss() }
}
ToolbarItem(placement: .confirmationAction) {
Button("저장") { save() }
.disabled(trimmedName.isEmpty)
}
}
}
}
private func toggle(_ tag: Tag) {
if selectedTagIDs.contains(tag.id) {
selectedTagIDs.remove(tag.id)
} else {
selectedTagIDs.insert(tag.id)
}
}
private func save() {
let chosenTags = allTags.filter { selectedTagIDs.contains($0.id) }
let trimmedIcon = iconName.trimmingCharacters(in: .whitespacesAndNewlines)
let action = Action(
name: trimmedName,
type: type,
iconName: trimmedIcon.isEmpty ? "circle" : trimmedIcon,
tags: chosenTags
)
modelContext.insert(action)
dismiss()
}
}
#Preview {
ActionTabView()
.modelContainer(for: [Action.self, Tag.self, TimeSession.self, CountEntry.self], inMemory: true)
}