diff --git a/Memola/App/MemolaApp.swift b/Memola/App/MemolaApp.swift index a1175c8..93f81ed 100644 --- a/Memola/App/MemolaApp.swift +++ b/Memola/App/MemolaApp.swift @@ -30,5 +30,18 @@ struct MemolaApp: App { .windowResizability(.contentSize) .defaultSize(width: 1200, height: 800) #endif + WindowGroup(id: "memo-view", for: URL.self) { url in + if let url = url.wrappedValue, let memo = Persistence.loadMemo(of: url) { + MemoView(memo: memo) + #if os(macOS) + .frame(minWidth: 1000, minHeight: 600) + #endif + } + } + #if os(macOS) + .defaultPosition(.center) + .windowResizability(.contentSize) + .defaultSize(width: 1200, height: 800) + #endif } } diff --git a/Memola/Features/Dashboard/Dashboard/DashboardView.swift b/Memola/Features/Dashboard/Dashboard/DashboardView.swift index e637218..e027341 100644 --- a/Memola/Features/Dashboard/Dashboard/DashboardView.swift +++ b/Memola/Features/Dashboard/Dashboard/DashboardView.swift @@ -10,7 +10,9 @@ import SwiftUI struct DashboardView: View { @Environment(\.horizontalSizeClass) var horizontalSizeClass + #if os(iOS) @State var memo: MemoObject? + #endif @State var sidebarItem: SidebarItem? = .memos var body: some View { @@ -19,11 +21,23 @@ struct DashboardView: View { } detail: { switch sidebarItem { case .memos: + #if os(macOS) + MemosView() + #else MemosView(memo: $memo) + #endif case .trash: + #if os(macOS) + TrashView(sidebarItem: $sidebarItem) + #else TrashView(memo: $memo, sidebarItem: $sidebarItem) + #endif default: + #if os(macOS) + MemosView() + #else MemosView(memo: $memo) + #endif } } #if os(iOS) diff --git a/Memola/Features/Dashboard/Details/Memos/MemosView.swift b/Memola/Features/Dashboard/Details/Memos/MemosView.swift index df282a7..28165ff 100644 --- a/Memola/Features/Dashboard/Details/Memos/MemosView.swift +++ b/Memola/Features/Dashboard/Details/Memos/MemosView.swift @@ -8,6 +8,9 @@ import SwiftUI struct MemosView: View { + #if os(macOS) + @Environment(\.openWindow) var openWindow + #endif @Environment(\.horizontalSizeClass) var horizontalSizeClass @FetchRequest var memoObjects: FetchedResults @@ -15,7 +18,9 @@ struct MemosView: View { @State var query: String = "" @State var currentDate: Date = .now + #if os(iOS) @Binding var memo: MemoObject? + #endif @AppStorage("memola.memo-objects.memos.sort") var sort: Sort = .recent @AppStorage("memola.memo-objects.memos.filter") var filter: Filter = .none @@ -26,6 +31,21 @@ struct MemosView: View { query.isEmpty ? .memoEmpty : .memoNotFound } + #if os(macOS) + init() { + let standard = UserDefaults.standard + var descriptors: [SortDescriptor] = [] + var predicates: [NSPredicate] = [NSPredicate(format: "isTrash = NO")] + let sort = Sort(rawValue: standard.value(forKey: "memola.memo-objects.memos.sort") as? String ?? "") ?? .recent + let filter = Filter(rawValue: standard.value(forKey: "memola.memo-objects.memos.filter") as? String ?? "") ?? .none + if filter == .favorites { + predicates.append(NSPredicate(format: "isFavorite = YES")) + } + descriptors = sort.memoSortDescriptors + let predicate = NSCompoundPredicate(type: .and, subpredicates: predicates) + _memoObjects = FetchRequest(sortDescriptors: descriptors, predicate: predicate) + } + #else init(memo: Binding) { _memo = memo let standard = UserDefaults.standard @@ -40,6 +60,7 @@ struct MemosView: View { let predicate = NSCompoundPredicate(type: .and, subpredicates: predicates) _memoObjects = FetchRequest(sortDescriptors: descriptors, predicate: predicate) } + #endif var body: some View { MemoGrid(memoObjects: memoObjects, placeholder: placeholder) { memoObject, cellWidth in @@ -252,7 +273,11 @@ struct MemosView: View { } func openMemo(for memo: MemoObject) { + #if os(macOS) + openWindow(id: "memo-view", value: memo.objectID.uriRepresentation()) + #else self.memo = memo + #endif } func updatePredicate() { diff --git a/Memola/Features/Dashboard/Details/Trash/TrashView.swift b/Memola/Features/Dashboard/Details/Trash/TrashView.swift index 14ca604..0d7d628 100644 --- a/Memola/Features/Dashboard/Details/Trash/TrashView.swift +++ b/Memola/Features/Dashboard/Details/Trash/TrashView.swift @@ -8,6 +8,9 @@ import SwiftUI struct TrashView: View { + #if os(macOS) + @Environment(\.openWindow) var openWindow + #endif @Environment(\.horizontalSizeClass) var horizontalSizeClass @FetchRequest var memoObjects: FetchedResults @@ -16,13 +19,23 @@ struct TrashView: View { @State var restoredMemo: MemoObject? @State var deletedMemo: MemoObject? + #if os(iOS) @Binding var memo: MemoObject? + #endif @Binding var sidebarItem: SidebarItem? var placeholder: Placeholder.Info { query.isEmpty ? .trashEmpty : .trashNotFound } + #if os(macOS) + init(sidebarItem: Binding) { + _sidebarItem = sidebarItem + let descriptors = [SortDescriptor(\MemoObject.deletedAt, order: .reverse)] + let predicate = NSPredicate(format: "isTrash = YES") + _memoObjects = FetchRequest(sortDescriptors: descriptors, predicate: predicate) + } + #else init(memo: Binding, sidebarItem: Binding) { _memo = memo _sidebarItem = sidebarItem @@ -30,6 +43,7 @@ struct TrashView: View { let predicate = NSPredicate(format: "isTrash = YES") _memoObjects = FetchRequest(sortDescriptors: descriptors, predicate: predicate) } + #endif var body: some View { let restoresMemo = Binding { @@ -146,7 +160,13 @@ struct TrashView: View { func restoreAndOpenMemo(for memo: MemoObject?) { restoreMemo(for: memo) self.sidebarItem = .memos - self.memo = memo + if let memo { + #if os(macOS) + openWindow(id: "memo-view", value: memo.objectID.uriRepresentation()) + #else + self.memo = memo + #endif + } } func deleteMemo(for memo: MemoObject?) { diff --git a/Memola/Persistence/Core/Persistence.swift b/Memola/Persistence/Core/Persistence.swift index 5ebac21..15c19f3 100644 --- a/Memola/Persistence/Core/Persistence.swift +++ b/Memola/Persistence/Core/Persistence.swift @@ -77,6 +77,14 @@ final class Persistence { fatalError("[Memola]: \(error.localizedDescription)") } }() + + static func loadMemo(of url: URL) -> MemoObject? { + let viewContext = shared.viewContext + guard let objectID = viewContext.persistentStoreCoordinator?.managedObjectID(forURIRepresentation: url) else { + return nil + } + return viewContext.object(with: objectID) as? MemoObject + } } // MARK: - Global Method