From 542d3ea9d6664178eee7748d2f07562379ec012d Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Sun, 30 Jun 2024 01:02:37 +0700 Subject: [PATCH] feat: add restore and delete actions --- .../Dashboard/Dashboard/DashboardView.swift | 16 +++- .../Dashboard/Details/Memos/MemosView.swift | 15 +--- .../Dashboard/Details/Trash/TrashView.swift | 81 +++++++++++++++++-- Memola/Persistence/Objects/MemoObject.swift | 2 +- 4 files changed, 92 insertions(+), 22 deletions(-) diff --git a/Memola/Features/Dashboard/Dashboard/DashboardView.swift b/Memola/Features/Dashboard/Dashboard/DashboardView.swift index ca9b15e..c6aa549 100644 --- a/Memola/Features/Dashboard/Dashboard/DashboardView.swift +++ b/Memola/Features/Dashboard/Dashboard/DashboardView.swift @@ -10,6 +10,7 @@ import SwiftUI struct DashboardView: View { @Environment(\.horizontalSizeClass) var horizontalSizeClass + @State var memo: MemoObject? @State var sidebarItem: SidebarItem? = .memos var body: some View { @@ -18,12 +19,21 @@ struct DashboardView: View { } detail: { switch sidebarItem { case .memos: - MemosView() + MemosView(memo: $memo) case .trash: - TrashView() + TrashView(memo: $memo, sidebarItem: $sidebarItem) default: - MemosView() + MemosView(memo: $memo) } } + .fullScreenCover(item: $memo) { memo in + MemoView(memo: memo) + .onDisappear { + withPersistence(\.viewContext) { context in + try context.saveIfNeeded() + context.refreshAllObjects() + } + } + } } } diff --git a/Memola/Features/Dashboard/Details/Memos/MemosView.swift b/Memola/Features/Dashboard/Details/Memos/MemosView.swift index 06b39d9..d98119c 100644 --- a/Memola/Features/Dashboard/Details/Memos/MemosView.swift +++ b/Memola/Features/Dashboard/Details/Memos/MemosView.swift @@ -12,10 +12,11 @@ struct MemosView: View { @FetchRequest var memoObjects: FetchedResults - @State var memo: MemoObject? @State var query: String = "" @State var currentDate: Date = .now + @Binding var memo: MemoObject? + @AppStorage("memola.memo-objects.memos.sort") var sort: Sort = .recent @AppStorage("memola.memo-objects.memos.filter") var filter: Filter = .none @@ -25,7 +26,8 @@ struct MemosView: View { query.isEmpty ? .memoEmpty : .memoNotFound } - init() { + init(memo: Binding) { + _memo = memo let standard = UserDefaults.standard var descriptors: [SortDescriptor] = [] var predicates: [NSPredicate] = [NSPredicate(format: "isTrash = NO")] @@ -115,15 +117,6 @@ struct MemosView: View { } } } - .fullScreenCover(item: $memo) { memo in - MemoView(memo: memo) - .onDisappear { - withPersistence(\.viewContext) { context in - try context.saveIfNeeded() - context.refreshAllObjects() - } - } - } .onChange(of: sort) { oldValue, newValue in memoObjects.sortDescriptors = newValue.memoSortDescriptors } diff --git a/Memola/Features/Dashboard/Details/Trash/TrashView.swift b/Memola/Features/Dashboard/Details/Trash/TrashView.swift index 6a010a5..d161da7 100644 --- a/Memola/Features/Dashboard/Details/Trash/TrashView.swift +++ b/Memola/Features/Dashboard/Details/Trash/TrashView.swift @@ -13,18 +13,35 @@ struct TrashView: View { @FetchRequest var memoObjects: FetchedResults @State var query: String = "" + @State var restoredMemo: MemoObject? + @State var deletedMemo: MemoObject? + + @Binding var memo: MemoObject? + @Binding var sidebarItem: SidebarItem? var placeholder: Placeholder.Info { query.isEmpty ? .trashEmpty : .trashNotFound } - init() { + init(memo: Binding, sidebarItem: Binding) { + _memo = memo + _sidebarItem = sidebarItem let descriptors = [SortDescriptor(\MemoObject.deletedAt, order: .reverse)] let predicate = NSPredicate(format: "isTrash = YES") _memoObjects = FetchRequest(sortDescriptors: descriptors, predicate: predicate) } var body: some View { + let restoresMemo = Binding { + restoredMemo != nil + } set: { _ in + restoredMemo = nil + } + let deletesMemo = Binding { + deletedMemo != nil + } set: { _ in + deletedMemo = nil + } MemoGrid(memoObjects: memoObjects, placeholder: placeholder) { memoObject in memoCard(memoObject) } @@ -48,6 +65,31 @@ struct TrashView: View { .onChange(of: query) { oldValue, newValue in updatePredicate() } + .alert("Restore Memo", isPresented: restoresMemo) { + Button { + restoreMemo(for: restoredMemo) + } label: { + Text("Restore") + } + Button { + restoreAndOpenMemo(for: restoredMemo) + } label: { + Text("Restore and Open") + } + Button("Cancel", role: .cancel) { } + } message: { + Text("Would you like to restore this memo or restore and open it?") + } + .alert("Delete Memo Permanently", isPresented: deletesMemo) { + Button(role: .destructive) { + deleteMemo(for: deletedMemo) + } label: { + Text("Delete") + } + Button("Cancel", role: .cancel) { } + } message: { + Text("Are you sure you want to permanently delete this memo? This action cannot be undone.") + } } func memoCard(_ memoObject: MemoObject) -> some View { @@ -55,23 +97,25 @@ struct TrashView: View { card .contextMenu { Button { - + restoreMemo(for: memoObject) } label: { Label("Restore", systemImage: "square.and.arrow.down") } Button(role: .destructive) { - + deletedMemo = memoObject } label: { Label("Delete Permanently", systemImage: "trash") } } } details: { - Text("Deleted on \(memoObject.deletedAt.formatted(date: .abbreviated, time: .standard))") - .font(.caption) - .foregroundStyle(.secondary) + if let deletedAt = memoObject.deletedAt { + Text("Deleted on \(deletedAt.formatted(date: .abbreviated, time: .standard))") + .font(.caption) + .foregroundStyle(.secondary) + } } .onTapGesture { - + restoredMemo = memoObject } } @@ -82,4 +126,27 @@ struct TrashView: View { } memoObjects.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates) } + + func restoreMemo(for memo: MemoObject?) { + guard let memo else { return } + memo.isTrash = false + memo.deletedAt = nil + withPersistence(\.viewContext) { context in + try context.saveIfNeeded() + } + } + + func restoreAndOpenMemo(for memo: MemoObject?) { + restoreMemo(for: memo) + self.sidebarItem = .memos + self.memo = memo + } + + func deleteMemo(for memo: MemoObject?) { + guard let memo else { return } + withPersistenceSync(\.viewContext) { context in + context.delete(memo) + try context.saveIfNeeded() + } + } } diff --git a/Memola/Persistence/Objects/MemoObject.swift b/Memola/Persistence/Objects/MemoObject.swift index 0870cb2..c62d8f4 100644 --- a/Memola/Persistence/Objects/MemoObject.swift +++ b/Memola/Persistence/Objects/MemoObject.swift @@ -14,7 +14,7 @@ final class MemoObject: NSManagedObject, Identifiable { @NSManaged var title: String @NSManaged var createdAt: Date @NSManaged var updatedAt: Date - @NSManaged var deletedAt: Date + @NSManaged var deletedAt: Date? @NSManaged var isFavorite: Bool @NSManaged var isTrash: Bool @NSManaged var tool: ToolObject