diff --git a/Sources/Secretive.xcodeproj/project.pbxproj b/Sources/Secretive.xcodeproj/project.pbxproj index ab820fa..88ed6a8 100644 --- a/Sources/Secretive.xcodeproj/project.pbxproj +++ b/Sources/Secretive.xcodeproj/project.pbxproj @@ -73,6 +73,7 @@ 50BDCB762E6450950072D2E7 /* ConfigurationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */; }; 50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; }; 50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */; }; + 50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -239,6 +240,7 @@ 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationItemView.swift; sourceTree = ""; }; 50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = ""; }; 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonStyle.swift; sourceTree = ""; }; + 50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowBackgroundStyle.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -293,15 +295,16 @@ path = Helpers; sourceTree = ""; }; - 504788ED2E681EB200B4556F /* Styles */ = { + 504788ED2E681EB200B4556F /* Modifiers */ = { isa = PBXGroup; children = ( + 50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */, 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */, 50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */, 504789222E697DD300B4556F /* BoxBackgroundStyle.swift */, 5065E312295517C500E16645 /* ToolbarButtonStyle.swift */, ); - path = Styles; + path = Modifiers; sourceTree = ""; }; 504788EE2E681EC300B4556F /* Secrets */ = { @@ -432,7 +435,7 @@ children = ( 504788EF2E681ED700B4556F /* Configuration */, 504788EE2E681EC300B4556F /* Secrets */, - 504788ED2E681EB200B4556F /* Styles */, + 504788ED2E681EB200B4556F /* Modifiers */, 504788F02E681F0100B4556F /* Views */, ); path = Views; @@ -686,6 +689,7 @@ 504788F22E681F3A00B4556F /* Instructions.swift in Sources */, 50BDCB742E6436CA0072D2E7 /* ErrorStyle.swift in Sources */, 2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */, + 50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.swift in Sources */, 5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */, 504788EC2E680DC800B4556F /* URLs.swift in Sources */, 504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */, diff --git a/Sources/Secretive/App.swift b/Sources/Secretive/App.swift index 64cc298..d88ce53 100644 --- a/Sources/Secretive/App.swift +++ b/Sources/Secretive/App.swift @@ -4,54 +4,17 @@ import SecureEnclaveSecretKit import SmartCardSecretKit import Brief -extension EnvironmentValues { - - // This is injected through .environment modifier below instead of @Entry for performance reasons (basially, restrictions around init/mainactor causing delay in loading secrets/"empty screen" blip). - @MainActor fileprivate static let _secretStoreList: SecretStoreList = { - let list = SecretStoreList() - let cryptoKit = SecureEnclave.Store() - let migrator = SecureEnclave.CryptoKitMigrator() - try? migrator.migrate(to: cryptoKit) - list.add(store: cryptoKit) - list.add(store: SmartCard.Store()) - return list - }() - - private static let _agentStatusChecker = AgentStatusChecker() - @Entry var agentStatusChecker: any AgentStatusCheckerProtocol = _agentStatusChecker - private static let _updater: any UpdaterProtocol = { - @AppStorage("defaultsHasRunSetup") var hasRunSetup = false - return Updater(checkOnLaunch: hasRunSetup) - }() - @Entry var updater: any UpdaterProtocol = _updater - - private static let _justUpdatedChecker = JustUpdatedChecker() - @Entry var justUpdatedChecker: any JustUpdatedCheckerProtocol = _justUpdatedChecker - - @MainActor var secretStoreList: SecretStoreList { - EnvironmentValues._secretStoreList - } -} - @main struct Secretive: App { @Environment(\.agentStatusChecker) var agentStatusChecker @Environment(\.justUpdatedChecker) var justUpdatedChecker @AppStorage("defaultsHasRunSetup") var hasRunSetup = false - @State private var showingSetup = false - @State private var showingIntegrations = false - @State private var showingCreation = false @SceneBuilder var body: some Scene { WindowGroup { - ContentView(showingCreation: $showingCreation, runningSetup: $showingSetup, hasRunSetup: $hasRunSetup) + ContentView() .environment(EnvironmentValues._secretStoreList) - .onAppear { - if !hasRunSetup { - showingSetup = true - } - } .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in guard hasRunSetup else { return } agentStatusChecker.check() @@ -62,25 +25,41 @@ struct Secretive: App { forceLaunchAgent() } } - .sheet(isPresented: $showingIntegrations) { - IntegrationsView() - } } .commands { + AppCommands() + } + WindowGroup(id: String(describing: IntegrationsView.self)) { + IntegrationsView() + } + } + +} + +extension Secretive { + + struct AppCommands: Commands { + + @Environment(\.openWindow) var openWindow + @Environment(\.openURL) var openURL + @FocusedValue(\.showCreateSecret) var showCreateSecret + + var body: some Commands { CommandGroup(before: CommandGroupPlacement.appSettings) { Button(.integrationsMenuBarTitle, systemImage: "app.connected.to.app.below.fill") { - showingIntegrations = true + openWindow(id: String(describing: IntegrationsView.self)) } } CommandGroup(after: CommandGroupPlacement.newItem) { - Button(.appMenuNewSecretButton) { - showingCreation = true + Button(.appMenuNewSecretButton, systemImage: "plus") { + showCreateSecret?() } .keyboardShortcut(KeyboardShortcut(KeyEquivalent("N"), modifiers: [.command, .shift])) + .disabled(showCreateSecret?.isEnabled == false) } CommandGroup(replacing: .help) { Button(.appMenuHelpButton) { - NSWorkspace.shared.open(Constants.helpURL) + openURL(Constants.helpURL) } } SidebarCommands() @@ -113,8 +92,56 @@ extension Secretive { } - private enum Constants { static let helpURL = URL(string: "https://github.com/maxgoedjen/secretive/blob/main/FAQ.md")! } + +extension EnvironmentValues { + + // This is injected through .environment modifier below instead of @Entry for performance reasons (basially, restrictions around init/mainactor causing delay in loading secrets/"empty screen" blip). + @MainActor fileprivate static let _secretStoreList: SecretStoreList = { + let list = SecretStoreList() + let cryptoKit = SecureEnclave.Store() + let migrator = SecureEnclave.CryptoKitMigrator() + try? migrator.migrate(to: cryptoKit) + list.add(store: cryptoKit) + list.add(store: SmartCard.Store()) + return list + }() + + private static let _agentStatusChecker = AgentStatusChecker() + @Entry var agentStatusChecker: any AgentStatusCheckerProtocol = _agentStatusChecker + private static let _updater: any UpdaterProtocol = { + @AppStorage("defaultsHasRunSetup") var hasRunSetup = false + return Updater(checkOnLaunch: hasRunSetup) + }() + @Entry var updater: any UpdaterProtocol = _updater + + private static let _justUpdatedChecker = JustUpdatedChecker() + @Entry var justUpdatedChecker: any JustUpdatedCheckerProtocol = _justUpdatedChecker + + @MainActor var secretStoreList: SecretStoreList { + EnvironmentValues._secretStoreList + } +} + +extension FocusedValues { + @Entry var showCreateSecret: OpenSheet? +} + +final class OpenSheet { + + let closure: () -> Void + let isEnabled: Bool + + init(isEnabled: Bool = true, closure: @escaping () -> Void) { + self.isEnabled = isEnabled + self.closure = closure + } + + func callAsFunction() { + closure() + } + +} diff --git a/Sources/Secretive/Views/Configuration/ConfigurationItemView.swift b/Sources/Secretive/Views/Configuration/ConfigurationItemView.swift index 77e6a2e..edf9f67 100644 --- a/Sources/Secretive/Views/Configuration/ConfigurationItemView.swift +++ b/Sources/Secretive/Views/Configuration/ConfigurationItemView.swift @@ -32,7 +32,7 @@ struct ConfigurationItemView: View { Spacer() switch action { case .copy(let string): - Button(.copyableClickToCopyButton, systemImage: "document.on.document") { + Button(.copyableClickToCopyButton, systemImage: "doc.on.doc") { NSPasteboard.general.declareTypes([.string], owner: nil) NSPasteboard.general.setString(string, forType: .string) } diff --git a/Sources/Secretive/Views/Configuration/IntegrationsView.swift b/Sources/Secretive/Views/Configuration/IntegrationsView.swift index b738d0e..ced62d0 100644 --- a/Sources/Secretive/Views/Configuration/IntegrationsView.swift +++ b/Sources/Secretive/Views/Configuration/IntegrationsView.swift @@ -21,46 +21,18 @@ struct IntegrationsView: View { } } } detail: { - IntegrationsDetailView(selectedInstruction: $selectedInstruction) - .fauxToolbar { - Button(.setupDoneButton) { - dismiss() - } - .normalButton() - } + IntegrationsDetailView(selectedInstruction: $selectedInstruction) } + .toolbar { + Button(.setupDoneButton) { + dismiss() + } + } + .hiddenToolbar() + .windowBackgroundStyle(.thinMaterial) .onAppear { selectedInstruction = instructions.gettingStarted } - } - -} - -extension View { - - func fauxToolbar(content: () -> Content) -> some View { - modifier(FauxToolbarModifier(toolbarContent: content())) - } - -} - -struct FauxToolbarModifier: ViewModifier { - - var toolbarContent: ToolbarContent - - func body(content: Content) -> some View { - VStack(alignment: .leading, spacing: 0) { - content - Divider() - HStack { - Spacer() - toolbarContent - .padding(.top, 8) - .padding(.trailing, 16) - .padding(.bottom, 16) - } - } - .frame(minWidth: 400, minHeight: 400) } diff --git a/Sources/Secretive/Views/Styles/ActionButtonStyle.swift b/Sources/Secretive/Views/Modifiers/ActionButtonStyle.swift similarity index 100% rename from Sources/Secretive/Views/Styles/ActionButtonStyle.swift rename to Sources/Secretive/Views/Modifiers/ActionButtonStyle.swift diff --git a/Sources/Secretive/Views/Styles/BoxBackgroundStyle.swift b/Sources/Secretive/Views/Modifiers/BoxBackgroundStyle.swift similarity index 100% rename from Sources/Secretive/Views/Styles/BoxBackgroundStyle.swift rename to Sources/Secretive/Views/Modifiers/BoxBackgroundStyle.swift diff --git a/Sources/Secretive/Views/Styles/ErrorStyle.swift b/Sources/Secretive/Views/Modifiers/ErrorStyle.swift similarity index 100% rename from Sources/Secretive/Views/Styles/ErrorStyle.swift rename to Sources/Secretive/Views/Modifiers/ErrorStyle.swift diff --git a/Sources/Secretive/Views/Styles/ToolbarButtonStyle.swift b/Sources/Secretive/Views/Modifiers/ToolbarButtonStyle.swift similarity index 100% rename from Sources/Secretive/Views/Styles/ToolbarButtonStyle.swift rename to Sources/Secretive/Views/Modifiers/ToolbarButtonStyle.swift diff --git a/Sources/Secretive/Views/Modifiers/WindowBackgroundStyle.swift b/Sources/Secretive/Views/Modifiers/WindowBackgroundStyle.swift new file mode 100644 index 0000000..809d5e3 --- /dev/null +++ b/Sources/Secretive/Views/Modifiers/WindowBackgroundStyle.swift @@ -0,0 +1,47 @@ +import SwiftUI + +struct WindowBackgroundStyleModifier: ViewModifier { + + let shapeStyle: any ShapeStyle + + func body(content: Content) -> some View { + if #available(macOS 15.0, *) { + content + .containerBackground( + shapeStyle, for: .window + ) + } else { + content + } + } + +} + +extension View { + + func windowBackgroundStyle(_ style: some ShapeStyle) -> some View { + modifier(WindowBackgroundStyleModifier(shapeStyle: style)) + } + +} + +struct HiddenToolbarModifier: ViewModifier { + + func body(content: Content) -> some View { + if #available(macOS 15.0, *) { + content + .toolbarBackgroundVisibility(.hidden, for: .automatic) + } else { + content + } + } + +} + +extension View { + + func hiddenToolbar() -> some View { + modifier(HiddenToolbarModifier()) + } + +} diff --git a/Sources/Secretive/Views/Views/ContentView.swift b/Sources/Secretive/Views/Views/ContentView.swift index 117d0d8..822c318 100644 --- a/Sources/Secretive/Views/Views/ContentView.swift +++ b/Sources/Secretive/Views/Views/ContentView.swift @@ -6,9 +6,9 @@ import Brief struct ContentView: View { - @Binding var showingCreation: Bool - @Binding var runningSetup: Bool - @Binding var hasRunSetup: Bool + @AppStorage("defaultsHasRunSetup") var hasRunSetup = false + @State var showingCreation = false + @State var runningSetup = true @State var showingAgentInfo = false @State var activeSecret: AnySecret? @Environment(\.colorScheme) var colorScheme @@ -35,6 +35,23 @@ struct ContentView: View { toolbarItem(appPathNoticeView, id: "appPath") toolbarItem(newItemView, id: "new") } + .onAppear { + if !hasRunSetup { + runningSetup = true + } + } + .focusedSceneValue(\.showCreateSecret, .init(isEnabled: !runningSetup) { + showingCreation = true + }) + .sheet(isPresented: $showingCreation) { + if let modifiable = storeList.modifiableStore { + CreateSecretView(store: modifiable) { created in + if let created { + activeSecret = created + } + } + } + } .sheet(isPresented: $runningSetup) { SetupView(setupComplete: $hasRunSetup) } @@ -114,15 +131,6 @@ extension ContentView { showingCreation = true } .menuButton() - .sheet(isPresented: $showingCreation) { - if let modifiable = storeList.modifiableStore { - CreateSecretView(store: modifiable) { created in - if let created { - activeSecret = created - } - } - } - } } } diff --git a/Sources/Secretive/Views/Views/CopyableView.swift b/Sources/Secretive/Views/Views/CopyableView.swift index 5d5b431..69ad500 100644 --- a/Sources/Secretive/Views/Views/CopyableView.swift +++ b/Sources/Secretive/Views/Views/CopyableView.swift @@ -79,7 +79,7 @@ struct CopyableView: View { var copyButton: some View { switch interactionState { case .hovering: - Button(.copyableClickToCopyButton, systemImage: "document.on.document") { + Button(.copyableClickToCopyButton, systemImage: "doc.on.doc") { withAnimation { // Button will eat the click, so we set interaction state manually. interactionState = .clicking