mirror of
https://github.com/dscyrescotti/Memola.git
synced 2026-04-25 01:58:52 +02:00
feat: implement trash view
This commit is contained in:
@@ -7,6 +7,13 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
EC01511E2C305CA9008A115E /* DashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC01511D2C305CA9008A115E /* DashboardView.swift */; };
|
||||||
|
EC0151202C305D7B008A115E /* SidebarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC01511F2C305D7B008A115E /* SidebarItem.swift */; };
|
||||||
|
EC0151232C306089008A115E /* Sidebar.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0151222C306089008A115E /* Sidebar.swift */; };
|
||||||
|
EC0151262C3067B9008A115E /* TrashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0151252C3067B9008A115E /* TrashView.swift */; };
|
||||||
|
EC01512A2C306935008A115E /* MemoGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0151292C306935008A115E /* MemoGrid.swift */; };
|
||||||
|
EC01512C2C306BEF008A115E /* MemoCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC01512B2C306BEF008A115E /* MemoCard.swift */; };
|
||||||
|
EC01512E2C30727F008A115E /* MemoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC01512D2C30727F008A115E /* MemoPreview.swift */; };
|
||||||
EC0D14212BF79C73009BFE5F /* ToolObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0D14202BF79C73009BFE5F /* ToolObject.swift */; };
|
EC0D14212BF79C73009BFE5F /* ToolObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0D14202BF79C73009BFE5F /* ToolObject.swift */; };
|
||||||
EC0D14242BF79C98009BFE5F /* MemolaModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = EC0D14222BF79C98009BFE5F /* MemolaModel.xcdatamodeld */; };
|
EC0D14242BF79C98009BFE5F /* MemolaModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = EC0D14222BF79C98009BFE5F /* MemolaModel.xcdatamodeld */; };
|
||||||
EC0D14262BF7A8C9009BFE5F /* PenObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0D14252BF7A8C9009BFE5F /* PenObject.swift */; };
|
EC0D14262BF7A8C9009BFE5F /* PenObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0D14252BF7A8C9009BFE5F /* PenObject.swift */; };
|
||||||
@@ -117,6 +124,13 @@
|
|||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
EC01511D2C305CA9008A115E /* DashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardView.swift; sourceTree = "<group>"; };
|
||||||
|
EC01511F2C305D7B008A115E /* SidebarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarItem.swift; sourceTree = "<group>"; };
|
||||||
|
EC0151222C306089008A115E /* Sidebar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sidebar.swift; sourceTree = "<group>"; };
|
||||||
|
EC0151252C3067B9008A115E /* TrashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrashView.swift; sourceTree = "<group>"; };
|
||||||
|
EC0151292C306935008A115E /* MemoGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoGrid.swift; sourceTree = "<group>"; };
|
||||||
|
EC01512B2C306BEF008A115E /* MemoCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoCard.swift; sourceTree = "<group>"; };
|
||||||
|
EC01512D2C30727F008A115E /* MemoPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoPreview.swift; sourceTree = "<group>"; };
|
||||||
EC0D14202BF79C73009BFE5F /* ToolObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolObject.swift; sourceTree = "<group>"; };
|
EC0D14202BF79C73009BFE5F /* ToolObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolObject.swift; sourceTree = "<group>"; };
|
||||||
EC0D14232BF79C98009BFE5F /* MemolaModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MemolaModel.xcdatamodel; sourceTree = "<group>"; };
|
EC0D14232BF79C98009BFE5F /* MemolaModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MemolaModel.xcdatamodel; sourceTree = "<group>"; };
|
||||||
EC0D14252BF7A8C9009BFE5F /* PenObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenObject.swift; sourceTree = "<group>"; };
|
EC0D14252BF7A8C9009BFE5F /* PenObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenObject.swift; sourceTree = "<group>"; };
|
||||||
@@ -239,6 +253,61 @@
|
|||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
EC01511A2C305ABB008A115E /* Dashboard */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
EC0151272C306906008A115E /* Details */,
|
||||||
|
EC0151212C30605F008A115E /* Sidebar */,
|
||||||
|
EC01511C2C305C99008A115E /* Dashboard */,
|
||||||
|
);
|
||||||
|
path = Dashboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
EC01511C2C305C99008A115E /* Dashboard */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
EC01511D2C305CA9008A115E /* DashboardView.swift */,
|
||||||
|
);
|
||||||
|
path = Dashboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
EC0151212C30605F008A115E /* Sidebar */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
EC0151222C306089008A115E /* Sidebar.swift */,
|
||||||
|
EC01511F2C305D7B008A115E /* SidebarItem.swift */,
|
||||||
|
);
|
||||||
|
path = Sidebar;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
EC0151242C3067B2008A115E /* Trash */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
EC0151252C3067B9008A115E /* TrashView.swift */,
|
||||||
|
);
|
||||||
|
path = Trash;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
EC0151272C306906008A115E /* Details */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
EC0151282C306927008A115E /* Shared */,
|
||||||
|
EC0151242C3067B2008A115E /* Trash */,
|
||||||
|
ECA738782BE5EEF700A4542E /* Memos */,
|
||||||
|
);
|
||||||
|
path = Details;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
EC0151282C306927008A115E /* Shared */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
EC0151292C306935008A115E /* MemoGrid.swift */,
|
||||||
|
EC01512B2C306BEF008A115E /* MemoCard.swift */,
|
||||||
|
EC01512D2C30727F008A115E /* MemoPreview.swift */,
|
||||||
|
);
|
||||||
|
path = Shared;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
EC1437B42BE748E60022C903 /* Views */ = {
|
EC1437B42BE748E60022C903 /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -418,8 +487,8 @@
|
|||||||
ECA738772BE5EEE800A4542E /* Features */ = {
|
ECA738772BE5EEE800A4542E /* Features */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
EC01511A2C305ABB008A115E /* Dashboard */,
|
||||||
ECA7387B2BE5EF3500A4542E /* Memo */,
|
ECA7387B2BE5EF3500A4542E /* Memo */,
|
||||||
ECA738782BE5EEF700A4542E /* Memos */,
|
|
||||||
);
|
);
|
||||||
path = Features;
|
path = Features;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -427,9 +496,9 @@
|
|||||||
ECA738782BE5EEF700A4542E /* Memos */ = {
|
ECA738782BE5EEF700A4542E /* Memos */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
ECA738792BE5EF0400A4542E /* MemosView.swift */,
|
|
||||||
EC1815072C2D980B00541369 /* Sort.swift */,
|
EC1815072C2D980B00541369 /* Sort.swift */,
|
||||||
EC1815092C2DA09E00541369 /* Filter.swift */,
|
EC1815092C2DA09E00541369 /* Filter.swift */,
|
||||||
|
ECA738792BE5EF0400A4542E /* MemosView.swift */,
|
||||||
);
|
);
|
||||||
path = Memos;
|
path = Memos;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -897,6 +966,7 @@
|
|||||||
ECA738912BE600F500A4542E /* Cache.metal in Sources */,
|
ECA738912BE600F500A4542E /* Cache.metal in Sources */,
|
||||||
ECA7389C2BE601AF00A4542E /* PointGridVertex.swift in Sources */,
|
ECA7389C2BE601AF00A4542E /* PointGridVertex.swift in Sources */,
|
||||||
ECA738A82BE6025900A4542E /* GraphicUniforms.swift in Sources */,
|
ECA738A82BE6025900A4542E /* GraphicUniforms.swift in Sources */,
|
||||||
|
EC01511E2C305CA9008A115E /* DashboardView.swift in Sources */,
|
||||||
EC8F54AE2C2AF5A4001C7C74 /* LineGridVertex.swift in Sources */,
|
EC8F54AE2C2AF5A4001C7C74 /* LineGridVertex.swift in Sources */,
|
||||||
ECA738E62BE611FD00A4542E /* CGRect++.swift in Sources */,
|
ECA738E62BE611FD00A4542E /* CGRect++.swift in Sources */,
|
||||||
EC5E83902BFDB69C00261D9C /* MovingAverage.swift in Sources */,
|
EC5E83902BFDB69C00261D9C /* MovingAverage.swift in Sources */,
|
||||||
@@ -926,6 +996,7 @@
|
|||||||
ECEC01A82BEE11BA006DA24C /* QuadShape.swift in Sources */,
|
ECEC01A82BEE11BA006DA24C /* QuadShape.swift in Sources */,
|
||||||
ECA738862BE5FF2500A4542E /* Canvas.swift in Sources */,
|
ECA738862BE5FF2500A4542E /* Canvas.swift in Sources */,
|
||||||
ECA738882BE5FF4400A4542E /* Renderer.swift in Sources */,
|
ECA738882BE5FF4400A4542E /* Renderer.swift in Sources */,
|
||||||
|
EC01512C2C306BEF008A115E /* MemoCard.swift in Sources */,
|
||||||
ECA738D42BE60F9100A4542E /* StrokeGenerator.swift in Sources */,
|
ECA738D42BE60F9100A4542E /* StrokeGenerator.swift in Sources */,
|
||||||
ECA739082BE623F300A4542E /* PenDock.swift in Sources */,
|
ECA739082BE623F300A4542E /* PenDock.swift in Sources */,
|
||||||
ECA738CB2BE60F1900A4542E /* ViewPortContext.swift in Sources */,
|
ECA738CB2BE60F1900A4542E /* ViewPortContext.swift in Sources */,
|
||||||
@@ -953,6 +1024,7 @@
|
|||||||
EC42F7852C25267000E86E96 /* ElementGroup.swift in Sources */,
|
EC42F7852C25267000E86E96 /* ElementGroup.swift in Sources */,
|
||||||
EC18150F2C2DB13200541369 /* Date++.swift in Sources */,
|
EC18150F2C2DB13200541369 /* Date++.swift in Sources */,
|
||||||
ECD12A8F2C1AEBA400B96E12 /* Photo.swift in Sources */,
|
ECD12A8F2C1AEBA400B96E12 /* Photo.swift in Sources */,
|
||||||
|
EC0151232C306089008A115E /* Sidebar.swift in Sources */,
|
||||||
ECD12A932C1B062000B96E12 /* Photo.metal in Sources */,
|
ECD12A932C1B062000B96E12 /* Photo.metal in Sources */,
|
||||||
ECA7388F2BE600DA00A4542E /* Grid.metal in Sources */,
|
ECA7388F2BE600DA00A4542E /* Grid.metal in Sources */,
|
||||||
EC2BEBF42C0F5FF7005DB0AF /* RTree.swift in Sources */,
|
EC2BEBF42C0F5FF7005DB0AF /* RTree.swift in Sources */,
|
||||||
@@ -964,12 +1036,16 @@
|
|||||||
ECBE529C2C1D94A4006BDB3D /* CameraView.swift in Sources */,
|
ECBE529C2C1D94A4006BDB3D /* CameraView.swift in Sources */,
|
||||||
ECA7389E2BE601CB00A4542E /* QuadVertex.swift in Sources */,
|
ECA7389E2BE601CB00A4542E /* QuadVertex.swift in Sources */,
|
||||||
ECA738B32BE60D9E00A4542E /* CanvasView.swift in Sources */,
|
ECA738B32BE60D9E00A4542E /* CanvasView.swift in Sources */,
|
||||||
|
EC0151202C305D7B008A115E /* SidebarItem.swift in Sources */,
|
||||||
|
EC01512A2C306935008A115E /* MemoGrid.swift in Sources */,
|
||||||
ECA738C42BE60E8800A4542E /* MarkerPenStyle.swift in Sources */,
|
ECA738C42BE60E8800A4542E /* MarkerPenStyle.swift in Sources */,
|
||||||
|
EC0151262C3067B9008A115E /* TrashView.swift in Sources */,
|
||||||
ECA738BF2BE60E3400A4542E /* Pen.swift in Sources */,
|
ECA738BF2BE60E3400A4542E /* Pen.swift in Sources */,
|
||||||
ECFA15282BEF225000455818 /* QuadObject.swift in Sources */,
|
ECFA15282BEF225000455818 /* QuadObject.swift in Sources */,
|
||||||
ECA738932BE6011100A4542E /* Stroke.metal in Sources */,
|
ECA738932BE6011100A4542E /* Stroke.metal in Sources */,
|
||||||
ECA738B62BE60DCD00A4542E /* History.swift in Sources */,
|
ECA738B62BE60DCD00A4542E /* History.swift in Sources */,
|
||||||
ECA738D22BE60F7B00A4542E /* PenStroke.swift in Sources */,
|
ECA738D22BE60F7B00A4542E /* PenStroke.swift in Sources */,
|
||||||
|
EC01512E2C30727F008A115E /* MemoPreview.swift in Sources */,
|
||||||
ECA738F22BE6128F00A4542E /* Collection++.swift in Sources */,
|
ECA738F22BE6128F00A4542E /* Collection++.swift in Sources */,
|
||||||
EC5050072BF65CED00B4D86E /* PenDropDelegate.swift in Sources */,
|
EC5050072BF65CED00B4D86E /* PenDropDelegate.swift in Sources */,
|
||||||
ECA738A32BE6020A00A4542E /* CGFloat++.swift in Sources */,
|
ECA738A32BE6020A00A4542E /* CGFloat++.swift in Sources */,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import SwiftUI
|
|||||||
struct MemolaApp: App {
|
struct MemolaApp: App {
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
MemosView()
|
DashboardView()
|
||||||
.persistence(\.viewContext)
|
.persistence(\.viewContext)
|
||||||
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willTerminateNotification)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willTerminateNotification)) { _ in
|
||||||
withPersistenceSync(\.viewContext) { context in
|
withPersistenceSync(\.viewContext) { context in
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ struct Placeholder: View {
|
|||||||
.foregroundStyle(.primary)
|
.foregroundStyle(.primary)
|
||||||
Text(info.description)
|
Text(info.description)
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
|
.lineLimit(.none)
|
||||||
.fontWeight(.regular)
|
.fontWeight(.regular)
|
||||||
.frame(minHeight: 50, alignment: .top)
|
.frame(minHeight: 50, alignment: .top)
|
||||||
}
|
}
|
||||||
@@ -41,15 +42,29 @@ extension Placeholder {
|
|||||||
|
|
||||||
static let memoNotFound: Info = {
|
static let memoNotFound: Info = {
|
||||||
let icon: String = "sparkle.magnifyingglass"
|
let icon: String = "sparkle.magnifyingglass"
|
||||||
let title: String = "No Memos Found"
|
let title: String = "Memos Not Found"
|
||||||
let description: String = "Explore your other memos or create your own."
|
let description: String = "There are no memos matching your search.\n Please try different keywords or create a new memo."
|
||||||
return Placeholder.Info(title: title, description: description, icon: icon)
|
return Placeholder.Info(title: title, description: description, icon: icon)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
static let memoEmpty: Info = {
|
static let memoEmpty: Info = {
|
||||||
let icon: String = "note.text"
|
let icon: String = "note.text"
|
||||||
let title: String = "No Memos"
|
let title: String = "No Memos Available"
|
||||||
let description: String = "Create a new memo to jot your thoughts or notes down."
|
let description: String = "You have not created any memos yet.\n Tap the 'New Memo' button to get started."
|
||||||
|
return Placeholder.Info(title: title, description: description, icon: icon)
|
||||||
|
}()
|
||||||
|
|
||||||
|
static let trashEmpty: Info = {
|
||||||
|
let icon: String = "trash"
|
||||||
|
let title: String = "Trash is Empty"
|
||||||
|
let description: String = "There are no memos in the trash.\n Deleted memos will appear here."
|
||||||
|
return Placeholder.Info(title: title, description: description, icon: icon)
|
||||||
|
}()
|
||||||
|
|
||||||
|
static let trashNotFound: Info = {
|
||||||
|
let icon: String = "exclamationmark.magnifyingglass"
|
||||||
|
let title: String = "No Deleted Memos Found"
|
||||||
|
let description: String = "No memos found in the trash matching your search.\n Try different keywords or check back later."
|
||||||
return Placeholder.Info(title: title, description: description, icon: icon)
|
return Placeholder.Info(title: title, description: description, icon: icon)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|||||||
29
Memola/Features/Dashboard/Dashboard/DashboardView.swift
Normal file
29
Memola/Features/Dashboard/Dashboard/DashboardView.swift
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// DashboardView.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 6/29/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct DashboardView: View {
|
||||||
|
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
||||||
|
|
||||||
|
@State var sidebarItem: SidebarItem? = .memos
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationSplitView {
|
||||||
|
Sidebar(sidebarItem: $sidebarItem, horizontalSizeClass: horizontalSizeClass)
|
||||||
|
} detail: {
|
||||||
|
switch sidebarItem {
|
||||||
|
case .memos:
|
||||||
|
MemosView()
|
||||||
|
case .trash:
|
||||||
|
TrashView()
|
||||||
|
default:
|
||||||
|
MemosView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,13 +16,15 @@ struct MemosView: View {
|
|||||||
@State var query: String = ""
|
@State var query: String = ""
|
||||||
@State var currentDate: Date = .now
|
@State var currentDate: Date = .now
|
||||||
|
|
||||||
@AppStorage("memola.memo-objects.sort") var sort: Sort = .recent
|
@AppStorage("memola.memo-objects.memos.sort") var sort: Sort = .recent
|
||||||
@AppStorage("memola.memo-objects.filter") var filter: Filter = .none
|
@AppStorage("memola.memo-objects.memos.filter") var filter: Filter = .none
|
||||||
|
|
||||||
let cellWidth: CGFloat = 250
|
|
||||||
let cellHeight: CGFloat = 150
|
|
||||||
let timer = Timer.publish(every: 60, on: .main, in: .common).autoconnect()
|
let timer = Timer.publish(every: 60, on: .main, in: .common).autoconnect()
|
||||||
|
|
||||||
|
var placeholder: Placeholder.Info {
|
||||||
|
query.isEmpty ? .memoEmpty : .memoNotFound
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
let standard = UserDefaults.standard
|
let standard = UserDefaults.standard
|
||||||
var descriptors: [SortDescriptor<MemoObject>] = []
|
var descriptors: [SortDescriptor<MemoObject>] = []
|
||||||
@@ -38,57 +40,43 @@ struct MemosView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
MemoGrid(memoObjects: memoObjects, placeholder: placeholder) { memoObject in
|
||||||
memoGrid
|
memoCard(memoObject)
|
||||||
.searchable(text: $query, placement: .toolbar, prompt: Text("Search"))
|
}
|
||||||
.toolbar {
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.searchable(text: $query, placement: .toolbar, prompt: Text("Search"))
|
||||||
|
.toolbar {
|
||||||
|
if horizontalSizeClass == .compact {
|
||||||
|
ToolbarItem(placement: .principal) {
|
||||||
|
Text("Memos")
|
||||||
|
.font(.title3)
|
||||||
|
.fontWeight(.bold)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
ToolbarItem(placement: .topBarLeading) {
|
ToolbarItem(placement: .topBarLeading) {
|
||||||
Text("Memola")
|
Text("Memola")
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
}
|
}
|
||||||
ToolbarItemGroup(placement: .topBarTrailing) {
|
}
|
||||||
HStack(spacing: 5) {
|
ToolbarItemGroup(placement: .topBarTrailing) {
|
||||||
Button {
|
HStack(spacing: 5) {
|
||||||
createMemo(title: "Untitled")
|
Button {
|
||||||
} label: {
|
createMemo(title: "Untitled")
|
||||||
Image(systemName: "square.and.pencil")
|
} label: {
|
||||||
}
|
Image(systemName: "square.and.pencil")
|
||||||
.hoverEffect(.lift)
|
}
|
||||||
if horizontalSizeClass == .compact {
|
.hoverEffect(.lift)
|
||||||
Menu {
|
if horizontalSizeClass == .compact {
|
||||||
VStack {
|
Menu {
|
||||||
Picker("", selection: $sort) {
|
VStack {
|
||||||
ForEach(Sort.all) { sort in
|
|
||||||
Text(sort.name)
|
|
||||||
.tag(sort)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.pickerStyle(.automatic)
|
|
||||||
Picker("", selection: $filter) {
|
|
||||||
ForEach(Filter.all) { filter in
|
|
||||||
Text(filter.name)
|
|
||||||
.tag(filter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.pickerStyle(.automatic)
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "ellipsis.circle")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Menu {
|
|
||||||
Picker("", selection: $sort) {
|
Picker("", selection: $sort) {
|
||||||
ForEach(Sort.all) { sort in
|
ForEach(Sort.all) { sort in
|
||||||
Text(sort.name)
|
Text(sort.name)
|
||||||
.tag(sort)
|
.tag(sort)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} label: {
|
.pickerStyle(.automatic)
|
||||||
Image(systemName: "arrow.up.arrow.down.circle")
|
|
||||||
}
|
|
||||||
.hoverEffect(.lift)
|
|
||||||
Menu {
|
|
||||||
Picker("", selection: $filter) {
|
Picker("", selection: $filter) {
|
||||||
ForEach(Filter.all) { filter in
|
ForEach(Filter.all) { filter in
|
||||||
Text(filter.name)
|
Text(filter.name)
|
||||||
@@ -96,66 +84,67 @@ struct MemosView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.pickerStyle(.automatic)
|
.pickerStyle(.automatic)
|
||||||
} label: {
|
|
||||||
Image(systemName: "line.3.horizontal.decrease.circle")
|
|
||||||
}
|
}
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "ellipsis.circle")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Menu {
|
||||||
|
Picker("", selection: $sort) {
|
||||||
|
ForEach(Sort.all) { sort in
|
||||||
|
Text(sort.name)
|
||||||
|
.tag(sort)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "arrow.up.arrow.down.circle")
|
||||||
|
}
|
||||||
|
.hoverEffect(.lift)
|
||||||
|
Menu {
|
||||||
|
Picker("", selection: $filter) {
|
||||||
|
ForEach(Filter.all) { filter in
|
||||||
|
Text(filter.name)
|
||||||
|
.tag(filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(.automatic)
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "line.3.horizontal.decrease.circle")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.fullScreenCover(item: $memo) { memo in
|
.fullScreenCover(item: $memo) { memo in
|
||||||
MemoView(memo: memo)
|
MemoView(memo: memo)
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
withPersistence(\.viewContext) { context in
|
withPersistence(\.viewContext) { context in
|
||||||
try context.saveIfNeeded()
|
try context.saveIfNeeded()
|
||||||
context.refreshAllObjects()
|
context.refreshAllObjects()
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onChange(of: sort) { oldValue, newValue in
|
|
||||||
memoObjects.sortDescriptors = newValue.memoSortDescriptors
|
|
||||||
}
|
|
||||||
.onChange(of: query) { oldValue, newValue in
|
|
||||||
updatePredicate()
|
|
||||||
}
|
|
||||||
.onChange(of: filter) { oldValue, newValue in
|
|
||||||
updatePredicate()
|
|
||||||
}
|
|
||||||
.onReceive(timer) { date in
|
|
||||||
currentDate = date
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
memoObjects.sortDescriptors = sort.memoSortDescriptors
|
|
||||||
updatePredicate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
var memoGrid: some View {
|
|
||||||
if memoObjects.isEmpty {
|
|
||||||
Placeholder(info: query.isEmpty ? .memoEmpty : .memoNotFound)
|
|
||||||
} else {
|
|
||||||
GeometryReader { proxy in
|
|
||||||
let count = Int(proxy.size.width / cellWidth)
|
|
||||||
let columns: [GridItem] = .init(repeating: GridItem(.flexible(), spacing: 15), count: count)
|
|
||||||
ScrollView {
|
|
||||||
LazyVGrid(columns: columns, spacing: 15) {
|
|
||||||
ForEach(memoObjects) { memo in
|
|
||||||
memoCard(memo)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
.onChange(of: sort) { oldValue, newValue in
|
||||||
|
memoObjects.sortDescriptors = newValue.memoSortDescriptors
|
||||||
|
}
|
||||||
|
.onChange(of: query) { oldValue, newValue in
|
||||||
|
updatePredicate()
|
||||||
|
}
|
||||||
|
.onChange(of: filter) { oldValue, newValue in
|
||||||
|
updatePredicate()
|
||||||
|
}
|
||||||
|
.onReceive(timer) { date in
|
||||||
|
currentDate = date
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
memoObjects.sortDescriptors = sort.memoSortDescriptors
|
||||||
|
updatePredicate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func memoCard(_ memoObject: MemoObject) -> some View {
|
func memoCard(_ memoObject: MemoObject) -> some View {
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
MemoCard(memoObject: memoObject) { card in
|
||||||
Rectangle()
|
card
|
||||||
.frame(height: cellHeight)
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
Button {
|
Button {
|
||||||
openMemo(for: memoObject)
|
openMemo(for: memoObject)
|
||||||
@@ -183,15 +172,10 @@ struct MemosView: View {
|
|||||||
}
|
}
|
||||||
.padding(5)
|
.padding(5)
|
||||||
}
|
}
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
} details: {
|
||||||
Text(memoObject.title)
|
Text("Edited \(memoObject.updatedAt.getTimeDifference(to: currentDate))")
|
||||||
.font(.headline)
|
.font(.caption)
|
||||||
.lineLimit(1)
|
.foregroundStyle(.secondary)
|
||||||
.truncationMode(.tail)
|
|
||||||
Text("Edited \(memoObject.updatedAt.getTimeDifference(to: currentDate))")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
openMemo(for: memoObject)
|
openMemo(for: memoObject)
|
||||||
@@ -272,6 +256,7 @@ struct MemosView: View {
|
|||||||
|
|
||||||
func markAsTrash(for memo: MemoObject) {
|
func markAsTrash(for memo: MemoObject) {
|
||||||
memo.isTrash = true
|
memo.isTrash = true
|
||||||
|
memo.deletedAt = .now
|
||||||
withPersistence(\.viewContext) { context in
|
withPersistence(\.viewContext) { context in
|
||||||
try context.saveIfNeeded()
|
try context.saveIfNeeded()
|
||||||
}
|
}
|
||||||
@@ -46,4 +46,19 @@ extension Sort {
|
|||||||
return [SortDescriptor(\.createdAt)]
|
return [SortDescriptor(\.createdAt)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var trashSortDescriptors: [SortDescriptor<MemoObject>] {
|
||||||
|
switch self {
|
||||||
|
case .recent:
|
||||||
|
return [SortDescriptor(\.updatedAt, order: .reverse)]
|
||||||
|
case .aToZ:
|
||||||
|
return [SortDescriptor(\.title), SortDescriptor(\.updatedAt, order: .reverse)]
|
||||||
|
case .zToA:
|
||||||
|
return [SortDescriptor(\.title, order: .reverse), SortDescriptor(\.updatedAt, order: .reverse)]
|
||||||
|
case .newest:
|
||||||
|
return [SortDescriptor(\.createdAt, order: .reverse)]
|
||||||
|
case .oldest:
|
||||||
|
return [SortDescriptor(\.createdAt)]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
37
Memola/Features/Dashboard/Details/Shared/MemoCard.swift
Normal file
37
Memola/Features/Dashboard/Details/Shared/MemoCard.swift
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
//
|
||||||
|
// MemoCard.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 6/29/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct MemoCard<Preview: View, Detail: View>: View {
|
||||||
|
let memoObject: MemoObject
|
||||||
|
let modifyPreview: ((MemoPreview) -> Preview)?
|
||||||
|
let details: () -> Detail
|
||||||
|
|
||||||
|
init(memoObject: MemoObject, @ViewBuilder modifyPreview: @escaping (MemoPreview) -> Preview, @ViewBuilder details: @escaping () -> Detail) {
|
||||||
|
self.memoObject = memoObject
|
||||||
|
self.modifyPreview = modifyPreview
|
||||||
|
self.details = details
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
if let modifyPreview {
|
||||||
|
modifyPreview(MemoPreview())
|
||||||
|
} else {
|
||||||
|
MemoPreview()
|
||||||
|
}
|
||||||
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
Text(memoObject.title)
|
||||||
|
.font(.headline)
|
||||||
|
.lineLimit(1)
|
||||||
|
.truncationMode(.tail)
|
||||||
|
details()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
Memola/Features/Dashboard/Details/Shared/MemoGrid.swift
Normal file
36
Memola/Features/Dashboard/Details/Shared/MemoGrid.swift
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// MemoGrid.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 6/29/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct MemoGrid<Card: View>: View {
|
||||||
|
let cellWidth: CGFloat = 250
|
||||||
|
let cellHeight: CGFloat = 150
|
||||||
|
|
||||||
|
let memoObjects: FetchedResults<MemoObject>
|
||||||
|
let placeholder: Placeholder.Info
|
||||||
|
@ViewBuilder let card: (MemoObject) -> Card
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if memoObjects.isEmpty {
|
||||||
|
Placeholder(info: placeholder)
|
||||||
|
} else {
|
||||||
|
GeometryReader { proxy in
|
||||||
|
let count = Int(proxy.size.width / cellWidth)
|
||||||
|
let columns: [GridItem] = .init(repeating: GridItem(.flexible(), spacing: 15), count: count)
|
||||||
|
ScrollView {
|
||||||
|
LazyVGrid(columns: columns, spacing: 15) {
|
||||||
|
ForEach(memoObjects) { memoObject in
|
||||||
|
card(memoObject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Memola/Features/Dashboard/Details/Shared/MemoPreview.swift
Normal file
18
Memola/Features/Dashboard/Details/Shared/MemoPreview.swift
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// MemoPreview.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 6/29/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct MemoPreview: View {
|
||||||
|
let cellHeight: CGFloat = 150
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Rectangle()
|
||||||
|
.frame(height: cellHeight)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
|
}
|
||||||
|
}
|
||||||
85
Memola/Features/Dashboard/Details/Trash/TrashView.swift
Normal file
85
Memola/Features/Dashboard/Details/Trash/TrashView.swift
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
//
|
||||||
|
// TrashView.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 6/29/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct TrashView: View {
|
||||||
|
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
||||||
|
|
||||||
|
@FetchRequest var memoObjects: FetchedResults<MemoObject>
|
||||||
|
|
||||||
|
@State var query: String = ""
|
||||||
|
|
||||||
|
var placeholder: Placeholder.Info {
|
||||||
|
query.isEmpty ? .trashEmpty : .trashNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
let descriptors = [SortDescriptor(\MemoObject.deletedAt, order: .reverse)]
|
||||||
|
let predicate = NSPredicate(format: "isTrash = YES")
|
||||||
|
_memoObjects = FetchRequest(sortDescriptors: descriptors, predicate: predicate)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
MemoGrid(memoObjects: memoObjects, placeholder: placeholder) { memoObject in
|
||||||
|
memoCard(memoObject)
|
||||||
|
}
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.searchable(text: $query, placement: .toolbar, prompt: Text("Search"))
|
||||||
|
.toolbar {
|
||||||
|
if horizontalSizeClass == .compact {
|
||||||
|
ToolbarItem(placement: .principal) {
|
||||||
|
Text("Trash")
|
||||||
|
.font(.title3)
|
||||||
|
.fontWeight(.bold)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ToolbarItem(placement: .topBarLeading) {
|
||||||
|
Text("Memola")
|
||||||
|
.font(.title3)
|
||||||
|
.fontWeight(.bold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: query) { oldValue, newValue in
|
||||||
|
updatePredicate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func memoCard(_ memoObject: MemoObject) -> some View {
|
||||||
|
MemoCard(memoObject: memoObject) { card in
|
||||||
|
card
|
||||||
|
.contextMenu {
|
||||||
|
Button {
|
||||||
|
|
||||||
|
} label: {
|
||||||
|
Label("Restore", systemImage: "square.and.arrow.down")
|
||||||
|
}
|
||||||
|
Button(role: .destructive) {
|
||||||
|
|
||||||
|
} label: {
|
||||||
|
Label("Delete Permanently", systemImage: "trash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} details: {
|
||||||
|
Text("Deleted on \(memoObject.deletedAt.formatted(date: .abbreviated, time: .standard))")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updatePredicate() {
|
||||||
|
var predicates: [NSPredicate] = [NSPredicate(format: "isTrash = YES")]
|
||||||
|
if !query.isEmpty {
|
||||||
|
predicates.append(NSPredicate(format: "title contains[c] %@", query))
|
||||||
|
}
|
||||||
|
memoObjects.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
|
||||||
|
}
|
||||||
|
}
|
||||||
78
Memola/Features/Dashboard/Sidebar/Sidebar.swift
Normal file
78
Memola/Features/Dashboard/Sidebar/Sidebar.swift
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
//
|
||||||
|
// Sidebar.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 6/29/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct Sidebar: View {
|
||||||
|
let sidebarItems: [SidebarItem] = [.memos, .trash]
|
||||||
|
@Binding var sidebarItem: SidebarItem?
|
||||||
|
|
||||||
|
let horizontalSizeClass: UserInterfaceSizeClass?
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
List(selection: $sidebarItem) {
|
||||||
|
ForEach(sidebarItems) { item in
|
||||||
|
Button {
|
||||||
|
sidebarItem = item
|
||||||
|
} label: {
|
||||||
|
Label(item.title, systemImage: item.icon)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
}
|
||||||
|
.buttonStyle(sidebarItem == item ? .selected : .unselected)
|
||||||
|
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listStyle(.sidebar)
|
||||||
|
.navigationTitle(horizontalSizeClass == .compact ? "Memola" : "")
|
||||||
|
.scrollContentBackground(.hidden)
|
||||||
|
.background(Color(uiColor: .secondarySystemFill))
|
||||||
|
.navigationSplitViewColumnWidth(min: 250, ideal: 250, max: 250)
|
||||||
|
.navigationBarTitleDisplayMode(horizontalSizeClass == .compact ? .automatic : .inline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Sidebar {
|
||||||
|
struct SidebarItemButtonStyle: ButtonStyle {
|
||||||
|
let state: State
|
||||||
|
|
||||||
|
func makeBody(configuration: Configuration) -> some View {
|
||||||
|
configuration.label
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
#if os(macOS)
|
||||||
|
.padding(10)
|
||||||
|
#else
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
#endif
|
||||||
|
.contentShape(RoundedRectangle(cornerRadius: 10))
|
||||||
|
.background {
|
||||||
|
switch state {
|
||||||
|
case .selected:
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.fill(.primary.opacity(0.08))
|
||||||
|
case .unselected:
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
case selected
|
||||||
|
case unselected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ButtonStyle where Self == Sidebar.SidebarItemButtonStyle {
|
||||||
|
static var selected: Sidebar.SidebarItemButtonStyle {
|
||||||
|
Sidebar.SidebarItemButtonStyle(state: .selected)
|
||||||
|
}
|
||||||
|
|
||||||
|
static var unselected: Sidebar.SidebarItemButtonStyle {
|
||||||
|
Sidebar.SidebarItemButtonStyle(state: .unselected)
|
||||||
|
}
|
||||||
|
}
|
||||||
35
Memola/Features/Dashboard/Sidebar/SidebarItem.swift
Normal file
35
Memola/Features/Dashboard/Sidebar/SidebarItem.swift
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
//
|
||||||
|
// SidebarItem.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 6/29/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum SidebarItem: String, Identifiable, Hashable, Equatable {
|
||||||
|
var id: String { rawValue }
|
||||||
|
|
||||||
|
case memos
|
||||||
|
case trash
|
||||||
|
|
||||||
|
var title: String {
|
||||||
|
switch self {
|
||||||
|
case .memos:
|
||||||
|
"Memos"
|
||||||
|
case .trash:
|
||||||
|
"Trash"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var icon: String {
|
||||||
|
switch self {
|
||||||
|
case .memos:
|
||||||
|
"rectangle.3.group"
|
||||||
|
case .trash:
|
||||||
|
"trash"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static let all: [SidebarItem] = [.memos, .trash]
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ final class MemoObject: NSManagedObject, Identifiable {
|
|||||||
@NSManaged var title: String
|
@NSManaged var title: String
|
||||||
@NSManaged var createdAt: Date
|
@NSManaged var createdAt: Date
|
||||||
@NSManaged var updatedAt: Date
|
@NSManaged var updatedAt: Date
|
||||||
|
@NSManaged var deletedAt: Date
|
||||||
@NSManaged var isFavorite: Bool
|
@NSManaged var isFavorite: Bool
|
||||||
@NSManaged var isTrash: Bool
|
@NSManaged var isTrash: Bool
|
||||||
@NSManaged var tool: ToolObject
|
@NSManaged var tool: ToolObject
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
</entity>
|
</entity>
|
||||||
<entity name="MemoObject" representedClassName="MemoObject" syncable="YES">
|
<entity name="MemoObject" representedClassName="MemoObject" syncable="YES">
|
||||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="deletedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
<attribute name="isFavorite" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
<attribute name="isFavorite" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||||
<attribute name="isTrash" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
<attribute name="isTrash" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||||
<attribute name="title" attributeType="String"/>
|
<attribute name="title" attributeType="String"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user