feat: added list overview and detail view
This commit is contained in:
@@ -2,16 +2,16 @@ import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
var body: some View {
|
||||
VStack {
|
||||
Image(systemName: "globe")
|
||||
.imageScale(.large)
|
||||
.foregroundStyle(.tint)
|
||||
Text("Hello, world!")
|
||||
NavigationStack {
|
||||
ListsOverviewView()
|
||||
.navigationDestination(for: UUID.self) { listID in
|
||||
ListDetailView(listID: listID)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ContentView()
|
||||
.environment(ListStore())
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@ import SwiftUI
|
||||
|
||||
@main
|
||||
struct MindDumpApp: App {
|
||||
@State private var store = ListStore()
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
.environment(store)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
MindDump/Models/TodoItem.swift
Normal file
15
MindDump/Models/TodoItem.swift
Normal file
@@ -0,0 +1,15 @@
|
||||
import Foundation
|
||||
|
||||
struct TodoItem: Identifiable {
|
||||
let id: UUID
|
||||
var title: String
|
||||
var isCompleted: Bool
|
||||
let createdAt: Date
|
||||
|
||||
init(id: UUID = UUID(), title: String, isCompleted: Bool = false, createdAt: Date = Date()) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.isCompleted = isCompleted
|
||||
self.createdAt = createdAt
|
||||
}
|
||||
}
|
||||
15
MindDump/Models/TodoList.swift
Normal file
15
MindDump/Models/TodoList.swift
Normal file
@@ -0,0 +1,15 @@
|
||||
import Foundation
|
||||
|
||||
struct TodoList: Identifiable {
|
||||
let id: UUID
|
||||
var name: String
|
||||
var items: [TodoItem]
|
||||
let isInbox: Bool
|
||||
|
||||
init(id: UUID = UUID(), name: String, items: [TodoItem] = [], isInbox: Bool = false) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.items = items
|
||||
self.isInbox = isInbox
|
||||
}
|
||||
}
|
||||
44
MindDump/ViewModels/ListStore.swift
Normal file
44
MindDump/ViewModels/ListStore.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
import Foundation
|
||||
import Observation
|
||||
|
||||
@Observable
|
||||
class ListStore {
|
||||
var lists: [TodoList]
|
||||
|
||||
init() {
|
||||
self.lists = [TodoList(name: "Inbox", isInbox: true)]
|
||||
}
|
||||
|
||||
func addList(name: String) {
|
||||
let list = TodoList(name: name)
|
||||
lists.append(list)
|
||||
}
|
||||
|
||||
func deleteList(_ list: TodoList) {
|
||||
guard !list.isInbox else { return }
|
||||
lists.removeAll { $0.id == list.id }
|
||||
}
|
||||
|
||||
func addItem(to listID: UUID, title: String) {
|
||||
guard let index = lists.firstIndex(where: { $0.id == listID }) else { return }
|
||||
let item = TodoItem(title: title)
|
||||
lists[index].items.append(item)
|
||||
}
|
||||
|
||||
func deleteItem(_ itemID: UUID, from listID: UUID) {
|
||||
guard let listIndex = lists.firstIndex(where: { $0.id == listID }) else { return }
|
||||
lists[listIndex].items.removeAll { $0.id == itemID }
|
||||
}
|
||||
|
||||
func updateItem(_ itemID: UUID, in listID: UUID, title: String) {
|
||||
guard let listIndex = lists.firstIndex(where: { $0.id == listID }),
|
||||
let itemIndex = lists[listIndex].items.firstIndex(where: { $0.id == itemID }) else { return }
|
||||
lists[listIndex].items[itemIndex].title = title
|
||||
}
|
||||
|
||||
func toggleItemCompleted(_ itemID: UUID, in listID: UUID) {
|
||||
guard let listIndex = lists.firstIndex(where: { $0.id == listID }),
|
||||
let itemIndex = lists[listIndex].items.firstIndex(where: { $0.id == itemID }) else { return }
|
||||
lists[listIndex].items[itemIndex].isCompleted.toggle()
|
||||
}
|
||||
}
|
||||
52
MindDump/Views/ListDetailView.swift
Normal file
52
MindDump/Views/ListDetailView.swift
Normal file
@@ -0,0 +1,52 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ListDetailView: View {
|
||||
@Environment(ListStore.self) private var store
|
||||
let listID: UUID
|
||||
@State private var editorItem: TodoItem?
|
||||
@State private var showingEditor = false
|
||||
|
||||
private var todoList: TodoList? {
|
||||
store.lists.first { $0.id == listID }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if let todoList {
|
||||
if todoList.items.isEmpty {
|
||||
ContentUnavailableView("Keine Einträge", systemImage: "tray")
|
||||
} else {
|
||||
List {
|
||||
ForEach(todoList.items) { item in
|
||||
TodoRowView(item: item, onToggle: {
|
||||
store.toggleItemCompleted(item.id, in: listID)
|
||||
}, onTap: {
|
||||
editorItem = item
|
||||
showingEditor = true
|
||||
})
|
||||
}
|
||||
.onDelete { offsets in
|
||||
for index in offsets {
|
||||
store.deleteItem(todoList.items[index].id, from: listID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(todoList?.name ?? "")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button(action: {
|
||||
editorItem = nil
|
||||
showingEditor = true
|
||||
}) {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showingEditor) {
|
||||
TodoEditorView(listID: listID, item: editorItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
50
MindDump/Views/ListsOverviewView.swift
Normal file
50
MindDump/Views/ListsOverviewView.swift
Normal file
@@ -0,0 +1,50 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ListsOverviewView: View {
|
||||
@Environment(ListStore.self) private var store
|
||||
@State private var showingAddList = false
|
||||
@State private var newListName = ""
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(store.lists) { list in
|
||||
NavigationLink(value: list.id) {
|
||||
HStack {
|
||||
Text(list.name)
|
||||
Spacer()
|
||||
Text("\(list.items.count)")
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onDelete(perform: deleteLists)
|
||||
}
|
||||
.navigationTitle("MindDump")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button(action: { showingAddList = true }) {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert("Neue Liste", isPresented: $showingAddList) {
|
||||
TextField("Name", text: $newListName)
|
||||
Button("Abbrechen", role: .cancel) {
|
||||
newListName = ""
|
||||
}
|
||||
Button("Erstellen") {
|
||||
let name = newListName.trimmingCharacters(in: .whitespaces)
|
||||
if !name.isEmpty {
|
||||
store.addList(name: name)
|
||||
}
|
||||
newListName = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteLists(at offsets: IndexSet) {
|
||||
for index in offsets {
|
||||
store.deleteList(store.lists[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
52
MindDump/Views/TodoEditorView.swift
Normal file
52
MindDump/Views/TodoEditorView.swift
Normal file
@@ -0,0 +1,52 @@
|
||||
import SwiftUI
|
||||
|
||||
struct TodoEditorView: View {
|
||||
@Environment(ListStore.self) private var store
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
let listID: UUID
|
||||
var item: TodoItem?
|
||||
|
||||
@State private var title: String = ""
|
||||
@FocusState private var titleFocused: Bool
|
||||
|
||||
private var isEditing: Bool { item != nil }
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
TextField("Titel", text: $title)
|
||||
.focused($titleFocused)
|
||||
}
|
||||
.navigationTitle(isEditing ? "Bearbeiten" : "Neuer Eintrag")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("Abbrechen") { dismiss() }
|
||||
}
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("Fertig") { save() }
|
||||
.disabled(title.trimmingCharacters(in: .whitespaces).isEmpty)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if let item {
|
||||
title = item.title
|
||||
}
|
||||
titleFocused = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func save() {
|
||||
let trimmed = title.trimmingCharacters(in: .whitespaces)
|
||||
guard !trimmed.isEmpty else { return }
|
||||
|
||||
if let item {
|
||||
store.updateItem(item.id, in: listID, title: trimmed)
|
||||
} else {
|
||||
store.addItem(to: listID, title: trimmed)
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
27
MindDump/Views/TodoRowView.swift
Normal file
27
MindDump/Views/TodoRowView.swift
Normal file
@@ -0,0 +1,27 @@
|
||||
import SwiftUI
|
||||
|
||||
struct TodoRowView: View {
|
||||
let item: TodoItem
|
||||
let onToggle: () -> Void
|
||||
var onTap: (() -> Void)?
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Button(action: onToggle) {
|
||||
Image(systemName: item.isCompleted ? "checkmark.circle.fill" : "circle")
|
||||
.foregroundStyle(item.isCompleted ? .green : .secondary)
|
||||
.imageScale(.large)
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
|
||||
Button(action: { onTap?() }) {
|
||||
Text(item.title)
|
||||
.strikethrough(item.isCompleted)
|
||||
.foregroundStyle(item.isCompleted ? .secondary : .primary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user