From 167fa6baf6e0cce9c6dbecdd2a0d10d8e76a64a3 Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Wed, 17 Jul 2024 21:29:13 +0700 Subject: [PATCH] feat: add setting view --- Memola.xcodeproj/project.pbxproj | 32 ++++++ Memola/App/Application.swift | 11 ++ Memola/App/MemolaApp.swift | 33 +++++- Memola/Config/Info.plist | 63 ++++++----- .../Dashboard/Dashboard/DashboardView.swift | 2 +- .../Features/Dashboard/Sidebar/Sidebar.swift | 103 +++++++++++++++--- .../Settings/Settings/SettingsView.swift | 19 ++++ Memola/Shortcut/Commands/AppCommands.swift | 10 +- Memola/Shortcut/Commands/FileCommands.swift | 16 +++ Memola/Utilies/AppScene/AppScene.swift | 1 + Memola/Utilies/AppWindow/AppWindow.swift | 19 ++++ 11 files changed, 259 insertions(+), 50 deletions(-) create mode 100644 Memola/Features/Settings/Settings/SettingsView.swift create mode 100644 Memola/Utilies/AppWindow/AppWindow.swift diff --git a/Memola.xcodeproj/project.pbxproj b/Memola.xcodeproj/project.pbxproj index 9b61856..07a3bc7 100644 --- a/Memola.xcodeproj/project.pbxproj +++ b/Memola.xcodeproj/project.pbxproj @@ -32,6 +32,8 @@ EC2002ED2C417B68002EBD5F /* AppScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002EC2C417B68002EBD5F /* AppScene.swift */; }; EC2002F02C417BF1002EBD5F /* ActiveSceneKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002EF2C417BF1002EBD5F /* ActiveSceneKey.swift */; }; EC2106AD2C10C2A700FBE27C /* AnyStroke.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2106AC2C10C2A700FBE27C /* AnyStroke.swift */; }; + EC2117632C47FA30005B32A1 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2117622C47FA30005B32A1 /* SettingsView.swift */; }; + EC2117662C4802C0005B32A1 /* AppWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2117652C4802C0005B32A1 /* AppWindow.swift */; }; EC2BEBF42C0F5FF7005DB0AF /* RTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2BEBF32C0F5FF7005DB0AF /* RTree.swift */; }; EC2BEBF62C0F600D005DB0AF /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2BEBF52C0F600D005DB0AF /* Box.swift */; }; EC2BEBF82C0F601A005DB0AF /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2BEBF72C0F601A005DB0AF /* Node.swift */; }; @@ -169,6 +171,8 @@ EC2002EC2C417B68002EBD5F /* AppScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScene.swift; sourceTree = ""; }; EC2002EF2C417BF1002EBD5F /* ActiveSceneKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSceneKey.swift; sourceTree = ""; }; EC2106AC2C10C2A700FBE27C /* AnyStroke.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyStroke.swift; sourceTree = ""; }; + EC2117622C47FA30005B32A1 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + EC2117652C4802C0005B32A1 /* AppWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppWindow.swift; sourceTree = ""; }; EC2BEBF32C0F5FF7005DB0AF /* RTree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTree.swift; sourceTree = ""; }; EC2BEBF52C0F600D005DB0AF /* Box.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Box.swift; sourceTree = ""; }; EC2BEBF72C0F601A005DB0AF /* Node.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = ""; }; @@ -439,6 +443,30 @@ path = AppScene; sourceTree = ""; }; + EC2117602C47FA14005B32A1 /* Settings */ = { + isa = PBXGroup; + children = ( + EC2117612C47FA29005B32A1 /* Settings */, + ); + path = Settings; + sourceTree = ""; + }; + EC2117612C47FA29005B32A1 /* Settings */ = { + isa = PBXGroup; + children = ( + EC2117622C47FA30005B32A1 /* SettingsView.swift */, + ); + path = Settings; + sourceTree = ""; + }; + EC2117642C4802B4005B32A1 /* AppWindow */ = { + isa = PBXGroup; + children = ( + EC2117652C4802C0005B32A1 /* AppWindow.swift */, + ); + path = AppWindow; + sourceTree = ""; + }; EC2BEBF22C0F5FE1005DB0AF /* RTree */ = { isa = PBXGroup; children = ( @@ -607,6 +635,7 @@ children = ( EC01511A2C305ABB008A115E /* Dashboard */, ECA7387B2BE5EF3500A4542E /* Memo */, + EC2117602C47FA14005B32A1 /* Settings */, ); path = Features; sourceTree = ""; @@ -996,6 +1025,7 @@ ECF7B2E52C391DFA004D2C57 /* Utilies */ = { isa = PBXGroup; children = ( + EC2117642C4802B4005B32A1 /* AppWindow */, EC6E3BDD2C43D5A500DD20F3 /* SidebarVisibility */, EC2002EE2C417BBF002EBD5F /* AppScene */, ECF7B2CF2C39169C004D2C57 /* Extensions */, @@ -1134,6 +1164,7 @@ EC2002DD2C4163E8002EBD5F /* AppCommands.swift in Sources */, EC1815082C2D980B00541369 /* Sort.swift in Sources */, EC6E3BD92C43C6C000DD20F3 /* OnDismissSearchViewModifier.swift in Sources */, + EC2117662C4802C0005B32A1 /* AppWindow.swift in Sources */, ECA7387D2BE5EF4B00A4542E /* MemoView.swift in Sources */, ECDDD40D2C366B3B00DF9D5E /* PreviewRenderPass.swift in Sources */, ECF7B2E12C39169C004D2C57 /* View++.swift in Sources */, @@ -1159,6 +1190,7 @@ EC8C9DCE2C39882500A8F3C4 /* NSSyncScrollView.swift in Sources */, ECF7B2D72C39169C004D2C57 /* Color++.swift in Sources */, EC01512C2C306BEF008A115E /* MemoCard.swift in Sources */, + EC2117632C47FA30005B32A1 /* SettingsView.swift in Sources */, ECA738D42BE60F9100A4542E /* StrokeGenerator.swift in Sources */, ECF7B2D52C39169C004D2C57 /* CGSize++.swift in Sources */, ECA739082BE623F300A4542E /* PenDock.swift in Sources */, diff --git a/Memola/App/Application.swift b/Memola/App/Application.swift index 8f473e9..5a04751 100644 --- a/Memola/App/Application.swift +++ b/Memola/App/Application.swift @@ -54,6 +54,17 @@ extension Application: NSApplicationDelegate { NSWindow.allowsAutomaticWindowTabbing = false UserDefaults.standard.register(defaults: ["NSQuitAlwaysKeepsWindows": false]) } + + func openWindow(for appWindow: AppWindow) { + let window = NSApplication.shared.windows.first { window in + window.identifier?.rawValue.contains(appWindow.id) == true + } + if window == nil, let url = appWindow.url { + NSWorkspace.shared.open(url) + } else { + window?.makeKeyAndOrderFront(nil) + } + } } #else extension Application: UIApplicationDelegate { diff --git a/Memola/App/MemolaApp.swift b/Memola/App/MemolaApp.swift index 5889ecd..7319024 100644 --- a/Memola/App/MemolaApp.swift +++ b/Memola/App/MemolaApp.swift @@ -16,7 +16,7 @@ struct MemolaApp: App { #endif var body: some Scene { - WindowGroup { + WindowGroup(id: AppWindow.dashboard.id) { DashboardView() .persistence(\.viewContext) .onReceive(NotificationCenter.default.publisher(for: Platform.Application.willTerminateNotification)) { _ in @@ -36,13 +36,38 @@ struct MemolaApp: App { .defaultPosition(.center) .windowResizability(.contentSize) .defaultSize(width: 1200, height: 800) - .windowToolbarStyle(.unifiedCompact) + .windowToolbarStyle(.unified) + .handlesExternalEvents(matching: [AppWindow.dashboard.id]) #endif .commands { - AppCommands() - FileCommands() + #if os(macOS) + AppCommands(application: application) + #endif + FileCommands(application: application) EditCommands() ViewCommands(application: application) } + #if os(macOS) + WindowGroup(id: AppWindow.settings.id) { + SettingsView() + .onReceive(NotificationCenter.default.publisher(for: Platform.Application.willTerminateNotification)) { _ in + withPersistenceSync(\.viewContext) { context in + try context.saveIfNeeded() + } + withPersistenceSync(\.backgroundContext) { context in + try context.saveIfNeeded() + } + } + #if os(macOS) + .frame(minWidth: 700, minHeight: 500) + #endif + .environmentObject(application) + } + .defaultPosition(.center) + .windowResizability(.contentSize) + .defaultSize(width: 800, height: 400) + .windowToolbarStyle(.unifiedCompact) + .handlesExternalEvents(matching: [AppWindow.settings.id]) + #endif } } diff --git a/Memola/Config/Info.plist b/Memola/Config/Info.plist index 5c10a78..e0a7480 100644 --- a/Memola/Config/Info.plist +++ b/Memola/Config/Info.plist @@ -2,30 +2,37 @@ - NSCameraUsageDescription - Memola requires access to the camera to capture photos. - CFBundleShortVersionString - $(MARKETING_VERSION) - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) - UILaunchScreen - - UILaunchScreen - - - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleExecutable $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + memola + + + + CFBundleVersion + $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS + NSCameraUsageDescription + Memola requires access to the camera to capture photos. UIApplicationSceneManifest UIApplicationSupportsMultipleScenes @@ -35,19 +42,23 @@ UIApplicationSupportsIndirectInputEvents - CFBundleVersion - $(CURRENT_PROJECT_VERSION) + UILaunchScreen + + UILaunchScreen + + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UISupportedInterfaceOrientations~iphone UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - CFBundleInfoDictionaryVersion - 6.0 - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleName - $(PRODUCT_NAME) diff --git a/Memola/Features/Dashboard/Dashboard/DashboardView.swift b/Memola/Features/Dashboard/Dashboard/DashboardView.swift index 69d4645..4e1cfa1 100644 --- a/Memola/Features/Dashboard/Dashboard/DashboardView.swift +++ b/Memola/Features/Dashboard/Dashboard/DashboardView.swift @@ -52,7 +52,7 @@ struct DashboardView: View { } .animation(.easeIn, value: application.memoObject) .toolbar(application.memoObject == nil ? .visible : .hidden, for: .windowToolbar) - .toolbarBackground(Color(nsColor: .windowBackgroundColor), for: .windowToolbar) + .toolbarBackground(Color(nsColor: .unemphasizedSelectedContentBackgroundColor), for: .windowToolbar) .onChange(of: columnVisibility) { oldValue, newValue in application.changeSidebarVisibility(newValue == .all ? .shown : .hidden) } diff --git a/Memola/Features/Dashboard/Sidebar/Sidebar.swift b/Memola/Features/Dashboard/Sidebar/Sidebar.swift index 3d1794f..0041e3e 100644 --- a/Memola/Features/Dashboard/Sidebar/Sidebar.swift +++ b/Memola/Features/Dashboard/Sidebar/Sidebar.swift @@ -9,35 +9,91 @@ import SwiftUI struct Sidebar: View { private let sidebarItems: [SidebarItem] = [.memos, .trash] - @Binding private var sidebarItem: SidebarItem? - private let horizontalSizeClass: UserInterfaceSizeClass? + @Binding private var sidebarItem: SidebarItem? + + @State private var presentsSettings: Bool = false + + #if os(macOS) + @EnvironmentObject private var application: Application + #endif + init(sidebarItem: Binding, horizontalSizeClass: UserInterfaceSizeClass?) { self._sidebarItem = sidebarItem self.horizontalSizeClass = horizontalSizeClass } var body: some View { + #if os(macOS) + regularList + #else + Group { + if horizontalSizeClass == .compact { + compactList + } else { + regularList + } + } + .sheet(isPresented: $presentsSettings) { + SettingsView() + } + #endif + } + + private var regularList: some View { + VStack(spacing: 10) { + list + Divider() + settingsButton + .buttonStyle(.unselected) + .padding(.horizontal, 10) + } + #if os(macOS) + .padding(.bottom, 10) + .background(Color(color: .windowBackgroundColor)) + #else + .background(Color(color: .secondarySystemBackground)) + #endif + .navigationSplitViewColumnWidth(min: 250, ideal: 250, max: 250) + } + + private var compactList: some View { + list + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + settingsButton + } + } + } + + private var list: some View { List(selection: $sidebarItem) { ForEach(sidebarItems) { item in - if horizontalSizeClass == .compact { - Button { - sidebarItem = item - } label: { - Label(item.title, systemImage: item.icon) - .foregroundColor(.primary) + Group { + if horizontalSizeClass == .compact { + Button { + sidebarItem = item + } label: { + Label(item.title, systemImage: item.icon) + .foregroundColor(.primary) + } + } else { + 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)) } - } else { - 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)) } + #if os(macOS) + .padding(.top, item == .memos ? 20 : 0) + #else + .padding(.top, horizontalSizeClass == .regular ? (item == .memos ? 20 : 0) : 0) + #endif } } .listStyle(.sidebar) @@ -48,11 +104,22 @@ struct Sidebar: View { #else .background(Color(color: .secondarySystemBackground)) #endif - .navigationSplitViewColumnWidth(min: 250, ideal: 250, max: 250) #if os(iOS) .navigationBarTitleDisplayMode(horizontalSizeClass == .compact ? .automatic : .inline) #endif } + + private var settingsButton: some View { + Button { + #if os(macOS) + application.openWindow(for: .settings) + #else + presentsSettings.toggle() + #endif + } label: { + Label("Settings", systemImage: "gearshape.fill") + } + } } extension Sidebar { diff --git a/Memola/Features/Settings/Settings/SettingsView.swift b/Memola/Features/Settings/Settings/SettingsView.swift new file mode 100644 index 0000000..bbbbd8a --- /dev/null +++ b/Memola/Features/Settings/Settings/SettingsView.swift @@ -0,0 +1,19 @@ +// +// SettingsView.swift +// Memola +// +// Created by Dscyre Scotti on 7/17/24. +// + +import SwiftUI + +struct SettingsView: View { + var body: some View { + NavigationStack { + Text("Settings View") + .navigationTitle("Settings") + } + .focusedSceneValue(\.activeSceneKey, .settings) + } +} + diff --git a/Memola/Shortcut/Commands/AppCommands.swift b/Memola/Shortcut/Commands/AppCommands.swift index 16c39fd..99bec35 100644 --- a/Memola/Shortcut/Commands/AppCommands.swift +++ b/Memola/Shortcut/Commands/AppCommands.swift @@ -5,13 +5,20 @@ // Created by Dscyre Scotti on 7/12/24. // +#if os(macOS) import SwiftUI struct AppCommands: Commands { + @ObservedObject private var application: Application + + init(application: Application) { + self.application = application + } + var body: some Commands { CommandGroup(replacing: .appSettings) { Button { - + application.openWindow(for: .settings) } label: { Text("Services...") } @@ -19,3 +26,4 @@ struct AppCommands: Commands { } } } +#endif diff --git a/Memola/Shortcut/Commands/FileCommands.swift b/Memola/Shortcut/Commands/FileCommands.swift index 3a602ba..a848a70 100644 --- a/Memola/Shortcut/Commands/FileCommands.swift +++ b/Memola/Shortcut/Commands/FileCommands.swift @@ -11,8 +11,24 @@ struct FileCommands: Commands { @Environment(\.shortcut) private var shortcut @FocusedValue(\.activeSceneKey) private var appScene + @ObservedObject private var application: Application + + init(application: Application) { + self.application = application + } + var body: some Commands { CommandGroup(replacing: .newItem) { + #if os(macOS) + if appScene == nil { + Button { + application.openWindow(for: .dashboard) + } label: { + Text("Open Dashboard") + } + .keyboardShortcut("m", modifiers: [.command]) + } + #endif if appScene == .memos { Button { shortcut.trigger(.newMemo) diff --git a/Memola/Utilies/AppScene/AppScene.swift b/Memola/Utilies/AppScene/AppScene.swift index 0c71200..7ea16fb 100644 --- a/Memola/Utilies/AppScene/AppScene.swift +++ b/Memola/Utilies/AppScene/AppScene.swift @@ -11,4 +11,5 @@ enum AppScene { case memos case trash case memo + case settings } diff --git a/Memola/Utilies/AppWindow/AppWindow.swift b/Memola/Utilies/AppWindow/AppWindow.swift new file mode 100644 index 0000000..6aeecc4 --- /dev/null +++ b/Memola/Utilies/AppWindow/AppWindow.swift @@ -0,0 +1,19 @@ +// +// AppWindow.swift +// Memola +// +// Created by Dscyre Scotti on 7/17/24. +// + +import Foundation + +enum AppWindow: String, Identifiable { + var id: String { rawValue } + + case dashboard + case settings + + var url: URL? { + URL(string: "memola:\(id)") + } +}