Created
June 30, 2020 12:27
-
-
Save chriseidhof/a09473fcef9ac3f2af820eab14b951b6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ContentView.swift | |
// NotesClone | |
// | |
// Created by Chris Eidhof on 29.06.20. | |
// | |
import SwiftUI | |
@main | |
struct NotesCloneApp: App { | |
var body: some Scene { | |
WindowGroup { | |
ContentView() | |
} | |
} | |
} | |
struct Note: Codable, Identifiable, Hashable { | |
private(set) var id = UUID() | |
var createdAt = Date() | |
var title: String | |
var text: String | |
} | |
let encoder = JSONEncoder() | |
let decoder = JSONDecoder() | |
final class JSONStore<A>: ObservableObject { | |
@Published var value: A { | |
didSet { | |
// print("Did set: \(value)") | |
} | |
} | |
init(_ initialValue: A) { | |
self.value = initialValue | |
} | |
} | |
extension Data { | |
var notes: [UUID: Note] { | |
get { | |
return (try? decoder.decode([UUID: Note].self, from: self)) ?? [:] | |
} | |
set { | |
self = (try? encoder.encode(newValue)) ?? .init() | |
} | |
} | |
} | |
struct Folder: Identifiable, Codable, Hashable { | |
var id = UUID() | |
var name = "On My Mac" | |
var notes: [UUID: Note] | |
} | |
func sample() -> [Folder] { | |
let note = Note(title: "My Note", text: "My first note.") | |
let f = Folder(notes: [note.id: note]) | |
return [f] | |
} | |
struct ContentView: View { | |
@ObservedObject var store: JSONStore<[Folder]> = JSONStore(sample()) | |
@State var selectedIndex: Set<Int> = [0] | |
var body: some View { | |
NavigationView { | |
VStack(alignment: .leading) { | |
List(selection: $selectedIndex) { | |
ForEach(store.value) { folder in | |
NavigationLink( | |
destination: FolderView(store: store, folder: folder), | |
label: { | |
Label(folder.name, systemImage: "folder") | |
} | |
).tag(folder.id) | |
} | |
}.listStyle(SidebarListStyle()) | |
Button(action: { | |
store.value.append(Folder(name: "New Folder", notes: [:])) | |
}) { | |
HStack { | |
Image(systemName: "plus.circle.fill") | |
Text("New Folder") | |
}.padding() | |
}.buttonStyle(PlainButtonStyle()) | |
} | |
.frame(minWidth: 100, maxWidth: 200, maxHeight: .infinity) | |
Text("Select a note").frame(maxWidth: .infinity, maxHeight: .infinity) | |
}.navigationViewStyle(DoubleColumnNavigationViewStyle()) | |
} | |
} | |
extension Dictionary { | |
subscript(unsafe key: Key) -> Value { | |
get { self[key]! } | |
set { self[key] = newValue } | |
} | |
} | |
extension Dictionary where Value == Note { | |
var sortedKeys: [Key] { | |
return self.sorted(by: { $0.value.createdAt > $1.value.createdAt }).map { $0.key } | |
} | |
} | |
enum ListOrGrid: Hashable { | |
case list | |
case grid | |
} | |
struct FolderView: View { | |
@ObservedObject var store: JSONStore<[Folder]> | |
var folder: Folder | |
var notes: [UUID:Note] { folder.notes } | |
@State var state: ListOrGrid = .list | |
var folderBinding: Binding<Folder> { | |
let folderIx = store.value.firstIndex(where: { $0.id == folder.id })! | |
return $store.value[folderIx] | |
} | |
func noteBinding(id: UUID) -> Binding<Note> { | |
folderBinding.notes[unsafe: id] | |
} | |
func noteLink(id: UUID) -> some View { | |
let dest: NoteView = NoteView(note: noteBinding(id: id)) | |
return NavigationLink( | |
destination: dest, | |
label: { | |
VStack(alignment: .leading) { | |
let note = notes[unsafe: id] | |
Text(note.title).font(.headline) | |
HStack { | |
Text(note.createdAt, style: .date).layoutPriority(10) | |
Text(note.text).lineLimit(1) | |
} | |
} | |
.padding(.vertical, 8) | |
}) | |
} | |
func gridCell(id: UUID) -> some View { | |
print(selectedNote == id) | |
let rect = RoundedRectangle(cornerRadius: 10) | |
let cell = rect | |
.fill(BackgroundStyle()) | |
.shadow(color: Color.gray.opacity(0.5), radius: 5) | |
.overlay(rect.fill(selectedNote == id ? Color.yellow : Color.clear)) | |
.overlay( | |
Text(notes[unsafe: id].text) | |
.font(Font.caption2) | |
.padding() | |
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) | |
) | |
.onTapGesture(count: 1) { | |
print("Selecting \(id)") | |
} | |
.onTapGesture(count: 2) { | |
print("Openiing \(id)") | |
self.selectedNote = id | |
} | |
return | |
cell.frame(width: 200, height: 150) | |
} | |
@State var selectedNote: UUID? = nil | |
@State var selectedNotes: Set<UUID> = [] | |
var body: some View { | |
Group { | |
switch state { | |
case .list: | |
NavigationView { | |
List(selection: $selectedNotes) { | |
ForEach(Array(notes.sortedKeys), id: \.self) { (id: UUID) in | |
noteLink(id: id).tag(id) | |
}.onDelete { indices in | |
let keys = notes.sortedKeys | |
let ids = indices.map { keys[$0] } | |
for i in ids { | |
// self.store.delete(note) = nil | |
print("TODO delete \(i)") | |
} | |
} | |
}.listStyle(InsetListStyle()) | |
Text("TODO").frame(maxWidth: .infinity, maxHeight: .infinity) | |
} | |
case .grid: | |
ScrollView { | |
LazyVGrid(columns: [GridItem(.adaptive(minimum: 200))], alignment: .leading) { | |
ForEach(Array(notes.sortedKeys), id: \.self) { id in | |
gridCell(id: id) | |
.frame(width: 200, height: 150) | |
} | |
}.padding() | |
}.frame(maxWidth: .infinity, maxHeight: .infinity) | |
} | |
} | |
.navigationTitle(folder.name) | |
.toolbar { | |
ToolbarItem(placement: .navigation) { | |
Picker(selection: $state, label: EmptyView()) { | |
Image(systemName: "list.bullet").padding(4).tag(ListOrGrid.list) | |
Image(systemName: "square.grid.2x2").padding(4).tag(ListOrGrid.grid) | |
// Label("List", systemImage: "list.bullet").tag(ListOrGrid.list) | |
// Label("Grid", systemImage: "square.grid.2x2").tag(ListOrGrid.grid) | |
} | |
.labelStyle(IconOnlyLabelStyle()) | |
.pickerStyle(SegmentedPickerStyle()) | |
} | |
ToolbarItem(placement: .primaryAction) { | |
Button(action: { | |
let newNote = Note(title: "New Note", text: "New Note \(Date())") | |
withAnimation(.default) { | |
self.folderBinding.notes[newNote.id].wrappedValue = newNote | |
print(self.folderBinding.notes.wrappedValue.count) | |
} | |
}) { | |
Image(systemName: "square.and.pencil") | |
} | |
} | |
} | |
} | |
} | |
struct NoteView: View { | |
@Binding var note: Note | |
var body: some View { | |
TextEditor(text: $note.text) | |
.padding() | |
.frame(maxWidth: .infinity, maxHeight: .infinity) | |
.edgesIgnoringSafeArea(.all) | |
.navigationTitle(note.title) | |
.toolbar(items: { | |
ToolbarItem(placement: .primaryAction) { | |
Button { | |
} label: { | |
// todo the image doesn't show up? the button's there but blank. | |
Label("Share", systemImage: "square.and.arrow.up") | |
.labelStyle(IconOnlyLabelStyle()) | |
} | |
} | |
}) | |
.presentedWindowToolbarStyle(UnifiedCompactWindowToolbarStyle()) | |
.presentedWindowStyle(HiddenTitleBarWindowStyle()) | |
} | |
} | |
struct ContentView_Previews: PreviewProvider { | |
static var previews: some View { | |
ContentView() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment