From ca93472cd49092859d915c406634b17e15b65308 Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Sun, 14 Jul 2024 16:03:05 +0700 Subject: [PATCH] feat: add find keyboard shortcut --- Memola.xcodeproj/project.pbxproj | 16 ++++++--- .../{AppDelegate.swift => Application.swift} | 13 ++++++-- Memola/App/MemolaApp.swift | 6 ++-- .../ContextMenuViewModifier.swift | 2 +- .../OnDismissSearchViewModifier.swift | 29 ++++++++++++++++ .../ViewModifiers/OnDragViewModifier.swift | 2 +- .../Dashboard/Dashboard/DashboardView.swift | 5 +-- .../Dashboard/Details/Memos/MemosView.swift | 7 +++- .../Dashboard/Details/Trash/TrashView.swift | 7 +++- Memola/Shortcut/Commands/ViewCommands.swift | 18 +++++++++- Memola/Shortcut/Core/Shortcut.swift | 2 +- .../NavigationSplitViewVisibility++.swift | 33 +++++++++++++++++++ 12 files changed, 123 insertions(+), 17 deletions(-) rename Memola/App/{AppDelegate.swift => Application.swift} (54%) create mode 100644 Memola/Components/ViewModifiers/OnDismissSearchViewModifier.swift create mode 100644 Memola/Utilies/Extensions/NavigationSplitViewVisibility++.swift diff --git a/Memola.xcodeproj/project.pbxproj b/Memola.xcodeproj/project.pbxproj index 039dc18..f2fd160 100644 --- a/Memola.xcodeproj/project.pbxproj +++ b/Memola.xcodeproj/project.pbxproj @@ -25,7 +25,6 @@ EC2002D52C416033002EBD5F /* FileCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002D42C416033002EBD5F /* FileCommands.swift */; }; EC2002D72C4160EF002EBD5F /* EditCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002D62C4160EF002EBD5F /* EditCommands.swift */; }; EC2002D92C4161ED002EBD5F /* ViewCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002D82C4161ED002EBD5F /* ViewCommands.swift */; }; - EC2002DB2C4162E5002EBD5F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002DA2C4162E5002EBD5F /* AppDelegate.swift */; }; EC2002DD2C4163E8002EBD5F /* AppCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002DC2C4163E8002EBD5F /* AppCommands.swift */; }; EC2002E12C416470002EBD5F /* Shortcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002E02C416470002EBD5F /* Shortcut.swift */; }; EC2002E52C416551002EBD5F /* Shortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002E42C416551002EBD5F /* Shortcuts.swift */; }; @@ -45,6 +44,9 @@ EC50500D2BF6674400B4D86E /* OnDragViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC50500C2BF6674400B4D86E /* OnDragViewModifier.swift */; }; EC5D40812C21CE270067F090 /* PhotoBackgroundRenderPass.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC5D40802C21CE270067F090 /* PhotoBackgroundRenderPass.swift */; }; EC5E83902BFDB69C00261D9C /* MovingAverage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC5E838F2BFDB69C00261D9C /* MovingAverage.swift */; }; + EC6E3BD72C43C6A400DD20F3 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6E3BD62C43C6A400DD20F3 /* Application.swift */; }; + EC6E3BD92C43C6C000DD20F3 /* OnDismissSearchViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6E3BD82C43C6C000DD20F3 /* OnDismissSearchViewModifier.swift */; }; + EC6E3BDB2C43C78700DD20F3 /* NavigationSplitViewVisibility++.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6E3BDA2C43C78700DD20F3 /* NavigationSplitViewVisibility++.swift */; }; EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */; }; EC7F6BF02BE5E6E400A34A7B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */; }; EC7F6BF32BE5E6E400A34A7B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BF22BE5E6E400A34A7B /* Preview Assets.xcassets */; }; @@ -160,7 +162,6 @@ EC2002D42C416033002EBD5F /* FileCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCommands.swift; sourceTree = ""; }; EC2002D62C4160EF002EBD5F /* EditCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditCommands.swift; sourceTree = ""; }; EC2002D82C4161ED002EBD5F /* ViewCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewCommands.swift; sourceTree = ""; }; - EC2002DA2C4162E5002EBD5F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; EC2002DC2C4163E8002EBD5F /* AppCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCommands.swift; sourceTree = ""; }; EC2002E02C416470002EBD5F /* Shortcut.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shortcut.swift; sourceTree = ""; }; EC2002E42C416551002EBD5F /* Shortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shortcuts.swift; sourceTree = ""; }; @@ -181,6 +182,9 @@ EC50500E2BF670EA00B4D86E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; EC5D40802C21CE270067F090 /* PhotoBackgroundRenderPass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoBackgroundRenderPass.swift; sourceTree = ""; }; EC5E838F2BFDB69C00261D9C /* MovingAverage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovingAverage.swift; sourceTree = ""; }; + EC6E3BD62C43C6A400DD20F3 /* Application.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; + EC6E3BD82C43C6C000DD20F3 /* OnDismissSearchViewModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnDismissSearchViewModifier.swift; sourceTree = ""; }; + EC6E3BDA2C43C78700DD20F3 /* NavigationSplitViewVisibility++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NavigationSplitViewVisibility++.swift"; sourceTree = ""; }; EC7F6BE82BE5E6E300A34A7B /* Memola.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Memola.app; sourceTree = BUILT_PRODUCTS_DIR; }; EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemolaApp.swift; sourceTree = ""; }; EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -492,6 +496,7 @@ EC50500B2BF6673300B4D86E /* ViewModifiers */ = { isa = PBXGroup; children = ( + EC6E3BD82C43C6C000DD20F3 /* OnDismissSearchViewModifier.swift */, EC50500C2BF6674400B4D86E /* OnDragViewModifier.swift */, EC0D14272BF7BF20009BFE5F /* ContextMenuViewModifier.swift */, ); @@ -585,7 +590,7 @@ isa = PBXGroup; children = ( EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */, - EC2002DA2C4162E5002EBD5F /* AppDelegate.swift */, + EC6E3BD62C43C6A400DD20F3 /* Application.swift */, ); path = App; sourceTree = ""; @@ -950,6 +955,7 @@ ECF7B2CF2C39169C004D2C57 /* Extensions */ = { isa = PBXGroup; children = ( + EC6E3BDA2C43C78700DD20F3 /* NavigationSplitViewVisibility++.swift */, ECF7B2BD2C39169C004D2C57 /* Array++.swift */, ECF7B2BE2C39169C004D2C57 /* CGAffineTransform++.swift */, ECF7B2BF2C39169C004D2C57 /* CGFloat++.swift */, @@ -1120,6 +1126,7 @@ EC2002E52C416551002EBD5F /* Shortcuts.swift in Sources */, EC2002DD2C4163E8002EBD5F /* AppCommands.swift in Sources */, EC1815082C2D980B00541369 /* Sort.swift in Sources */, + EC6E3BD92C43C6C000DD20F3 /* OnDismissSearchViewModifier.swift in Sources */, ECA7387D2BE5EF4B00A4542E /* MemoView.swift in Sources */, ECDDD40D2C366B3B00DF9D5E /* PreviewRenderPass.swift in Sources */, ECF7B2E12C39169C004D2C57 /* View++.swift in Sources */, @@ -1129,6 +1136,7 @@ ECFA15202BEF21EF00455818 /* MemoObject.swift in Sources */, ECE883C12C00C9CB0045C53D /* StrokeStyle.swift in Sources */, EC37FB122C1B2DD90008D976 /* ToolSelection.swift in Sources */, + EC6E3BDB2C43C78700DD20F3 /* NavigationSplitViewVisibility++.swift in Sources */, ECA738C62BE60E9D00A4542E /* EraserPenStyle.swift in Sources */, ECA738A62BE6023F00A4542E /* GridUniforms.swift in Sources */, ECA738D72BE60FC100A4542E /* SolidPointStrokeGenerator.swift in Sources */, @@ -1139,6 +1147,7 @@ ECA738862BE5FF2500A4542E /* Canvas.swift in Sources */, ECF7B2DE2C39169C004D2C57 /* NSManagedObjectContext++.swift in Sources */, ECA738882BE5FF4400A4542E /* Renderer.swift in Sources */, + EC6E3BD72C43C6A400DD20F3 /* Application.swift in Sources */, EC8C9DCE2C39882500A8F3C4 /* NSSyncScrollView.swift in Sources */, ECF7B2D72C39169C004D2C57 /* Color++.swift in Sources */, EC01512C2C306BEF008A115E /* MemoCard.swift in Sources */, @@ -1179,7 +1188,6 @@ ECDAC07B2C318DBC0000ED77 /* ElementToolbar.swift in Sources */, EC50500D2BF6674400B4D86E /* OnDragViewModifier.swift in Sources */, EC9AB09F2C1401A40076AF58 /* EraserObject.swift in Sources */, - EC2002DB2C4162E5002EBD5F /* AppDelegate.swift in Sources */, ECBE529C2C1D94A4006BDB3D /* CameraView.swift in Sources */, ECA7389E2BE601CB00A4542E /* QuadVertex.swift in Sources */, ECA738B32BE60D9E00A4542E /* CanvasView.swift in Sources */, diff --git a/Memola/App/AppDelegate.swift b/Memola/App/Application.swift similarity index 54% rename from Memola/App/AppDelegate.swift rename to Memola/App/Application.swift index 2950d07..fe73936 100644 --- a/Memola/App/AppDelegate.swift +++ b/Memola/App/Application.swift @@ -1,19 +1,26 @@ // -// AppDelegate.swift +// Application.swift // Memola // // Created by Dscyre Scotti on 7/12/24. // +import Combine import SwiftUI -class AppDelegate: NSObject, ObservableObject { } +class Application: NSObject, ObservableObject { + +} #if os(macOS) -extension AppDelegate: NSApplicationDelegate { +extension Application: NSApplicationDelegate { func applicationDidFinishLaunching(_ notification: Notification) { NSWindow.allowsAutomaticWindowTabbing = false UserDefaults.standard.register(defaults: ["NSQuitAlwaysKeepsWindows": false]) } } +#else +extension Application: UIApplicationDelegate { + func applicationDidFinishLaunching(_ application: UIApplication) { } +} #endif diff --git a/Memola/App/MemolaApp.swift b/Memola/App/MemolaApp.swift index 2a0a44e..cf96907 100644 --- a/Memola/App/MemolaApp.swift +++ b/Memola/App/MemolaApp.swift @@ -10,9 +10,11 @@ import SwiftUI @main struct MemolaApp: App { #if os(macOS) - @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + @NSApplicationDelegateAdaptor(Application.self) var application + #else + @UIApplicationDelegateAdaptor(Application.self) var application #endif - + var body: some Scene { WindowGroup { DashboardView() diff --git a/Memola/Components/ViewModifiers/ContextMenuViewModifier.swift b/Memola/Components/ViewModifiers/ContextMenuViewModifier.swift index ee1a3f2..34f458c 100644 --- a/Memola/Components/ViewModifiers/ContextMenuViewModifier.swift +++ b/Memola/Components/ViewModifiers/ContextMenuViewModifier.swift @@ -8,7 +8,7 @@ import SwiftUI import Foundation -struct ContextMenuViewModifier: ViewModifier { +private struct ContextMenuViewModifier: ViewModifier { let condition: Bool let menuItems: () -> MenuContent let preview: () -> Preview diff --git a/Memola/Components/ViewModifiers/OnDismissSearchViewModifier.swift b/Memola/Components/ViewModifiers/OnDismissSearchViewModifier.swift new file mode 100644 index 0000000..10c74f6 --- /dev/null +++ b/Memola/Components/ViewModifiers/OnDismissSearchViewModifier.swift @@ -0,0 +1,29 @@ +// +// OnDismissSearchViewModifier.swift +// Memola +// +// Created by Dscyre Scotti on 7/14/24. +// + +import SwiftUI + +private struct OnDismissSearchViewModifier: ViewModifier { + @Environment(\.dismissSearch) var dismissSearch + + @Binding var isActive: Bool + + func body(content: Content) -> some View { + content + .onChange(of: isActive) { oldValue, newValue in + if !newValue { + dismissSearch() + } + } + } +} + +extension View { + func onDismissSearch(isActive: Binding) -> some View { + modifier(OnDismissSearchViewModifier(isActive: isActive)) + } +} diff --git a/Memola/Components/ViewModifiers/OnDragViewModifier.swift b/Memola/Components/ViewModifiers/OnDragViewModifier.swift index b60d540..c31c141 100644 --- a/Memola/Components/ViewModifiers/OnDragViewModifier.swift +++ b/Memola/Components/ViewModifiers/OnDragViewModifier.swift @@ -8,7 +8,7 @@ import SwiftUI import Foundation -struct OnDragViewModifier: ViewModifier { +private struct OnDragViewModifier: ViewModifier { let condition: Bool let data: () -> NSItemProvider let preview: () -> Preview diff --git a/Memola/Features/Dashboard/Dashboard/DashboardView.swift b/Memola/Features/Dashboard/Dashboard/DashboardView.swift index 519439b..a319f08 100644 --- a/Memola/Features/Dashboard/Dashboard/DashboardView.swift +++ b/Memola/Features/Dashboard/Dashboard/DashboardView.swift @@ -13,12 +13,13 @@ struct DashboardView: View { @StateObject var memoManager: MemoManager = .shared @State var sidebarItem: SidebarItem? = .memos + @AppStorage("memola.app.scene.side-bar.column-visibility") var columnVisibility: NavigationSplitViewVisibility = .all @Namespace var namespace var body: some View { #if os(macOS) - NavigationSplitView { + NavigationSplitView(columnVisibility: $columnVisibility) { Sidebar(sidebarItem: $sidebarItem, horizontalSizeClass: horizontalSizeClass) } detail: { switch sidebarItem { @@ -45,7 +46,7 @@ struct DashboardView: View { } } #else - NavigationSplitView { + NavigationSplitView(columnVisibility: $columnVisibility) { Sidebar(sidebarItem: $sidebarItem, horizontalSizeClass: horizontalSizeClass) } detail: { switch sidebarItem { diff --git a/Memola/Features/Dashboard/Details/Memos/MemosView.swift b/Memola/Features/Dashboard/Details/Memos/MemosView.swift index 96479ff..9e9569a 100644 --- a/Memola/Features/Dashboard/Details/Memos/MemosView.swift +++ b/Memola/Features/Dashboard/Details/Memos/MemosView.swift @@ -15,6 +15,7 @@ struct MemosView: View { @State var query: String = "" @State var currentDate: Date = .now + @State var isActiveSearch: Bool = false @AppStorage("memola.memo-objects.memos.sort") var sort: Sort = .recent @AppStorage("memola.memo-objects.memos.filter") var filter: Filter = .none @@ -43,12 +44,16 @@ struct MemosView: View { MemoGrid(memoObjects: memoObjects, placeholder: placeholder) { memoObject, cellWidth in memoCard(memoObject, cellWidth) } + .onDismissSearch(isActive: $isActiveSearch) .focusedSceneValue(\.activeSceneKey, .memos) .navigationTitle(horizontalSizeClass == .compact ? "Memos" : "") #if os(iOS) .navigationBarTitleDisplayMode(.inline) #endif - .searchable(text: $query, placement: .toolbar, prompt: Text("Search")) + .searchable(text: $query, isPresented: $isActiveSearch, placement: .toolbar, prompt: Text("Search")) + .onSubmit(of: .search) { + isActiveSearch = false + } .toolbar { #if os(macOS) ToolbarItem(placement: .navigation) { diff --git a/Memola/Features/Dashboard/Details/Trash/TrashView.swift b/Memola/Features/Dashboard/Details/Trash/TrashView.swift index 534d8d2..bda573c 100644 --- a/Memola/Features/Dashboard/Details/Trash/TrashView.swift +++ b/Memola/Features/Dashboard/Details/Trash/TrashView.swift @@ -16,6 +16,7 @@ struct TrashView: View { @State var query: String = "" @State var restoredMemo: MemoObject? @State var deletedMemo: MemoObject? + @State var isActiveSearch: Bool = false @Binding var sidebarItem: SidebarItem? @@ -44,12 +45,16 @@ struct TrashView: View { MemoGrid(memoObjects: memoObjects, placeholder: placeholder) { memoObject, cellWidth in memoCard(memoObject, cellWidth) } + .onDismissSearch(isActive: $isActiveSearch) .focusedSceneValue(\.activeSceneKey, .trash) .navigationTitle(horizontalSizeClass == .compact ? "Trash" : "") #if os(iOS) .navigationBarTitleDisplayMode(.inline) #endif - .searchable(text: $query, placement: .toolbar, prompt: Text("Search")) + .searchable(text: $query, isPresented: $isActiveSearch, placement: .toolbar, prompt: Text("Search")) + .onSubmit(of: .search) { + isActiveSearch = false + } .toolbar { #if os(macOS) ToolbarItem(placement: .navigation) { diff --git a/Memola/Shortcut/Commands/ViewCommands.swift b/Memola/Shortcut/Commands/ViewCommands.swift index 487c9f7..aa4240f 100644 --- a/Memola/Shortcut/Commands/ViewCommands.swift +++ b/Memola/Shortcut/Commands/ViewCommands.swift @@ -8,7 +8,23 @@ import SwiftUI struct ViewCommands: Commands { + @FocusedValue(\.activeSceneKey) var appScene + var body: some Commands { - CommandGroup(replacing: .toolbar) { } + CommandGroup(replacing: .toolbar) { + if appScene == .trash || appScene == .memos { + Button { + #if os(macOS) + guard let toolbar = NSApp.keyWindow?.toolbar else { return } + if let search = toolbar.items.first(where: { $0.itemIdentifier.rawValue == "com.apple.SwiftUI.search" }) as? NSSearchToolbarItem { + search.beginSearchInteraction() + } + #endif + } label: { + Text("Find Memo") + } + .keyboardShortcut("f", modifiers: [.command]) + } + } } } diff --git a/Memola/Shortcut/Core/Shortcut.swift b/Memola/Shortcut/Core/Shortcut.swift index 456e6f0..57c4043 100644 --- a/Memola/Shortcut/Core/Shortcut.swift +++ b/Memola/Shortcut/Core/Shortcut.swift @@ -8,7 +8,7 @@ import Combine import Foundation -class Shortcut: ObservableObject { +class Shortcut { static let shared: Shortcut = .init() private let shortcutPublisher = PassthroughSubject() diff --git a/Memola/Utilies/Extensions/NavigationSplitViewVisibility++.swift b/Memola/Utilies/Extensions/NavigationSplitViewVisibility++.swift new file mode 100644 index 0000000..1599039 --- /dev/null +++ b/Memola/Utilies/Extensions/NavigationSplitViewVisibility++.swift @@ -0,0 +1,33 @@ +// +// NavigationSplitViewVisibility++.swift +// Memola +// +// Created by Dscyre Scotti on 7/14/24. +// + +import SwiftUI + +extension NavigationSplitViewVisibility: RawRepresentable { + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .all + case 1: self = .automatic + case 2: self = .detailOnly + case 3: self = .doubleColumn + default: self = .all + } + } + + public var rawValue: Int { + switch self { + case .all: 0 + case .automatic: 1 + case .detailOnly: 2 + case .doubleColumn: 3 + default: -1 + } + } + + public typealias RawValue = Int +} +