From 3681254deb97c25be7f9288d79cc1e41a61034d4 Mon Sep 17 00:00:00 2001 From: glushchenko Date: Thu, 21 Nov 2024 21:18:15 +0200 Subject: [PATCH] #1777 --- FSNotes iOS/AppDelegate.swift | 56 +--- FSNotes iOS/EditorViewController.swift | 21 +- FSNotes iOS/Extensions/UIApplication+.swift | 4 + FSNotes iOS/View/NotesTableView.swift | 7 +- FSNotes iOS/View/SidebarTableView.swift | 11 +- FSNotes iOS/ViewController.swift | 283 ++++++++---------- FSNotes.xcodeproj/project.pbxproj | 16 +- .../xcschemes/FSNotes iOS.xcscheme | 6 +- FSNotes/Business/Note.swift | 82 ++++- FSNotes/Business/Project.swift | 18 +- FSNotes/Business/Storage.swift | 60 +++- FSNotesCore/Shared/Extensions/String+.swift | 16 + 12 files changed, 316 insertions(+), 264 deletions(-) diff --git a/FSNotes iOS/AppDelegate.swift b/FSNotes iOS/AppDelegate.swift index f81a79de9..7c6ce0f30 100644 --- a/FSNotes iOS/AppDelegate.swift +++ b/FSNotes iOS/AppDelegate.swift @@ -21,6 +21,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { public static var gitVC = [String: GitViewController]() public static var gitProgress: GitProgress? + + // MARK: Static Properties + static let applicationShortcutUserInfoIconKey = "applicationShortcutUserInfoIconKey" func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { var shouldPerformAdditionalDelegateHandling = true @@ -108,16 +111,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. } - - func applicationDidBecomeActive(_ application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - - guard let shortcut = launchedShortcutItem else { return } - _ = handleShortCutItem(shortcut) - - // Reset which shortcut was chosen for next time. - launchedShortcutItem = nil - } func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { if let iCloudDocumentsURL = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents").standardized, @@ -134,43 +127,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { - let handledShortCutItem = handleShortCutItem(shortcutItem) - completionHandler(handledShortCutItem) - } - - // MARK: Static Properties - static let applicationShortcutUserInfoIconKey = "applicationShortcutUserInfoIconKey" - - func handleShortCutItem(_ shortcutItem: UIApplicationShortcutItem) -> Bool { - var handled = false - guard ShortcutIdentifier(fullType: shortcutItem.type) != nil else { return false } - guard let shortCutType = shortcutItem.type as String? else { return false } - - let vc = UIApplication.getVC() - - switch shortCutType { - case ShortcutIdentifier.makeNew.type: - vc.createNote() - - handled = true - break - case ShortcutIdentifier.clipboard.type: - vc.createNote(pasteboard: true) - - handled = true - break - case ShortcutIdentifier.search.type: - vc.loadViewIfNeeded() - vc.popViewController() - vc.loadSearchController() - handled = true - break - default: - - break + + if ShortcutIdentifier(fullType: shortcutItem.type) == .search { + UIApplication.getVC().enableSearchFocus() } - - return handled + + UIApplication.getVC().handleShortCutItem(shortcutItem) + completionHandler(true) } func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { @@ -191,6 +154,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { note = storage.getBy(title: id) if !vc.isLoadedDB, note == nil { vc.restoreFindID = id + return true } } } diff --git a/FSNotes iOS/EditorViewController.swift b/FSNotes iOS/EditorViewController.swift index 852248f05..1253f513f 100644 --- a/FSNotes iOS/EditorViewController.swift +++ b/FSNotes iOS/EditorViewController.swift @@ -196,8 +196,6 @@ class EditorViewController: UIViewController, UITextViewDelegate, UIDocumentPick public func fill(note: Note, selectedRange: NSRange? = nil, clearPreview: Bool = false, enableHandoff: Bool = true, completion: (() -> ())? = nil) { - UserDefaultsManagement.lastSelectedURL = note.url - if enableHandoff { registerHandoff(for: note) } @@ -1446,8 +1444,10 @@ class EditorViewController: UIViewController, UITextViewDelegate, UIDocumentPick } @objc public func togglePreview() { - guard let note = editArea.note else { return } - + guard let unwrappedNote = self.note, let note = Storage.shared().getBy(url: unwrappedNote.url) else { return } + + note.loadPreviewState() + if note.previewState { note.previewState = false getPreviewView()?.removeFromSuperview() @@ -1581,12 +1581,17 @@ class EditorViewController: UIViewController, UITextViewDelegate, UIDocumentPick override func restoreUserActivityState(_ activity: NSUserActivity) { if let id = activity.userInfo?["kCSSearchableItemActivityIdentifier"] as? String { let url = URL(fileURLWithPath: id) - if let note = Storage.shared().getBy(url: url) { + + var note = Storage.shared().getBy(url: url) + if nil === note { + note = Storage.shared().addNote(url: url) + } + + if let note = note { load(note: note) - return - } else { - UIApplication.getVC().restoreActivity = url } + + return } guard let name = activity.userInfo?["note-file-name"] as? String, diff --git a/FSNotes iOS/Extensions/UIApplication+.swift b/FSNotes iOS/Extensions/UIApplication+.swift index 5dc051cc1..23e434e05 100644 --- a/FSNotes iOS/Extensions/UIApplication+.swift +++ b/FSNotes iOS/Extensions/UIApplication+.swift @@ -24,4 +24,8 @@ extension UIApplication { let appDelegate = UIApplication.shared.delegate as! AppDelegate return appDelegate.mainController } + + static func getDelegate() -> AppDelegate { + return UIApplication.shared.delegate as! AppDelegate + } } diff --git a/FSNotes iOS/View/NotesTableView.swift b/FSNotes iOS/View/NotesTableView.swift index acd05fc90..6d65173b1 100644 --- a/FSNotes iOS/View/NotesTableView.swift +++ b/FSNotes iOS/View/NotesTableView.swift @@ -81,7 +81,7 @@ class NotesTableView: UITableView, let note = self.notes[indexPath.row] if !note.isLoaded && !note.isLoadedFromCache { - note.load() + note.uiLoad() } cell.configure(note: note) @@ -103,9 +103,10 @@ class NotesTableView: UITableView, guard !self.isEditing, notes.indices.contains(indexPath.row) else { return } - let note = notes[indexPath.row] + var note = notes[indexPath.row] + note.loadPreviewState() + let evc = UIApplication.getEVC() - if let editArea = evc.editArea, let u = editArea.undoManager { u.removeAllActions() } diff --git a/FSNotes iOS/View/SidebarTableView.swift b/FSNotes iOS/View/SidebarTableView.swift index 1f5324830..4f654eb3f 100644 --- a/FSNotes iOS/View/SidebarTableView.swift +++ b/FSNotes iOS/View/SidebarTableView.swift @@ -133,7 +133,9 @@ class SidebarTableView: UITableView, vc.buildSearchQuery() vc.reloadNotesTable() { DispatchQueue.main.async { + vc.notesTable.hideLoader() vc.setNavTitle(folder: name) + vc.isLoadedSidebar = true guard vc.notesTable.notes.count > 0 else { self.unloadAllTags() @@ -147,13 +149,6 @@ class SidebarTableView: UITableView, self.loadAllTags() vc.resizeSidebar(withAnimation: true) } - - if let url = UserDefaultsManagement.lastSelectedURL, - let note = Storage.shared().getBy(url: url) { - UserDefaultsManagement.lastSelectedURL = nil - - UIApplication.getEVC().load(note: note) - } } } } @@ -730,6 +725,8 @@ class SidebarTableView: UITableView, } tableView(self, didSelectRowAt: indexPath) + + viewController?.resizeSidebar(withAnimation: true) } public func reload(indexPath: IndexPath) { diff --git a/FSNotes iOS/ViewController.swift b/FSNotes iOS/ViewController.swift index 49bf688e4..8799bd348 100644 --- a/FSNotes iOS/ViewController.swift +++ b/FSNotes iOS/ViewController.swift @@ -55,10 +55,10 @@ class ViewController: UIViewController, UISearchBarDelegate, UIGestureRecognizer // Swipe animation from handleSidebarSwipe private var sidebarWidth: CGFloat = 0 private var isLandscape: Bool? - - public var restoreActivity: URL? public var restoreFindID: String? + public var isLoadedDB: Bool = false + public var isLoadedSidebar: Bool = false public var folderCapacity: String? public var currentFolder: String? @@ -94,13 +94,6 @@ class ViewController: UIViewController, UISearchBarDelegate, UIGestureRecognizer navigationController?.navigationBar.scrollEdgeAppearance = appearance super.viewWillAppear(animated) - - UserDefaultsManagement.lastSelectedURL = nil - - reloadNotesTable() { [weak self] in - guard let self = self else { return } - self.stopAnimation(indicator: self.indicator) - } } override func viewDidAppear(_ animated: Bool) { @@ -128,9 +121,11 @@ class ViewController: UIViewController, UISearchBarDelegate, UIGestureRecognizer if searchFocus { disableSearchFocus() - DispatchQueue.main.asyncAfter(deadline: .now(), execute: { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: { self.navigationItem.searchController?.searchBar.becomeFirstResponder() }) + } else if !isLoadedSidebar { + notesTable.showLoader() } super.viewDidAppear(animated) @@ -153,30 +148,27 @@ class ViewController: UIViewController, UISearchBarDelegate, UIGestureRecognizer scheduledGitPull() disableLockedProject() - loadNotesTable() - notesTable.showLoader() - loadSidebar() loadNotches() loadPreSafeArea() if !initialLoadingState { - preLoadProjectsData() initialLoadingState = true - } - - loadNews() - restoreLastController() - - NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) - - notesTable.keyboardDismissMode = .onDrag - notesTable.contentInsetAdjustmentBehavior = .never - notesTable.alwaysBounceVertical = true - - if #available(iOS 15.0, *) { - sidebarTableView.sectionHeaderTopPadding = 0 + + loadNews() + + let appDelegate = UIApplication.getDelegate() + if let shortcut = appDelegate.launchedShortcutItem { + handleShortCutItem(shortcut) + appDelegate.launchedShortcutItem = nil + } else { + self.restoreLastController() + } + + DispatchQueue.global(qos: .userInteractive).async { + self.loadDB() + } } super.viewDidLoad() @@ -242,15 +234,21 @@ class ViewController: UIViewController, UISearchBarDelegate, UIGestureRecognizer navigationItem.leftBarButtonItems = [appSettings, generalSettings] setNavTitle(folder: NSLocalizedString("Inbox", comment: "")) + sidebarTableView.backgroundColor = UIColor.sidebar + sidebarTableView.dropDelegate = sidebarTableView + if #available(iOS 15.0, *) { + sidebarTableView.sectionHeaderTopPadding = 0 + } loadPlusButton() notesTable.viewDelegate = self notesTable.dragInteractionEnabled = true notesTable.dragDelegate = notesTable - sidebarTableView.dropDelegate = sidebarTableView - + notesTable.keyboardDismissMode = .onDrag + notesTable.contentInsetAdjustmentBehavior = .never + notesTable.alwaysBounceVertical = true notesTable.dataSource = notesTable notesTable.delegate = notesTable notesTable.layer.zPosition = 100 @@ -319,6 +317,8 @@ class ViewController: UIViewController, UISearchBarDelegate, UIGestureRecognizer NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) } public func configureGestures() { @@ -364,6 +364,28 @@ class ViewController: UIViewController, UISearchBarDelegate, UIGestureRecognizer public func disableSearchFocus() { searchFocus = false } + + public func handleShortCutItem(_ shortcutItem: UIApplicationShortcutItem) { + guard ShortcutIdentifier(fullType: shortcutItem.type) != nil else { return } + guard let shortCutType = shortcutItem.type as String? else { return } + + switch shortCutType { + case ShortcutIdentifier.makeNew.type: + self.createNote() + break + case ShortcutIdentifier.clipboard.type: + self.createNote(pasteboard: true) + break + case ShortcutIdentifier.search.type: + self.loadViewIfNeeded() + self.enableSearchFocus() + self.popViewController() + self.loadSearchController() + break + default: + break + } + } @IBAction public func toggleSidebar() { if UserDefaultsManagement.sidebarIsOpened { @@ -405,12 +427,6 @@ class ViewController: UIViewController, UISearchBarDelegate, UIGestureRecognizer gesture.state = .ended } - public func loadNotesTable() { - reloadNotesTable() { - self.stopAnimation(indicator: self.indicator) - } - } - public func loadSidebar() { sidebarTableView.dataSource = self.sidebarTableView sidebarTableView.delegate = self.sidebarTableView @@ -427,84 +443,64 @@ class ViewController: UIViewController, UISearchBarDelegate, UIGestureRecognizer if UserDefaultsManagement.sidebarIsOpened { resizeSidebar() } - - guard Storage.shared().getRoot() != nil else { return } - -// DispatchQueue.main.async { -// let inboxIndex = IndexPath(row: 0, section: 0) -// self.sidebarTableView.tableView(self.sidebarTableView, didSelectRowAt: inboxIndex) -// } } - - public func preLoadProjectsData() { - guard storage.getRoot() != nil else { return } - storage.loadInboxAndTrash() - self.reloadNotesTable() - - DispatchQueue.global(qos: .userInteractive).async { - let storage = self.storage - - let projectsLoading = Date() - let results = storage.getProjectDiffs() + public func loadDB() { + let storage = self.storage + + let dirsLoading = Date() + storage.loadNonSystemProject() + storage.loadProjectRelations() + + print("1. Loaded non system projects and relations in \(dirsLoading.timeIntervalSinceNow * -1) seconds") + + let notesLoadingPoint = Date() + let projects = storage.getProjects() + + for project in projects { + _ = project.loadNotes() + } + + print("2. Notes loading finished in \(notesLoadingPoint.timeIntervalSinceNow * -1) seconds") - OperationQueue.main.addOperation { - self.sidebarTableView.removeRows(projects: results.0) - self.sidebarTableView.insertRows(projects: results.1) - self.notesTable.doVisualChanges(results: (results.2, results.3, [])) - } + OperationQueue.main.addOperation { - print("0. Projects diff loading finished in \(projectsLoading.timeIntervalSinceNow * -1) seconds") - - let diffLoading = Date() - for project in storage.getProjects() { - let changes = project.checkNotesCacheDiff() - self.notesTable.doVisualChanges(results: changes) - self.notesTable.hideLoader() - } + self.importSavedInSharedExtension() + self.sidebarTableView.reloadSidebar() - print("1. Notes diff loading finished in \(diffLoading.timeIntervalSinceNow * -1) seconds") - - // enable iCloud Drive updates after projects structure formalized - self.cloudDriveManager?.metadataQuery.enableUpdates() - - let tagsPoint = Date() - storage.loadNotesContent() - - DispatchQueue.main.async { - - self.importSavedInSharedExtension() - - self.sidebarTableView.reloadSidebar() - self.resizeSidebar(withAnimation: true) - self.sidebarTableView.loadAllTags() - } - - print("2. Tags loading finished in \(tagsPoint.timeIntervalSinceNow * -1) seconds") - - // fill note from spotlight action - if let restore = self.restoreActivity { - if let note = Storage.shared().getBy(url: restore) { - DispatchQueue.main.async { - UIApplication.getEVC().load(note: note) - } + DispatchQueue.global(qos: .userInitiated).async { + let diffLoading = Date() + for project in storage.getProjects() { + let changes = project.checkNotesCacheDiff() + self.notesTable.doVisualChanges(results: changes) } - } - - if let restore = self.restoreFindID { - self.restoreFindID = nil - if let note = Storage.shared().getBy(title: restore) { - DispatchQueue.main.async { - UIApplication.getEVC().load(note: note) + + print("3. Notes diff loading finished in \(diffLoading.timeIntervalSinceNow * -1) seconds") + + // find:// + if let restore = self.restoreFindID { + self.restoreFindID = nil + if let note = Storage.shared().getBy(title: restore) { + OperationQueue.main.addOperation { + self.notesTable.hideLoader() + UIApplication.getEVC().load(note: note) + } } } + + // Load notes content + let notesFullLoading = Date() + self.storage.loadNotesContent() + print("4. Full notes loading in \(notesFullLoading.timeIntervalSinceNow * -1) seconds") + + let spotlightPoint = Date() + self.reIndexSpotlight() + print("5. Spotlight indexation finished in \(spotlightPoint.timeIntervalSinceNow * -1) seconds") + + // enable iCloud Drive updates after projects structure formalized + self.cloudDriveManager?.metadataQuery.enableUpdates() + self.isLoadedDB = true } - - let spotlightPoint = Date() - self.reIndexSpotlight() - print("4. Spotlight indexation finished in \(spotlightPoint.timeIntervalSinceNow * -1) seconds") - - self.isLoadedDB = true } } @@ -754,7 +750,6 @@ class ViewController: UIViewController, UISearchBarDelegate, UIGestureRecognizer self.loadPlusButton() } } - } private func toggleSearchView() { @@ -796,20 +791,6 @@ class ViewController: UIViewController, UISearchBarDelegate, UIGestureRecognizer indicator.bringSubviewToFront(view) } - public func startAnimation(indicator: UIActivityIndicatorView?) { - DispatchQueue.main.async { - indicator?.startAnimating() - indicator?.layer.zPosition = 101 - } - } - - public func stopAnimation(indicator: UIActivityIndicatorView?) { - DispatchQueue.main.async { - indicator?.stopAnimating() - indicator?.layer.zPosition = -1 - } - } - public func reloadNotesTable(completion: (() -> ())? = nil) { isActiveTableUpdating = true searchQueue.cancelAllOperations() @@ -870,7 +851,6 @@ class ViewController: UIViewController, UISearchBarDelegate, UIGestureRecognizer } self.isActiveTableUpdating = false - self.stopAnimation(indicator: self.indicator) completion?() } @@ -1443,46 +1423,37 @@ class ViewController: UIViewController, UISearchBarDelegate, UIGestureRecognizer } public func restoreLastController() { - guard !Storage.shared().isCrashedLastTime else { return } - - DispatchQueue.main.async { - if let noteURL = UserDefaultsManagement.currentNote { - if FileManager.default.fileExists(atPath: noteURL.path), - let project = Storage.shared().getProjectByNote(url: noteURL) - { - var note = Storage.shared().getBy(url: noteURL) - - if note == nil { - note = Note(url: noteURL, with: project) -// if let unwrapped = note { -// Storage.shared().add(unwrapped) -// } - } - - guard let note = note, !note.isEncrypted() else { return } - - UIApplication.getVC().openEditorViewController() - - let evc = UIApplication.getEVC() - evc.fill(note: note) - - if UserDefaultsManagement.currentEditorState == true, - let selectedRange = UserDefaultsManagement.currentRange, - !note.previewState - { - if selectedRange.upperBound <= note.content.length { - evc.editArea.selectedRange = selectedRange - } - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - evc.editArea.becomeFirstResponder() - } - } + guard !self.storage.isCrashedLastTime, + let noteURL = UserDefaultsManagement.currentNote, + FileManager.default.fileExists(atPath: noteURL.path) + else { return } + + let note = Storage.shared().addNote(url: noteURL) + + guard !note.isEncrypted() else { return } + note.loadPreviewState() - UserDefaultsManagement.currentNote = nil + UIApplication.getVC().openEditorViewController() + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + UIApplication.getEVC().fill(note: note) + UIApplication.getEVC().configureNavMenu() + + if UserDefaultsManagement.currentEditorState == true, + let selectedRange = UserDefaultsManagement.currentRange, + !note.previewState, + selectedRange.upperBound <= note.content.length + { + UIApplication.getEVC().editArea.becomeFirstResponder() + UIApplication.getEVC().editArea.selectedRange = selectedRange + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + UIApplication.getEVC().editArea.scrollRangeToVisible(selectedRange) } } } + + UserDefaultsManagement.currentNote = nil } public func reloadDatabase() { diff --git a/FSNotes.xcodeproj/project.pbxproj b/FSNotes.xcodeproj/project.pbxproj index cb622299d..f8451ace8 100644 --- a/FSNotes.xcodeproj/project.pbxproj +++ b/FSNotes.xcodeproj/project.pbxproj @@ -4154,7 +4154,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 308; + CURRENT_PROJECT_VERSION = 311; DEVELOPMENT_TEAM = 866P6MTE92; INFOPLIST_FILE = "FSNotes iOS Share/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -4163,7 +4163,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 6.9.4; + MARKETING_VERSION = 6.9.5; PRODUCT_BUNDLE_IDENTIFIER = "co.fluder.mobile.FSNotes-iOS.FSNotes-iOS-Share"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -4184,7 +4184,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 308; + CURRENT_PROJECT_VERSION = 311; DEVELOPMENT_TEAM = 866P6MTE92; ENABLE_NS_ASSERTIONS = NO; INFOPLIST_FILE = "FSNotes iOS Share/Info.plist"; @@ -4194,7 +4194,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 6.9.4; + MARKETING_VERSION = 6.9.5; PRODUCT_BUNDLE_IDENTIFIER = "co.fluder.mobile.FSNotes-iOS.FSNotes-iOS-Share"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -4219,7 +4219,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 308; + CURRENT_PROJECT_VERSION = 311; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 866P6MTE92; INFOPLIST_FILE = "FSNotes iOS/Info.plist"; @@ -4229,7 +4229,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 6.9.4; + MARKETING_VERSION = 6.9.5; PRODUCT_BUNDLE_IDENTIFIER = "co.fluder.mobile.FSNotes-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; @@ -4256,7 +4256,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 308; + CURRENT_PROJECT_VERSION = 311; DEVELOPMENT_TEAM = 866P6MTE92; ENABLE_NS_ASSERTIONS = NO; INFOPLIST_FILE = "FSNotes iOS/Info.plist"; @@ -4266,7 +4266,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 6.9.4; + MARKETING_VERSION = 6.9.5; PRODUCT_BUNDLE_IDENTIFIER = "co.fluder.mobile.FSNotes-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; diff --git a/FSNotes.xcodeproj/xcshareddata/xcschemes/FSNotes iOS.xcscheme b/FSNotes.xcodeproj/xcshareddata/xcschemes/FSNotes iOS.xcscheme index d380dcece..9bddec42d 100644 --- a/FSNotes.xcodeproj/xcshareddata/xcschemes/FSNotes iOS.xcscheme +++ b/FSNotes.xcodeproj/xcshareddata/xcschemes/FSNotes iOS.xcscheme @@ -1,7 +1,7 @@ + version = "1.7"> @@ -63,6 +63,10 @@ isEnabled = "YES"> + + Int64? { + do { + let attributes = try FileManager.default.attributesOfItem(atPath: path) + if let fileSize = attributes[.size] as? Int64 { + return fileSize + } + } catch { + print("Error retrieving file size: \(error.localizedDescription)") + } + return nil + } + public func isValidForCaching() -> Bool { return isLoaded || title.count > 0 || isEncrypted() || imageUrl != nil } @@ -224,7 +236,64 @@ public class Note: NSObject { return false } } + + private func readTitleAndPreview() -> (String?, String?) { + guard let fileHandle = FileHandle(forReadingAtPath: url.path) else { + print("Can not open the file.") + return (nil, nil) + } + defer { fileHandle.closeFile() } + + var saveChars = false + var title = String() + var preview = String() + + while let char = String(data: fileHandle.readData(ofLength: 1), encoding: .utf8) { + if char == "\n" { + if saveChars { + preview += " " + } else { + saveChars = true + } + continue + } + + if saveChars { + preview += char + if preview.count >= 100 { + break + } + } else { + title += char + } + } + + preview = preview.trimmingCharacters(in: .whitespacesAndNewlines) + title = title.trimmingCharacters(in: .whitespacesAndNewlines) + + return (title, preview) + } + + public func uiLoad() { + if let size = fileSize(atPath: self.url.path), size > 100000 { + loadFileName() + + let data = readTitleAndPreview() + if let title = data.0 { + self.title = title.trimMDSyntax() + } + + if let preview = data.1 { + self.preview = preview.trimMDSyntax() + } + + return + } + + load(tags: true) + } + func load(tags: Bool = true) { if let attributedString = getContent() { cacheHash = nil @@ -1481,14 +1550,7 @@ public class Note: NSObject { cleanText = cleanText.replacingOccurrences(of: image, with: "") } - cleanText = - cleanText - .replacingOccurrences(of: "```", with: "") - .replacingOccurrences(of: "- [ ]", with: "") - .replacingOccurrences(of: "- [x]", with: "") - .replacingOccurrences(of: "[[", with: "") - .replacingOccurrences(of: "]]", with: "") - .replacingOccurrences(of: "{{TOC}}", with: "") + cleanText = cleanText.trimMDSyntax() if cleanText.startsWith(string: "---") { FSParser.yamlBlockRegex.matches(cleanText, range: NSRange(location: 0, length: cleanText.count)) { (result) -> Void in @@ -2282,4 +2344,8 @@ public class Note: NSObject { return false } + + public func loadPreviewState() { + previewState = project.settings.notesPreview.contains(name) + } } diff --git a/FSNotes/Business/Project.swift b/FSNotes/Business/Project.swift index 266fb82b8..86b77740a 100644 --- a/FSNotes/Business/Project.swift +++ b/FSNotes/Business/Project.swift @@ -163,7 +163,7 @@ public class Project: Equatable { } else { prefix = "e\(url.path)" } - + return prefix.md5 } @@ -291,16 +291,13 @@ public class Project: Equatable { notes.append(note) } - // print("From cache: \(notes.count)") + print("From cache: \(notes.count)") isNeededCacheValidation = true } else if !cacheOnly { notes = fetchNotes() - for newNote in notes { - newNote.load() - } - - // print("From disk: \(notes.count)") + + print("From disk: \(notes.count)") } notes = loadPins(for: notes) @@ -750,7 +747,7 @@ public class Project: Equatable { } public func getNotes() -> [Note] { - return storage.noteList.filter({ $0.project == self }) + return storage.noteList.filter({ $0.project.url.path == self.url.path }) } public func countNotes(contains image: URL) -> Int { @@ -879,12 +876,7 @@ public class Project: Equatable { return ([], [], []) } - let results = checkFSAndMemoryDiff() - - if results.1.count > 0 { - print(results) - } print("Cache diff found: removed - \(results.0.count), added - \(results.1.count), modified - \(results.2.count).") diff --git a/FSNotes/Business/Storage.swift b/FSNotes/Business/Storage.swift index 9dbb70dba..4c0907d06 100644 --- a/FSNotes/Business/Storage.swift +++ b/FSNotes/Business/Storage.swift @@ -147,6 +147,7 @@ class Storage { public func insertProject(project: Project) { if projectExist(url: project.url) { + print("Project exist: \(project.label)") return } @@ -554,6 +555,8 @@ class Storage { func add(_ note: Note) { if !noteList.contains(where: { $0.name == note.name && $0.project == note.project }) { noteList.append(note) + } else { + print("Note exist: \(note.name)") } } @@ -1094,22 +1097,34 @@ class Storage { return src } - public func fetchNonSystemProjectURLs() -> [URL] { - guard let main = getDefault() else { return [URL]() } + public func loadNonSystemProject() { + guard let main = getDefault() else { return } - var projectURLs = [URL]() - - if let main = getDefault() { - projectURLs = getAllSubUrls(for: main.url) + let projectURLs = getAllSubUrls(for: main.url) + for projectURL in projectURLs { + let project = Project(storage: self, url: projectURL) + insertProject(project: project) } - - let bookmarksManager = SandboxBookmark.sharedInstance() - let urls = bookmarksManager.getRestoredUrls() - - for url in urls { + + let bookmarkURLs = fetchBookmarkUrls() + for url in bookmarkURLs { + if !projectURLs.contains(url) { + let project = Project(storage: self, url: url) + insertProject(project: project) + } + } + } + + public func fetchBookmarkUrls() -> [URL] { + guard let main = getDefault()?.url else { return [URL]() } + + var projectURLs = [URL]() + let bookmarkUrls = SandboxBookmark.sharedInstance().getRestoredUrls() + + for url in bookmarkUrls { if !projectURLs.contains(url) - && url != main.url - && url != trashURL { + && url != main + && url != self.trashURL { projectURLs.append(url) @@ -1523,7 +1538,24 @@ class Storage { } UserDefaultsManagement.apiBookmarksData = nil - } + } + + public func addNote(url: URL) -> Note { + let projectURL = url.deletingLastPathComponent() + var project: Project? + + if let unwrappedProject = getProjectBy(url: projectURL) { + project = unwrappedProject + } else { + project = Project(storage: self, url: projectURL) + insertProject(project: project!) + } + + let note = Note(url: url, with: project!) + add(note) + + return note + } } extension String: Error {} diff --git a/FSNotesCore/Shared/Extensions/String+.swift b/FSNotesCore/Shared/Extensions/String+.swift index 4a1998b5d..a8efc0e0a 100644 --- a/FSNotesCore/Shared/Extensions/String+.swift +++ b/FSNotesCore/Shared/Extensions/String+.swift @@ -36,6 +36,22 @@ public extension String { func trim() -> String { return self.trimmingCharacters(in: NSCharacterSet.whitespaces) } + + func trimMDSyntax() -> String { + return self + .replacingOccurrences(of: "###### ", with: " ") + .replacingOccurrences(of: "##### ", with: " ") + .replacingOccurrences(of: "#### ", with: " ") + .replacingOccurrences(of: "### ", with: " ") + .replacingOccurrences(of: "## ", with: " ") + .replacingOccurrences(of: "# ", with: " ") + .replacingOccurrences(of: "```", with: "") + .replacingOccurrences(of: "- [ ]", with: "") + .replacingOccurrences(of: "- [x]", with: "") + .replacingOccurrences(of: "[[", with: "") + .replacingOccurrences(of: "]]", with: "") + .replacingOccurrences(of: "{{TOC}}", with: "") + } func getPrefixMatchSequentially(char: String) -> String? { var result = String()