diff --git a/PiecesOfPaper/Model/NotificationName.swift b/PiecesOfPaper/Model/NotificationName.swift index 08222ad..1deb880 100644 --- a/PiecesOfPaper/Model/NotificationName.swift +++ b/PiecesOfPaper/Model/NotificationName.swift @@ -10,5 +10,5 @@ import Foundation public extension Notification.Name { static let addedNewNote = Notification.Name("addedNewNote") - static let channgedTagToNote = Notification.Name("channgedTagToNote") + static let changedTagToNote = Notification.Name("changedTagToNote") } diff --git a/PiecesOfPaper/Model/UserPreference.swift b/PiecesOfPaper/Model/UserPreference.swift index a6540bc..8134a59 100644 --- a/PiecesOfPaper/Model/UserPreference.swift +++ b/PiecesOfPaper/Model/UserPreference.swift @@ -11,6 +11,7 @@ import Foundation struct UserPreference { private let iCloudDisabledKey = "iCloud_disabled" private let autoSaveDisabledKey = "autosave_disabled" + private let infiniteScrollKey = "infinite_scroll_disabled" var enablediCloud: Bool { get { @@ -29,4 +30,13 @@ struct UserPreference { UserDefaults.standard.set(!newValue, forKey: autoSaveDisabledKey) } } + + var enabledInfiniteScroll: Bool { + get { + !UserDefaults.standard.bool(forKey: infiniteScrollKey) + } + set { + UserDefaults.standard.set(!newValue, forKey: infiniteScrollKey) + } + } } diff --git a/PiecesOfPaper/PiecesOfPaperApp.swift b/PiecesOfPaper/PiecesOfPaperApp.swift index a9f0ce9..363f663 100644 --- a/PiecesOfPaper/PiecesOfPaperApp.swift +++ b/PiecesOfPaper/PiecesOfPaperApp.swift @@ -14,7 +14,7 @@ struct PiecesOfPaperApp: App { @State var isAppLaunch = true @State var isShowCanvas = false @State var isShowTagList = false - @State var noteDocument: NoteDocument? + @StateObject var canvasViewModel = CanvasViewModel() var body: some Scene { WindowGroup { @@ -23,7 +23,7 @@ struct PiecesOfPaperApp: App { } .fullScreenCover(isPresented: $isShowCanvas) { NavigationView { - Canvas(noteDocument: noteDocument) + Canvas(viewModel: canvasViewModel) } } .sheet(isPresented: $isShowTagList, onDismiss: { @@ -33,7 +33,7 @@ struct PiecesOfPaperApp: App { } .onAppear { guard isAppLaunch else { return } - CanvasRouter.shared.bind(isShowCanvas: $isShowCanvas, noteDocument: $noteDocument) + CanvasRouter.shared.bind(isShowCanvas: $isShowCanvas, noteDocument: $canvasViewModel.document) CanvasRouter.shared.openNewCanvas() // I thought this can work, but SwiftUI cannot pass the document data... TagListRouter.shared.bind(isShowTagList: $isShowTagList) @@ -41,6 +41,5 @@ struct PiecesOfPaperApp: App { isAppLaunch = false } } -// } } } diff --git a/PiecesOfPaper/SideBarList.swift b/PiecesOfPaper/SideBarList.swift index e91055a..a8651ce 100644 --- a/PiecesOfPaper/SideBarList.swift +++ b/PiecesOfPaper/SideBarList.swift @@ -10,17 +10,21 @@ import SwiftUI struct SideBarList: View { @Binding var isAppLaunch: Bool + @StateObject var inboxNoteViewModel = NotesViewModel(targetDirectory: .inbox) + @StateObject var allNoteViewModel = NotesViewModel(targetDirectory: .all) + @StateObject var archivedNoteViewModel = NotesViewModel(targetDirectory: .archived) var body: some View { List { Section(header: Text("Folder")) { - NavigationLink(destination: Notes(targetDirectory: .inbox), isActive: $isAppLaunch) { + NavigationLink(destination: Notes(viewModel: inboxNoteViewModel), + isActive: $isAppLaunch) { Label("Inbox", systemImage: "tray") } - NavigationLink(destination: Notes(targetDirectory: .all)) { + NavigationLink(destination: Notes(viewModel: allNoteViewModel)) { Label("All", systemImage: "note.text") } - NavigationLink(destination: Notes(targetDirectory: .archived)) { + NavigationLink(destination: Notes(viewModel: archivedNoteViewModel)) { Label("Archived", systemImage: "archivebox") } } diff --git a/PiecesOfPaper/View/Canvas/Canvas.swift b/PiecesOfPaper/View/Canvas/Canvas.swift index 93a9605..e5f4f26 100644 --- a/PiecesOfPaper/View/Canvas/Canvas.swift +++ b/PiecesOfPaper/View/Canvas/Canvas.swift @@ -12,39 +12,17 @@ import StoreKit import LinkPresentation struct Canvas: View { - @ObservedObject var viewModel = CanvasViewModel() - @State private var canvasView = PKCanvasView() - @State var isShowActivityView = false { - didSet { - if isShowActivityView == true { - toolPicker.setVisible(false, forFirstResponder: canvasView) - } - } - } + @ObservedObject var viewModel: CanvasViewModel @Environment(\.presentationMode) var presentationMode @AppStorage("review_requested") var reviewRequested = false - var delegateBridge: CanvasDelegateBridgeObject - var toolPicker = PKToolPicker() - var activityViewController: UIActivityViewControllerWrapper { - let drawing = canvasView.drawing - var image = UIImage() - let trait = UITraitCollection(userInterfaceStyle: .light) - trait.performAsCurrent { - image = drawing.image(from: drawing.bounds, scale: UIScreen.main.scale) - } - - return UIActivityViewControllerWrapper(activityItems: [image, delegateBridge]) - } - var tap: some Gesture { TapGesture(count: 1) .onEnded { _ in viewModel.hideExceptPaper.toggle() - toolPicker.addObserver(canvasView) - toolPicker.setVisible(!viewModel.hideExceptPaper, forFirstResponder: canvasView) - canvasView.becomeFirstResponder() + viewModel.setVisibleToolPicker(!viewModel.hideExceptPaper) + viewModel.canvasView.becomeFirstResponder() } } @@ -55,26 +33,8 @@ struct Canvas: View { } var cancelButton: Alert.Button { .default(Text("Cancel")) } - init(noteDocument: NoteDocument?) { - delegateBridge = CanvasDelegateBridgeObject(toolPicker: toolPicker) - if let noteDocument = noteDocument { - viewModel.document = noteDocument - canvasView.drawing = noteDocument.entity.drawing - } - - delegateBridge.canvas = self - canvasView.delegate = delegateBridge - addPencilInteraction() - } - - private func addPencilInteraction() { - let pencilInteraction = UIPencilInteraction() - pencilInteraction.delegate = delegateBridge - canvasView.addInteraction(pencilInteraction) - } - var body: some View { - PKCanvasViewWrapper(canvasView: $canvasView) + PKCanvasViewWrapper(canvasView: $viewModel.canvasView) .gesture(tap) .navigationBarTitleDisplayMode(.inline) .statusBar(hidden: viewModel.hideExceptPaper) @@ -89,7 +49,7 @@ struct Canvas: View { ToolbarItemGroup(placement: .navigationBarTrailing) { if viewModel.document != nil { Button(action: { - toolPicker.setVisible(false, forFirstResponder: canvasView) + viewModel.setVisibleToolPicker(false) viewModel.showTagList.toggle() }) { Image(systemName: "tag.circle") @@ -101,7 +61,7 @@ struct Canvas: View { .popover(isPresented: $viewModel.showDrawingInformation) { NoteInformation(document: viewModel.document) } - Button(action: { isShowActivityView.toggle() }) { + Button(action: { viewModel.isShowActivityView.toggle() }) { Image(systemName: "square.and.arrow.up") } Button(action: close) { @@ -109,11 +69,12 @@ struct Canvas: View { } } } - .sheet(isPresented: $isShowActivityView, - onDismiss: { toolPicker.setVisible(true, forFirstResponder: canvasView) }) { - activityViewController + .sheet(isPresented: $viewModel.isShowActivityView, + onDismiss: { viewModel.setVisibleToolPicker(true) }) { + viewModel.activityViewController } - .sheet(isPresented: $viewModel.showTagList) { + .sheet(isPresented: $viewModel.showTagList, + onDismiss: { viewModel.setVisibleToolPicker(true) }) { TagListToNote(viewModel: TagListToNoteViewModel(noteDocument: viewModel.document)) } .alert(isPresented: $viewModel.showUnsavedAlert) { () -> Alert in @@ -121,29 +82,36 @@ struct Canvas: View { primaryButton: discardButton, secondaryButton: cancelButton) } + .onAppear { + viewModel.hideExceptPaper = true + } } private func archive() { if !UserPreference().enabledAutoSave { - guard !canvasView.drawing.strokes.isEmpty else { + guard !viewModel.canvasView.drawing.strokes.isEmpty else { presentationMode.wrappedValue.dismiss() return } - toolPicker.setVisible(false, forFirstResponder: canvasView) + viewModel.setVisibleToolPicker(false) viewModel.showUnsavedAlert.toggle() return } viewModel.archive() - NotificationCenter.default.post(name: .addedNewNote, object: viewModel.document) + // do not send notification + presentationMode.wrappedValue.dismiss() reviewRequest() } private func close() { if !UserPreference().enabledAutoSave { - viewModel.save(drawing: canvasView.drawing) + viewModel.save(drawing: viewModel.canvasView.drawing) + } + + if viewModel.hasSavedDocument { + NotificationCenter.default.post(name: .addedNewNote, object: viewModel.document) } - NotificationCenter.default.post(name: .addedNewNote, object: viewModel.document) presentationMode.wrappedValue.dismiss() reviewRequest() } diff --git a/PiecesOfPaper/View/Canvas/CanvasDelegateBridgeObject.swift b/PiecesOfPaper/View/Canvas/CanvasDelegateBridgeObject.swift index f415303..a203a6a 100644 --- a/PiecesOfPaper/View/Canvas/CanvasDelegateBridgeObject.swift +++ b/PiecesOfPaper/View/Canvas/CanvasDelegateBridgeObject.swift @@ -10,23 +10,28 @@ import SwiftUI import PencilKit import LinkPresentation +protocol CanvasDelegateBridgeObjectDelegate: AnyObject { + var hideExceptPaper: Bool { get set } + func save(drawing: PKDrawing) +} + // MARK: - PKToolPickerObserver /// This class conform some protocol, because SwiftUI Views cannot conform PencilKit delegates -class CanvasDelegateBridgeObject: NSObject, PKToolPickerObserver { - let toolPicker: PKToolPicker +final class CanvasDelegateBridgeObject: NSObject, PKToolPickerObserver { + let toolPicker = PKToolPicker() private let defaultTool = PKInkingTool(.pen, color: .black, width: 1) private var previousTool: PKTool! private var currentTool: PKTool! - var canvas: Canvas! + weak var delegate: CanvasDelegateBridgeObjectDelegate? - init(toolPicker: PKToolPicker) { - self.toolPicker = toolPicker + override init() { super.init() toolPicker.addObserver(self) toolPicker.selectedTool = defaultTool previousTool = defaultTool currentTool = defaultTool + toolPicker.showsDrawingPolicyControls = false } func toolPickerSelectedToolDidChange(_ toolPicker: PKToolPicker) { @@ -44,7 +49,7 @@ extension CanvasDelegateBridgeObject: UIPencilInteractionDelegate { switch action { case .switchPrevious: switchPreviousTool() case .switchEraser: switchEraser() - case .showColorPalette: canvas.viewModel.hideExceptPaper.toggle() + case .showColorPalette: delegate?.hideExceptPaper.toggle() case .ignore: return default: return } @@ -66,8 +71,25 @@ extension CanvasDelegateBridgeObject: UIPencilInteractionDelegate { // MARK: - PKCanvasViewDelegate extension CanvasDelegateBridgeObject: PKCanvasViewDelegate { func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) { + updateContentSizeIfNeeded(canvasView) + guard UserPreference().enabledAutoSave else { return } - canvas.viewModel.save(drawing: canvasView.drawing) + delegate?.save(drawing: canvasView.drawing) + } + + private func updateContentSizeIfNeeded(_ canvasView: PKCanvasView) { + guard !canvasView.drawing.bounds.isNull, + UserPreference().enabledInfiniteScroll else { return } + let drawingWidth = canvasView.drawing.bounds.maxX + if canvasView.contentSize.width < drawingWidth * 2 { + canvasView.contentSize.width += canvasView.frame.width + } + + let drawingHeight = canvasView.drawing.bounds.maxY + if canvasView.contentSize.height < drawingHeight * 2 { + canvasView.contentSize.height += canvasView.frame.height + } + } } diff --git a/PiecesOfPaper/View/NoteList/NoteImage.swift b/PiecesOfPaper/View/NoteList/NoteImage.swift index 97105f8..7be0e01 100644 --- a/PiecesOfPaper/View/NoteList/NoteImage.swift +++ b/PiecesOfPaper/View/NoteList/NoteImage.swift @@ -9,7 +9,7 @@ import SwiftUI struct NoteImage: View { - var noteDocument: NoteDocument + @Binding var noteDocument: NoteDocument var body: some View { Button(action: { open(noteDocument: noteDocument) }, diff --git a/PiecesOfPaper/View/NoteList/Notes.swift b/PiecesOfPaper/View/NoteList/Notes.swift index 91fea85..37c2328 100644 --- a/PiecesOfPaper/View/NoteList/Notes.swift +++ b/PiecesOfPaper/View/NoteList/Notes.swift @@ -18,10 +18,6 @@ struct Notes: View { } var cancelButton: Alert.Button { .default(Text("Cancel")) } - init(targetDirectory: NotesViewModel.TargetDirectory) { - self.viewModel = NotesViewModel(targetDirectory: targetDirectory) - } - var body: some View { Group { if !viewModel.isLoaded { @@ -36,8 +32,7 @@ struct Notes: View { Text("No Data") .font(.largeTitle) } else { - NotesScrollViewReader() - .environmentObject(viewModel) + NotesScrollViewReader(viewModel: viewModel) } } @@ -74,6 +69,6 @@ struct Notes: View { struct Notes_Previews: PreviewProvider { static var previews: some View { - Notes(targetDirectory: .inbox) + Notes(viewModel: NotesViewModel(targetDirectory: .inbox)) } } diff --git a/PiecesOfPaper/View/NoteList/NotesGrid.swift b/PiecesOfPaper/View/NoteList/NotesGrid.swift index 776e796..38a50cb 100644 --- a/PiecesOfPaper/View/NoteList/NotesGrid.swift +++ b/PiecesOfPaper/View/NoteList/NotesGrid.swift @@ -12,10 +12,7 @@ import PencilKit struct NotesGrid: View { @State var isShowActivityView = false @State var documentToShare: NoteDocument? - @EnvironmentObject var noteViewModel: NotesViewModel - var noteDocuments: [NoteDocument] { - noteViewModel.publishedNoteDocuments - } + @ObservedObject var viewModel: NotesViewModel let gridItem = GridItem(.adaptive(minimum: 250), spacing: 50.0) var activityViewController: UIActivityViewControllerWrapper? { @@ -32,47 +29,47 @@ struct NotesGrid: View { var body: some View { LazyVGrid(columns: [gridItem]) { - ForEach((0..= 5 } + var hasSavedDocument = false + + var activityViewController: UIActivityViewControllerWrapper { + let drawing = canvasView.drawing + var image = UIImage() + let trait = UITraitCollection(userInterfaceStyle: .light) + trait.performAsCurrent { + image = drawing.image(from: drawing.bounds, scale: UIScreen.main.scale) + } + + return UIActivityViewControllerWrapper(activityItems: [image, delegateBridge]) + } + + init(noteDocument: NoteDocument? = nil) { + self.document = noteDocument + + delegateBridge.delegate = self + canvasView.delegate = delegateBridge + delegateBridge.toolPicker.addObserver(canvasView) + addPencilInteraction() + } + + private func createNewDocument() { + defer { + hasSavedDocument = false + } + + guard let inboxUrl = FilePath.inboxUrl else { return } + let path = inboxUrl.appendingPathComponent(FilePath.fileName) + document = NoteDocument(fileURL: path, entity: NoteEntity(drawing: PKDrawing())) + canvasView = PKCanvasView() + } + + private func addPencilInteraction() { + let pencilInteraction = UIPencilInteraction() + pencilInteraction.delegate = delegateBridge + canvasView.addInteraction(pencilInteraction) + } + + func initialContentSize() { + guard !canvasView.drawing.bounds.isNull else { return } + + if canvasView.frame.width < canvasView.drawing.bounds.maxX { + canvasView.contentSize.width = canvasView.drawing.bounds.maxX + } + + if canvasView.frame.height < canvasView.drawing.bounds.maxY { + canvasView.contentSize.height = canvasView.drawing.bounds.maxY + } + } + func save(drawing: PKDrawing) { + defer { + hasSavedDocument = true + } + document?.entity.drawing = drawing document?.entity.updatedDate = Date() - if let document = document { + guard let document = document else { return } + + if FileManager.default.fileExists(atPath: document.fileURL.path) { document.save(to: document.fileURL, for: .forOverwriting) } else { - guard let inboxUrl = FilePath.inboxUrl else { return } - let path = inboxUrl.appendingPathComponent(FilePath.fileName) - document = NoteDocument(fileURL: path, entity: NoteEntity(drawing: drawing)) - document?.save(to: path, for: .forCreating) + document.save(to: document.fileURL, for: .forCreating) } } @@ -44,4 +131,8 @@ final class CanvasViewModel: ObservableObject { print("Could not archive: ", error.localizedDescription) } } + + func setVisibleToolPicker(_ isVisible: Bool) { + delegateBridge.toolPicker.setVisible(isVisible, forFirstResponder: canvasView) + } } diff --git a/PiecesOfPaper/ViewModel/NotesViewModel.swift b/PiecesOfPaper/ViewModel/NotesViewModel.swift index 245c2a4..0265c22 100644 --- a/PiecesOfPaper/ViewModel/NotesViewModel.swift +++ b/PiecesOfPaper/ViewModel/NotesViewModel.swift @@ -93,15 +93,20 @@ final class NotesViewModel: ObservableObject { NotificationCenter.default.publisher(for: .addedNewNote, object: nil) .map({ $0.object as? NoteDocument }) .sink { [weak self] document in - guard let document = document, - self?.shouldInsertStoredArray(isArchived: document.isArchived) ?? false else { return } + guard let self = self, let document = document, + self.shouldInsertStoredArray(isArchived: document.isArchived) else { return } - self?.noteDocuments.append(document) - self?.publish() + if self.noteDocuments.contains(document) { + self.noteDocuments = self.noteDocuments.map { $0 == document ? document : $0 } + } else { + self.noteDocuments.append(document) + } + + self.publish() } .store(in: &cancellable) - NotificationCenter.default.publisher(for: .channgedTagToNote, object: nil) + NotificationCenter.default.publisher(for: .changedTagToNote, object: nil) .map({ $0.object as? NoteDocument }) .sink { [weak self] document in guard let document = document, diff --git a/PiecesOfPaper/ViewModel/SettingViewModel.swift b/PiecesOfPaper/ViewModel/SettingViewModel.swift index a4e5a0a..9a1556f 100644 --- a/PiecesOfPaper/ViewModel/SettingViewModel.swift +++ b/PiecesOfPaper/ViewModel/SettingViewModel.swift @@ -24,8 +24,15 @@ final class SettingViewModel: ObservableObject { } } + @Published var enabledInfiniteScroll: Bool { + didSet { + userPreference.enabledInfiniteScroll = enabledInfiniteScroll + } + } + init() { enablediCloud = userPreference.enablediCloud enabledAutoSave = userPreference.enabledAutoSave + enabledInfiniteScroll = userPreference.enabledInfiniteScroll } } diff --git a/PiecesOfPaper/ViewModel/TagListToNoteViewModel.swift b/PiecesOfPaper/ViewModel/TagListToNoteViewModel.swift index 0ee2630..64f21a0 100644 --- a/PiecesOfPaper/ViewModel/TagListToNoteViewModel.swift +++ b/PiecesOfPaper/ViewModel/TagListToNoteViewModel.swift @@ -54,7 +54,7 @@ final class TagListToNoteViewModel: ObservableObject { guard let noteDocument = noteDocument else { return } noteDocument.save(to: noteDocument.fileURL, for: .forOverwriting) { [weak self] success in if success { - NotificationCenter.default.post(name: .channgedTagToNote, object: noteDocument) + NotificationCenter.default.post(name: .changedTagToNote, object: noteDocument) self?.objectWillChange.send() } else { print("save failed")