From 9ca859fa3f6f9c990e5c9e0f213656d9694676f7 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Wed, 3 Sep 2025 00:15:56 -0700 Subject: [PATCH] . --- Sources/Secretive.xcodeproj/project.pbxproj | 80 +++- Sources/Secretive/App.swift | 4 +- .../Configuration/GettingStartedView.swift | 49 ++ .../Views/Configuration/Instructions.swift | 179 ++++++++ .../Configuration/IntegrationsView.swift | 115 +++++ .../Views/{ => Configuration}/SetupView.swift | 10 +- .../Configuration/ToolConfigurationView.swift | 110 +++++ .../Views/ConfigurationItemView.swift | 59 --- Sources/Secretive/Views/ContentView.swift | 222 ---------- Sources/Secretive/Views/EmptyStoreView.swift | 71 --- .../Secretive/Views/IntegrationsView.swift | 417 ------------------ Sources/Secretive/Views/NoStoresView.swift | 24 - .../{ => Secrets}/CreateSecretView.swift | 0 .../{ => Secrets}/DeleteSecretView.swift | 0 .../Views/{ => Secrets}/EditSecretView.swift | 0 .../{ => Secrets}/SecretDetailView.swift | 0 .../{ => Secrets}/SecretListItemView.swift | 0 .../Views/{ => Secrets}/StoreListView.swift | 0 .../{ => Styles}/ActionButtonStyle.swift | 0 .../Views/{ => Styles}/ErrorStyle.swift | 0 .../{ => Styles}/ToolbarButtonStyle.swift | 0 .../Views/{ => Views}/AgentStatusView.swift | 0 .../Views/{ => Views}/CopyableView.swift | 0 .../Views/{ => Views}/UpdateView.swift | 0 24 files changed, 523 insertions(+), 817 deletions(-) create mode 100644 Sources/Secretive/Views/Configuration/GettingStartedView.swift create mode 100644 Sources/Secretive/Views/Configuration/Instructions.swift create mode 100644 Sources/Secretive/Views/Configuration/IntegrationsView.swift rename Sources/Secretive/Views/{ => Configuration}/SetupView.swift (96%) create mode 100644 Sources/Secretive/Views/Configuration/ToolConfigurationView.swift delete mode 100644 Sources/Secretive/Views/ConfigurationItemView.swift delete mode 100644 Sources/Secretive/Views/ContentView.swift delete mode 100644 Sources/Secretive/Views/EmptyStoreView.swift delete mode 100644 Sources/Secretive/Views/IntegrationsView.swift delete mode 100644 Sources/Secretive/Views/NoStoresView.swift rename Sources/Secretive/Views/{ => Secrets}/CreateSecretView.swift (100%) rename Sources/Secretive/Views/{ => Secrets}/DeleteSecretView.swift (100%) rename Sources/Secretive/Views/{ => Secrets}/EditSecretView.swift (100%) rename Sources/Secretive/Views/{ => Secrets}/SecretDetailView.swift (100%) rename Sources/Secretive/Views/{ => Secrets}/SecretListItemView.swift (100%) rename Sources/Secretive/Views/{ => Secrets}/StoreListView.swift (100%) rename Sources/Secretive/Views/{ => Styles}/ActionButtonStyle.swift (100%) rename Sources/Secretive/Views/{ => Styles}/ErrorStyle.swift (100%) rename Sources/Secretive/Views/{ => Styles}/ToolbarButtonStyle.swift (100%) rename Sources/Secretive/Views/{ => Views}/AgentStatusView.swift (100%) rename Sources/Secretive/Views/{ => Views}/CopyableView.swift (100%) rename Sources/Secretive/Views/{ => Views}/UpdateView.swift (100%) diff --git a/Sources/Secretive.xcodeproj/project.pbxproj b/Sources/Secretive.xcodeproj/project.pbxproj index fc7a0a3..16d6f7e 100644 --- a/Sources/Secretive.xcodeproj/project.pbxproj +++ b/Sources/Secretive.xcodeproj/project.pbxproj @@ -27,6 +27,9 @@ 50153E22250DECA300525160 /* SecretListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E21250DECA300525160 /* SecretListItemView.swift */; }; 5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; }; 504788EC2E680DC800B4556F /* URLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788EB2E680DC400B4556F /* URLs.swift */; }; + 504788F22E681F3A00B4556F /* Instructions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F12E681F3A00B4556F /* Instructions.swift */; }; + 504788F42E681F6900B4556F /* ToolConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F32E681F6900B4556F /* ToolConfigurationView.swift */; }; + 504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F52E68206F00B4556F /* GettingStartedView.swift */; }; 50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; }; 50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0424393D1500F76F6C /* LaunchAgentController.swift */; }; 50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; }; @@ -112,6 +115,9 @@ 50153E21250DECA300525160 /* SecretListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListItemView.swift; sourceTree = ""; }; 5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = ""; }; 504788EB2E680DC400B4556F /* URLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLs.swift; sourceTree = ""; }; + 504788F12E681F3A00B4556F /* Instructions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instructions.swift; sourceTree = ""; }; + 504788F32E681F6900B4556F /* ToolConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolConfigurationView.swift; sourceTree = ""; }; + 504788F52E68206F00B4556F /* GettingStartedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GettingStartedView.swift; sourceTree = ""; }; 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = ""; }; 50571E0424393D1500F76F6C /* LaunchAgentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAgentController.swift; sourceTree = ""; }; 50617D7F23FCE48E0099B055 /* Secretive.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Secretive.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -187,6 +193,55 @@ path = Helpers; sourceTree = ""; }; + 504788ED2E681EB200B4556F /* Styles */ = { + isa = PBXGroup; + children = ( + 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */, + 50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */, + 5065E312295517C500E16645 /* ToolbarButtonStyle.swift */, + ); + path = Styles; + sourceTree = ""; + }; + 504788EE2E681EC300B4556F /* Secrets */ = { + isa = PBXGroup; + children = ( + 5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */, + 50B8550C24138C4F009958AC /* DeleteSecretView.swift */, + 2C4A9D2E2636FFD3008CC8E2 /* EditSecretView.swift */, + 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */, + 506772C82425BB8500034DED /* NoStoresView.swift */, + 50C385A42407A76D00AF2719 /* SecretDetailView.swift */, + 50153E21250DECA300525160 /* SecretListItemView.swift */, + 5079BA0E250F29BF00EA86F4 /* StoreListView.swift */, + ); + path = Secrets; + sourceTree = ""; + }; + 504788EF2E681ED700B4556F /* Configuration */ = { + isa = PBXGroup; + children = ( + 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */, + 50AE96FF2E5C1A420018C710 /* IntegrationsView.swift */, + 504788F12E681F3A00B4556F /* Instructions.swift */, + 504788F32E681F6900B4556F /* ToolConfigurationView.swift */, + 5066A6C12516F303004B5A36 /* SetupView.swift */, + 504788F52E68206F00B4556F /* GettingStartedView.swift */, + ); + path = Configuration; + sourceTree = ""; + }; + 504788F02E681F0100B4556F /* Views */ = { + isa = PBXGroup; + children = ( + 50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */, + 50617D8423FCE48E0099B055 /* ContentView.swift */, + 5066A6C72516FE6E004B5A36 /* CopyableView.swift */, + 50153E1F250AFCB200525160 /* UpdateView.swift */, + ); + path = Views; + sourceTree = ""; + }; 50617D7623FCE48D0099B055 = { isa = PBXGroup; children = ( @@ -249,24 +304,10 @@ 508A58B0241ED1C40069DC07 /* Views */ = { isa = PBXGroup; children = ( - 50617D8423FCE48E0099B055 /* ContentView.swift */, - 5065E312295517C500E16645 /* ToolbarButtonStyle.swift */, - 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */, - 5079BA0E250F29BF00EA86F4 /* StoreListView.swift */, - 50153E21250DECA300525160 /* SecretListItemView.swift */, - 50C385A42407A76D00AF2719 /* SecretDetailView.swift */, - 5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */, - 50B8550C24138C4F009958AC /* DeleteSecretView.swift */, - 2C4A9D2E2636FFD3008CC8E2 /* EditSecretView.swift */, - 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */, - 506772C82425BB8500034DED /* NoStoresView.swift */, - 50153E1F250AFCB200525160 /* UpdateView.swift */, - 5066A6C12516F303004B5A36 /* SetupView.swift */, - 50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */, - 50AE96FF2E5C1A420018C710 /* IntegrationsView.swift */, - 5066A6C72516FE6E004B5A36 /* CopyableView.swift */, - 50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */, - 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */, + 504788EF2E681ED700B4556F /* Configuration */, + 504788EE2E681EC300B4556F /* Secrets */, + 504788ED2E681EB200B4556F /* Styles */, + 504788F02E681F0100B4556F /* Views */, ); path = Views; sourceTree = ""; @@ -445,6 +486,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 504788F22E681F3A00B4556F /* Instructions.swift in Sources */, 50BDCB742E6436CA0072D2E7 /* ErrorStyle.swift in Sources */, 2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */, 5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */, @@ -452,6 +494,7 @@ 5066A6C22516F303004B5A36 /* SetupView.swift in Sources */, 5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */, 50617D8523FCE48E0099B055 /* ContentView.swift in Sources */, + 504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */, 50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */, 50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */, 5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */, @@ -469,6 +512,7 @@ 50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */, 50BDCB762E6450950072D2E7 /* ConfigurationItemView.swift in Sources */, 50617D8323FCE48E0099B055 /* App.swift in Sources */, + 504788F42E681F6900B4556F /* ToolConfigurationView.swift in Sources */, 506772C92425BB8500034DED /* NoStoresView.swift in Sources */, 50153E22250DECA300525160 /* SecretListItemView.swift in Sources */, 508A58B5241ED48F0069DC07 /* PreviewAgentStatusChecker.swift in Sources */, diff --git a/Sources/Secretive/App.swift b/Sources/Secretive/App.swift index 93ecf03..b1b9575 100644 --- a/Sources/Secretive/App.swift +++ b/Sources/Secretive/App.swift @@ -45,9 +45,9 @@ struct Secretive: App { ContentView(showingCreation: $showingCreation, runningSetup: $showingSetup, hasRunSetup: $hasRunSetup) .environment(EnvironmentValues._secretStoreList) .onAppear { - if !hasRunSetup { +// if !hasRunSetup { showingSetup = true - } +// } } .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in guard hasRunSetup else { return } diff --git a/Sources/Secretive/Views/Configuration/GettingStartedView.swift b/Sources/Secretive/Views/Configuration/GettingStartedView.swift new file mode 100644 index 0000000..67c7b42 --- /dev/null +++ b/Sources/Secretive/Views/Configuration/GettingStartedView.swift @@ -0,0 +1,49 @@ +import SwiftUI + +struct GettingStartedView: View { + + private let instructions = Instructions() + + @Binding var selectedInstruction: ConfigurationFileInstructions? + + init(selectedInstruction: Binding) { + _selectedInstruction = selectedInstruction + } + + var body: some View { + Form { + Section(.integrationsGettingStartedTitle) { + Text(.integrationsGettingStartedTitleDescription) + } + Section { + Group { + Text(.integrationsGettingStartedSuggestionSsh) + .onTapGesture { + self.selectedInstruction = instructions.ssh + } + VStack(alignment: .leading, spacing: 5) { + Text(.integrationsGettingStartedSuggestionShell) + Text(.integrationsGettingStartedSuggestionShellDefault(shellName: String(localized: instructions.defaultShell.tool))) + .font(.caption2) + } + .onTapGesture { + self.selectedInstruction = instructions.defaultShell + } + Text(.integrationsGettingStartedSuggestionGit) + .onTapGesture { + self.selectedInstruction = instructions.git + } + } + .foregroundStyle(.link) + + } header: { + Text(.integrationsGettingStartedWhatShouldIConfigureTitle) + } + footer: { + Text(.integrationsGettingStartedMultipleConfig) + } + } + .formStyle(.grouped) + } + +} diff --git a/Sources/Secretive/Views/Configuration/Instructions.swift b/Sources/Secretive/Views/Configuration/Instructions.swift new file mode 100644 index 0000000..bb92b86 --- /dev/null +++ b/Sources/Secretive/Views/Configuration/Instructions.swift @@ -0,0 +1,179 @@ +import Foundation + +struct Instructions { + + enum Constants { + static let publicKeyPathPlaceholder = "_PUBLIC_KEY_PATH_PLACEHOLDER_" + static let publicKeyPlaceholder = "_PUBLIC_KEY_PLACEHOLDER_" + } + + var defaultShell: ConfigurationFileInstructions { + zsh + } + + var gettingStarted: ConfigurationFileInstructions = ConfigurationFileInstructions(.integrationsGettingStartedRowTitle, id: .gettingStarted) + + var ssh: ConfigurationFileInstructions { + ConfigurationFileInstructions( + tool: LocalizedStringResource.integrationsToolNameSsh, + configPath: "~/.ssh/config", + configText: "Host *\n\tIdentityAgent \(URL.socketPath)", + website: URL(string: "https://man.openbsd.org/ssh_config.5")!, + note: .integrationsSshSpecificKeyNote, + ) + } + + var git: ConfigurationFileInstructions { + ConfigurationFileInstructions( + tool: .integrationsToolNameGitSigning, + steps: [ + .init(path: "~/.gitconfig", steps: [ + .integrationsGitStepGitconfigDescription(publicKeyPathPlaceholder: Constants.publicKeyPathPlaceholder) + ], + note: .integrationsGitStepGitconfigSectionNote + ), + .init( + path: "~/.gitallowedsigners", + steps: [ + LocalizedStringResource(stringLiteral: Constants.publicKeyPlaceholder) + ], + note: .integrationsGitStepGitallowedsignersDescription + ), + ], + website: URL(string: "https://git-scm.com/docs/git-config")!, + ) + } + + var zsh: ConfigurationFileInstructions { + ConfigurationFileInstructions( + tool: .integrationsToolNameZsh, + configPath: "~/.zshrc", + configText: "export SSH_AUTH_SOCK=\(URL.socketPath)" + ) + } + + var instructions: [ConfigurationGroup] { + [ + ConfigurationGroup(name: .integrationsGettingStartedSectionTitle, instructions: [ + gettingStarted + ]), + ConfigurationGroup( + name: .integrationsSystemSectionTitle, + instructions: [ + ssh, + git, + ] + ), + ConfigurationGroup(name: .integrationsShellSectionTitle, instructions: [ + zsh, + ConfigurationFileInstructions( + tool: .integrationsToolNameBash, + configPath: "~/.bashrc", + configText: "export SSH_AUTH_SOCK=\(URL.socketPath)" + ), + ConfigurationFileInstructions( + tool: .integrationsToolNameFish, + configPath: "~/.config/fish/config.fish", + configText: "set -x SSH_AUTH_SOCK \(URL.socketPath)" + ), + ConfigurationFileInstructions(.integrationsOtherShellRowTitle, id: .otherShell), + ]), + ConfigurationGroup(name: .integrationsOtherSectionTitle, instructions: [ + ConfigurationFileInstructions(.integrationsAppsRowTitle, id: .otherApp), + ]), + ] + } + +} + +struct ConfigurationGroup: Identifiable { + let id = UUID() + var name: LocalizedStringResource + var instructions: [ConfigurationFileInstructions] = [] +} + +struct ConfigurationFileInstructions: Hashable, Identifiable { + + struct StepGroup: Hashable, Identifiable { + let path: String + let steps: [LocalizedStringResource] + let note: LocalizedStringResource? + var id: String { path } + + init(path: String, steps: [LocalizedStringResource], note: LocalizedStringResource? = nil) { + self.path = path + self.steps = steps + self.note = note + } + + func hash(into hasher: inout Hasher) { + id.hash(into: &hasher) + } + } + + var id: ID + var tool: LocalizedStringResource + var steps: [StepGroup] + var requiresSecret: Bool + var website: URL? + + init( + tool: LocalizedStringResource, + configPath: String, + configText: LocalizedStringResource, + requiresSecret: Bool = false, + website: URL? = nil, + note: LocalizedStringResource? = nil + ) { + self.id = .tool(String(localized: tool)) + self.tool = tool + self.steps = [StepGroup(path: configPath, steps: [configText], note: note)] + self.requiresSecret = requiresSecret + self.website = website + } + + init( + tool: LocalizedStringResource, + steps: [StepGroup], + requiresSecret: Bool = false, + website: URL? = nil + ) { + self.id = .tool(String(localized: tool)) + self.tool = tool + self.steps = steps + self.requiresSecret = true + self.website = website + } + + init(_ name: LocalizedStringResource, id: ID) { + self.id = id + tool = name + steps = [] + requiresSecret = false + } + + func hash(into hasher: inout Hasher) { + id.hash(into: &hasher) + } + + enum ID: Identifiable, Hashable { + case gettingStarted + case tool(String) + case otherShell + case otherApp + + var id: String { + switch self { + case .gettingStarted: + "getting_started" + case .tool(let name): + name + case .otherShell: + "other_shell" + case .otherApp: + "other_app" + } + } + } + +} diff --git a/Sources/Secretive/Views/Configuration/IntegrationsView.swift b/Sources/Secretive/Views/Configuration/IntegrationsView.swift new file mode 100644 index 0000000..de6b8a0 --- /dev/null +++ b/Sources/Secretive/Views/Configuration/IntegrationsView.swift @@ -0,0 +1,115 @@ +import SwiftUI + +struct IntegrationsView: View { + + @Environment(\.dismiss) private var dismiss + + @State private var selectedInstruction: ConfigurationFileInstructions? + private let instructions = Instructions() + + var body: some View { + NavigationSplitView { + List(selection: $selectedInstruction) { + ForEach(instructions.instructions) { group in + Section(group.name) { + ForEach(group.instructions) { instruction in + Text(instruction.tool) + .padding(.vertical, 8) + .tag(instruction) + } + } + } + } + } detail: { + IntegrationsDetailView(selectedInstruction: $selectedInstruction) + .fauxToolbar { + Button(.setupDoneButton) { + dismiss() + } + .normalButton() + } + } + .onAppear { + selectedInstruction = instructions.gettingStarted + } + .frame(minHeight: 500) + } + +} + +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) + } + } + + } + +} + +struct IntegrationsDetailView: View { + + @Binding private var selectedInstruction: ConfigurationFileInstructions? + + init(selectedInstruction: Binding) { + _selectedInstruction = selectedInstruction + } + + var body: some View { + if let selectedInstruction { + switch selectedInstruction.id { + case .gettingStarted: + GettingStartedView(selectedInstruction: $selectedInstruction) + case .tool: + ToolConfigurationView(selectedInstruction: selectedInstruction) + case .otherShell: + Form { + Section { + Link(.integrationsViewOtherGithubLink, destination: URL(string: "https://github.com/maxgoedjen/secretive-config-instructions/tree/main/shells")!) + } header: { + Text(.integrationsCommunityShellListDescription) + .font(.body) + } + } + .formStyle(.grouped) + + case .otherApp: + Form { + Section { + Link(.integrationsViewOtherGithubLink, destination: URL(string: "https://github.com/maxgoedjen/secretive-config-instructions/tree/main/apps")!) + } header: { + Text(.integrationsCommunityAppsListDescription) + .font(.body) + } + } + .formStyle(.grouped) + } + } + + } + +} + +#Preview { + IntegrationsView() + .frame(height: 500) +} diff --git a/Sources/Secretive/Views/SetupView.swift b/Sources/Secretive/Views/Configuration/SetupView.swift similarity index 96% rename from Sources/Secretive/Views/SetupView.swift rename to Sources/Secretive/Views/Configuration/SetupView.swift index 9a49930..2c2d66e 100644 --- a/Sources/Secretive/Views/SetupView.swift +++ b/Sources/Secretive/Views/Configuration/SetupView.swift @@ -67,7 +67,7 @@ struct SetupView: View { buttonWidth = width } .background(.white.opacity(0.1), in: RoundedRectangle(cornerRadius: 10)) - .frame(minWidth: 700, maxWidth: .infinity) + .frame(minWidth: 600, maxWidth: .infinity) HStack { Spacer() Button(.setupDoneButton) { @@ -154,17 +154,19 @@ struct StepView: View { } var body: some View { - HStack(spacing: 20) { + HStack(spacing: 0) { icon .resizable() .aspectRatio(contentMode: .fit) .frame(width: 24) - VStack(alignment: .leading, spacing: 6) { + Spacer() + .frame(width: 20) + VStack(alignment: .leading, spacing: 4) { Text(title) .bold() Text(description) } - Spacer() + Spacer(minLength: 20) actions } .padding(20) diff --git a/Sources/Secretive/Views/Configuration/ToolConfigurationView.swift b/Sources/Secretive/Views/Configuration/ToolConfigurationView.swift new file mode 100644 index 0000000..cd1bc69 --- /dev/null +++ b/Sources/Secretive/Views/Configuration/ToolConfigurationView.swift @@ -0,0 +1,110 @@ +import SwiftUI +import SecretKit + +struct ToolConfigurationView: View { + + private let instructions = Instructions() + let selectedInstruction: ConfigurationFileInstructions + + @Environment(\.secretStoreList) private var secretStoreList + + @State var creating = false + @State var selectedSecret: AnySecret? + + init(selectedInstruction: ConfigurationFileInstructions) { + self.selectedInstruction = selectedInstruction + } + + var body: some View { + Form { + if selectedInstruction.requiresSecret { + if secretStoreList.allSecrets.isEmpty { + Section { + Text(.integrationsConfigureUsingSecretEmptyCreate) + if let store = secretStoreList.modifiableStore { + HStack { + Spacer() + Button(.createSecretTitle) { + creating = true + } + .sheet(isPresented: $creating) { + CreateSecretView(store: store) { created in + selectedSecret = created + } + } + } + } + } + } else { + Section { + Picker(.integrationsConfigureUsingSecretSecretTitle, selection: $selectedSecret) { + if selectedSecret == nil { + Text(.integrationsConfigureUsingSecretNoSecret) + .tag(nil as (AnySecret?)) + } + ForEach(secretStoreList.allSecrets) { secret in + Text(secret.name) + .tag(secret) + } + } + } header: { + Text(.integrationsConfigureUsingSecretHeader) + } + .onAppear { + selectedSecret = secretStoreList.allSecrets.first + } + } + } + ForEach(selectedInstruction.steps) { stepGroup in + Section { + ConfigurationItemView(title: .integrationsPathTitle, value: stepGroup.path, action: .revealInFinder(stepGroup.path)) + ForEach(stepGroup.steps, id: \.self.key) { step in + ConfigurationItemView(title: .integrationsAddThisTitle, action: .copy(String(localized: step))) { + HStack { + Text(placeholdersReplaced(text: String(localized: step))) + .padding(8) + .font(.system(.subheadline, design: .monospaced)) + Spacer() + } + .frame(maxWidth: .infinity) + .background { + RoundedRectangle(cornerRadius: 6) + .fill(.black.opacity(0.05)) + .stroke(.separator, lineWidth: 1) + } + } + } + } footer: { + if let note = stepGroup.note { + Text(note) + .font(.caption) + } + } + } + if let url = selectedInstruction.website { + Section { + Link(destination: url) { + VStack(alignment: .leading, spacing: 5) { + Text(.integrationsWebLink) + .font(.headline) + Text(url.absoluteString) + .font(.caption2) + } + } + } + } + } + .formStyle(.grouped) + + } + + func placeholdersReplaced(text: String) -> String { + guard let selectedSecret else { return text } + let writer = OpenSSHPublicKeyWriter() + let fileController = PublicKeyFileStoreController(homeDirectory: URL.agentHomeURL) + return text + .replacingOccurrences(of: Instructions.Constants.publicKeyPlaceholder, with: writer.openSSHString(secret: selectedSecret)) + .replacingOccurrences(of: Instructions.Constants.publicKeyPathPlaceholder, with: fileController.publicKeyPath(for: selectedSecret)) + } + +} diff --git a/Sources/Secretive/Views/ConfigurationItemView.swift b/Sources/Secretive/Views/ConfigurationItemView.swift deleted file mode 100644 index 2c8520b..0000000 --- a/Sources/Secretive/Views/ConfigurationItemView.swift +++ /dev/null @@ -1,59 +0,0 @@ -import SwiftUI - -struct ConfigurationItemView: View { - - enum Action: Hashable { - case copy(String) - case revealInFinder(String) - } - - let title: LocalizedStringResource - let content: Content - let action: Action? - - init(title: LocalizedStringResource, value: String, action: Action? = nil) where Content == Text { - self.title = title - self.content = Text(value) - .font(.subheadline) - .foregroundStyle(.secondary) - self.action = action - } - - init(title: LocalizedStringResource, action: Action? = nil, content: () -> Content) { - self.title = title - self.content = content() - self.action = action - } - - var body: some View { - VStack(alignment: .leading) { - HStack { - Text(title) - Spacer() - switch action { - case .copy(let string): - Button(.copyableClickToCopyButton, systemImage: "document.on.document") { - NSPasteboard.general.declareTypes([.string], owner: nil) - NSPasteboard.general.setString(string, forType: .string) - } - .labelStyle(.iconOnly) - .buttonStyle(.borderless) - case .revealInFinder(let rawPath): - Button(.revealInFinderButton, systemImage: "folder") { - // All foundation-based normalization methods replace this with the container directly. - let processedPath = rawPath.replacingOccurrences(of: "~", with: "/Users/\(NSUserName())") - let url = URL(filePath: processedPath) - let folder = url.deletingLastPathComponent().path() - NSWorkspace.shared.selectFile(processedPath, inFileViewerRootedAtPath: folder) - } - .labelStyle(.iconOnly) - .buttonStyle(.borderless) - case nil: - EmptyView() - } - } - content - } - } -} - diff --git a/Sources/Secretive/Views/ContentView.swift b/Sources/Secretive/Views/ContentView.swift deleted file mode 100644 index 933013d..0000000 --- a/Sources/Secretive/Views/ContentView.swift +++ /dev/null @@ -1,222 +0,0 @@ -import SwiftUI -import SecretKit -import SecureEnclaveSecretKit -import SmartCardSecretKit -import Brief - -struct ContentView: View { - - @Binding var showingCreation: Bool - @Binding var runningSetup: Bool - @Binding var hasRunSetup: Bool - @State var showingAgentInfo = false - @State var activeSecret: AnySecret? - @Environment(\.colorScheme) var colorScheme - - @Environment(\.secretStoreList) private var storeList - @Environment(\.updater) private var updater: any UpdaterProtocol - @Environment(\.agentStatusChecker) private var agentStatusChecker: any AgentStatusCheckerProtocol - - @State private var selectedUpdate: Release? - @State private var showingAppPathNotice = false - - var body: some View { - VStack { - if storeList.anyAvailable { - StoreListView(activeSecret: $activeSecret) - } else { - NoStoresView() - } - } - .frame(minWidth: 640, minHeight: 320) - .toolbar { - toolbarItem(updateNoticeView, id: "update") - toolbarItem(runningOrRunSetupView, id: "setup") - toolbarItem(appPathNoticeView, id: "appPath") - toolbarItem(newItemView, id: "new") - } - .sheet(isPresented: $runningSetup) { - SetupView(setupComplete: $hasRunSetup) - } - } - -} - -extension ContentView { - - - @ToolbarContentBuilder - func toolbarItem(_ view: some View, id: String) -> some ToolbarContent { - if #available(macOS 26.0, *) { - ToolbarItem(id: id) { view } - .sharedBackgroundVisibility(.hidden) - } else { - ToolbarItem(id: id) { view } - } - } - - var needsSetup: Bool { - runningSetup || !hasRunSetup - } - - /// Item either showing a "everything's good, here's more info" or "something's wrong, re-run setup" message - /// These two are mutually exclusive - @ViewBuilder - var runningOrRunSetupView: some View { - if needsSetup { - setupNoticeView - } else { - agentStatusToolbarView - } - } - - var updateNoticeContent: (LocalizedStringResource, Color)? { - guard let update = updater.update else { return nil } - if update.critical { - return (.updateCriticalNoticeTitle, .red) - } else { - if updater.testBuild { - return (.updateTestNoticeTitle, .blue) - } else { - return (.updateNormalNoticeTitle, .orange) - } - } - } - - @ViewBuilder - var updateNoticeView: some View { - if let update = updater.update, let (text, color) = updateNoticeContent { - Button(action: { - selectedUpdate = update - }, label: { - Text(text) - .font(.headline) - .foregroundColor(.white) - }) - .buttonStyle(ToolbarButtonStyle(color: color)) - .sheet(item: $selectedUpdate) { update in - UpdateDetailView(update: update) - } - } - } - - @ViewBuilder - var newItemView: some View { - if storeList.modifiableStore?.isAvailable ?? false { - Button(.appMenuNewSecretButton, systemImage: "plus") { - showingCreation = true - } - .menuButton() - .sheet(isPresented: $showingCreation) { - if let modifiable = storeList.modifiableStore { - CreateSecretView(store: modifiable) { created in - if let created { - activeSecret = created - } - } - } - } - } - } - - @ViewBuilder - var setupNoticeView: some View { - Button(action: { - runningSetup = true - }, label: { - if !hasRunSetup { - Text(.agentSetupNoticeTitle) - .font(.headline) - } - }) - .buttonStyle(ToolbarButtonStyle(color: .orange)) - } - - @ViewBuilder - var agentStatusToolbarView: some View { - Button(action: { - showingAgentInfo = true - }, label: { - HStack { - if agentStatusChecker.running { - Text(.agentRunningNoticeTitle) - .font(.headline) - .foregroundColor(colorScheme == .light ? Color(white: 0.3) : .white) - Circle() - .frame(width: 10, height: 10) - .foregroundColor(Color.green) - } else { - Text(.agentNotRunningNoticeTitle) - .font(.headline) - Circle() - .frame(width: 10, height: 10) - .foregroundColor(Color.red) - } - } - }) - .buttonStyle( - ToolbarButtonStyle( - lightColor: agentStatusChecker.running ? .black.opacity(0.05) : .red.opacity(0.75), - darkColor: agentStatusChecker.running ? .white.opacity(0.05) : .red.opacity(0.5), - ) - ) - .popover(isPresented: $showingAgentInfo, attachmentAnchor: attachmentAnchor, arrowEdge: .bottom) { - AgentStatusView() - } - } - - @ViewBuilder - var appPathNoticeView: some View { - if !ApplicationDirectoryController().isInApplicationsDirectory { - Button(action: { - showingAppPathNotice = true - }, label: { - Group { - Text(.appNotInApplicationsNoticeTitle) - } - .font(.headline) - .foregroundColor(.white) - }) - .buttonStyle(ToolbarButtonStyle(color: .orange)) - .popover(isPresented: $showingAppPathNotice, attachmentAnchor: attachmentAnchor, arrowEdge: .bottom) { - VStack { - Image(systemName: "exclamationmark.triangle") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 64) - Text(.appNotInApplicationsNoticeDetailDescription) - .frame(maxWidth: 300) - } - .padding() - } - } - } - - var attachmentAnchor: PopoverAttachmentAnchor { - .rect(.bounds) - } - -} - -#if DEBUG - -struct ContentView_Previews: PreviewProvider { - - static var previews: some View { - Group { - // Empty on modifiable and nonmodifiable - ContentView(showingCreation: .constant(false), runningSetup: .constant(false), hasRunSetup: .constant(true)) - .environment(Preview.storeList(stores: [Preview.Store(numberOfRandomSecrets: 0)], modifiableStores: [Preview.StoreModifiable(numberOfRandomSecrets: 0)])) - .environment(PreviewUpdater()) - - // 5 items on modifiable and nonmodifiable - ContentView(showingCreation: .constant(false), runningSetup: .constant(false), hasRunSetup: .constant(true)) - .environment(Preview.storeList(stores: [Preview.Store()], modifiableStores: [Preview.StoreModifiable()])) - .environment(PreviewUpdater()) - } - - } -} - -#endif - diff --git a/Sources/Secretive/Views/EmptyStoreView.swift b/Sources/Secretive/Views/EmptyStoreView.swift deleted file mode 100644 index 3f0bd81..0000000 --- a/Sources/Secretive/Views/EmptyStoreView.swift +++ /dev/null @@ -1,71 +0,0 @@ -import SwiftUI -import SecretKit - -struct EmptyStoreView: View { - - @State var store: AnySecretStore? - - var body: some View { - if store is AnySecretStoreModifiable { - EmptyStoreModifiableView() - } else { - EmptyStoreImmutableView() - } - } -} - -struct EmptyStoreImmutableView: View { - - var body: some View { - VStack { - Text(.emptyStoreNonmodifiableTitle).bold() - Text(.emptyStoreNonmodifiableDescription) - Text(.emptyStoreNonmodifiableSupportedKeyTypes) - }.frame(maxWidth: .infinity, maxHeight: .infinity) - } - -} - -struct EmptyStoreModifiableView: View { - - var body: some View { - GeometryReader { windowGeometry in - VStack { - GeometryReader { g in - Path { path in - path.move(to: CGPoint(x: g.size.width / 2, y: g.size.height)) - path.addCurve(to: - CGPoint(x: g.size.width * (3/4), y: g.size.height * (1/2)), control1: - CGPoint(x: g.size.width / 2, y: g.size.height * (1/2)), control2: - CGPoint(x: g.size.width * (3/4), y: g.size.height * (1/2))) - path.addCurve(to: - CGPoint(x: g.size.width - 13, y: 0), control1: - CGPoint(x: g.size.width - 13 , y: g.size.height * (1/2)), control2: - CGPoint(x: g.size.width - 13, y: 0)) - }.stroke(style: StrokeStyle(lineWidth: 5, lineCap: .round)) - Path { path in - path.move(to: CGPoint(x: g.size.width - 23, y: 0)) - path.addLine(to: CGPoint(x: g.size.width - 13, y: -10)) - path.addLine(to: CGPoint(x: g.size.width - 3, y: 0)) - }.fill() - }.frame(height: (windowGeometry.size.height/2) - 20).padding() - Text(.emptyStoreModifiableClickHereTitle).bold() - Text(.emptyStoreModifiableClickHereDescription) - Spacer() - }.frame(maxWidth: .infinity, maxHeight: .infinity) - } - } -} - -#if DEBUG - -struct EmptyStoreModifiableView_Previews: PreviewProvider { - static var previews: some View { - Group { - EmptyStoreImmutableView() - EmptyStoreModifiableView() - } - } -} - -#endif diff --git a/Sources/Secretive/Views/IntegrationsView.swift b/Sources/Secretive/Views/IntegrationsView.swift deleted file mode 100644 index 3ee3af4..0000000 --- a/Sources/Secretive/Views/IntegrationsView.swift +++ /dev/null @@ -1,417 +0,0 @@ -import SwiftUI -import SecretKit - -struct IntegrationsView: View { - - @Environment(\.dismiss) private var dismiss - - @State private var selectedInstruction: ConfigurationFileInstructions? - private let instructions = Instructions() - - var body: some View { - NavigationSplitView { - List(selection: $selectedInstruction) { - ForEach(instructions.instructions) { group in - Section(group.name) { - ForEach(group.instructions) { instruction in - Text(instruction.tool) - .padding(.vertical, 8) - .tag(instruction) - } - } - } - } - } detail: { - IntegrationsDetailView(selectedInstruction: $selectedInstruction) - .fauxToolbar { - Button(.setupDoneButton) { - dismiss() - } - .normalButton() - } - } - .onAppear { - selectedInstruction = instructions.gettingStarted - } - .frame(minHeight: 500) - } - -} - -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) - } - } - - } - -} - -struct IntegrationsDetailView: View { - - @Environment(\.secretStoreList) private var secretStoreList - @State var creating = false - - @State var selectedSecret: AnySecret? - @Binding private var selectedInstruction: ConfigurationFileInstructions? - private let instructions = Instructions() - - init(selectedInstruction: Binding) { - _selectedInstruction = selectedInstruction - } - - var body: some View { - if let selectedInstruction { - switch selectedInstruction.id { - case .gettingStarted: - Form { - Section(.integrationsGettingStartedTitle) { - Text(.integrationsGettingStartedTitleDescription) - } - Section { - Group { - Text(.integrationsGettingStartedSuggestionSsh) - .onTapGesture { - self.selectedInstruction = instructions.ssh - } - VStack(alignment: .leading, spacing: 5) { - Text(.integrationsGettingStartedSuggestionShell) - Text(.integrationsGettingStartedSuggestionShellDefault(shellName: String(localized: instructions.defaultShell.tool))) - .font(.caption2) - } - .onTapGesture { - self.selectedInstruction = instructions.defaultShell - } - Text(.integrationsGettingStartedSuggestionGit) - .onTapGesture { - self.selectedInstruction = instructions.git - } - } - .foregroundStyle(.link) - - } header: { - Text(.integrationsGettingStartedWhatShouldIConfigureTitle) - } - footer: { - Text(.integrationsGettingStartedMultipleConfig) - } - } - .formStyle(.grouped) - case .tool: - Form { - if selectedInstruction.requiresSecret { - if secretStoreList.allSecrets.isEmpty { - Section { - Text(.integrationsConfigureUsingSecretEmptyCreate) - if let store = secretStoreList.modifiableStore { - HStack { - Spacer() - Button(.createSecretTitle) { - creating = true - } - .sheet(isPresented: $creating) { - CreateSecretView(store: store) { created in - selectedSecret = created - } - } - } - } - } - } else { - Section { - Picker(.integrationsConfigureUsingSecretSecretTitle, selection: $selectedSecret) { - if selectedSecret == nil { - Text(.integrationsConfigureUsingSecretNoSecret) - .tag(nil as (AnySecret?)) - } - ForEach(secretStoreList.allSecrets) { secret in - Text(secret.name) - .tag(secret) - } - } - } header: { - Text(.integrationsConfigureUsingSecretHeader) - } - .onAppear { - selectedSecret = secretStoreList.allSecrets.first - } - } - } - ForEach(selectedInstruction.steps) { stepGroup in - Section { - ConfigurationItemView(title: .integrationsPathTitle, value: stepGroup.path, action: .revealInFinder(stepGroup.path)) - ForEach(stepGroup.steps, id: \.self.key) { step in - ConfigurationItemView(title: .integrationsAddThisTitle, action: .copy(String(localized: step))) { - HStack { - Text(placeholdersReplaced(text: String(localized: step))) - .padding(8) - .font(.system(.subheadline, design: .monospaced)) - Spacer() - } - .frame(maxWidth: .infinity) - .background { - RoundedRectangle(cornerRadius: 6) - .fill(.black.opacity(0.05)) - .stroke(.separator, lineWidth: 1) - } - } - } - } footer: { - if let note = stepGroup.note { - Text(note) - .font(.caption) - } - } - } - if let url = selectedInstruction.website { - Section { - Link(destination: url) { - VStack(alignment: .leading, spacing: 5) { - Text(.integrationsWebLink) - .font(.headline) - Text(url.absoluteString) - .font(.caption2) - } - } - } - } - } - .formStyle(.grouped) - case .otherShell: - Form { - Section { - Link(.integrationsViewOtherGithubLink, destination: URL(string: "https://github.com/maxgoedjen/secretive-config-instructions/tree/main/shells")!) - } header: { - Text(.integrationsCommunityShellListDescription) - .font(.body) - } - } - .formStyle(.grouped) - - case .otherApp: - Form { - Section { - Link(.integrationsViewOtherGithubLink, destination: URL(string: "https://github.com/maxgoedjen/secretive-config-instructions/tree/main/apps")!) - } header: { - Text(.integrationsCommunityAppsListDescription) - .font(.body) - } - } - .formStyle(.grouped) - } - } - - } - - func placeholdersReplaced(text: String) -> String { - guard let selectedSecret else { return text } - let writer = OpenSSHPublicKeyWriter() - let fileController = PublicKeyFileStoreController(homeDirectory: URL.agentHomeURL) - return text - .replacingOccurrences(of: Instructions.Constants.publicKeyPlaceholder, with: writer.openSSHString(secret: selectedSecret)) - .replacingOccurrences(of: Instructions.Constants.publicKeyPathPlaceholder, with: fileController.publicKeyPath(for: selectedSecret)) - } -} - -private struct Instructions { - - enum Constants { - static let publicKeyPathPlaceholder = "_PUBLIC_KEY_PATH_PLACEHOLDER_" - static let publicKeyPlaceholder = "_PUBLIC_KEY_PLACEHOLDER_" - } - - var defaultShell: ConfigurationFileInstructions { - zsh - } - - var gettingStarted: ConfigurationFileInstructions = ConfigurationFileInstructions(.integrationsGettingStartedRowTitle, id: .gettingStarted) - - var ssh: ConfigurationFileInstructions { - ConfigurationFileInstructions( - tool: LocalizedStringResource.integrationsToolNameSsh, - configPath: "~/.ssh/config", - configText: "Host *\n\tIdentityAgent \(URL.socketPath)", - website: URL(string: "https://man.openbsd.org/ssh_config.5")!, - note: .integrationsSshSpecificKeyNote, - ) - } - - var git: ConfigurationFileInstructions { - ConfigurationFileInstructions( - tool: .integrationsToolNameGitSigning, - steps: [ - .init(path: "~/.gitconfig", steps: [ - .integrationsGitStepGitconfigDescription(publicKeyPathPlaceholder: Constants.publicKeyPathPlaceholder) - ], - note: .integrationsGitStepGitconfigSectionNote - ), - .init( - path: "~/.gitallowedsigners", - steps: [ - LocalizedStringResource(stringLiteral: Constants.publicKeyPlaceholder) - ], - note: .integrationsGitStepGitallowedsignersDescription - ), - ], - website: URL(string: "https://git-scm.com/docs/git-config")!, - ) - } - - var zsh: ConfigurationFileInstructions { - ConfigurationFileInstructions( - tool: .integrationsToolNameZsh, - configPath: "~/.zshrc", - configText: "export SSH_AUTH_SOCK=\(URL.socketPath)" - ) - } - - var instructions: [ConfigurationGroup] { - [ - ConfigurationGroup(name: .integrationsGettingStartedSectionTitle, instructions: [ - gettingStarted - ]), - ConfigurationGroup( - name: .integrationsSystemSectionTitle, - instructions: [ - ssh, - git, - ] - ), - ConfigurationGroup(name: .integrationsShellSectionTitle, instructions: [ - zsh, - ConfigurationFileInstructions( - tool: .integrationsToolNameBash, - configPath: "~/.bashrc", - configText: "export SSH_AUTH_SOCK=\(URL.socketPath)" - ), - ConfigurationFileInstructions( - tool: .integrationsToolNameFish, - configPath: "~/.config/fish/config.fish", - configText: "set -x SSH_AUTH_SOCK \(URL.socketPath)" - ), - ConfigurationFileInstructions(.integrationsOtherShellRowTitle, id: .otherShell), - ]), - ConfigurationGroup(name: .integrationsOtherSectionTitle, instructions: [ - ConfigurationFileInstructions(.integrationsAppsRowTitle, id: .otherApp), - ]), - ] - } - -} - -struct ConfigurationGroup: Identifiable { - let id = UUID() - var name: LocalizedStringResource - var instructions: [ConfigurationFileInstructions] = [] -} - -struct ConfigurationFileInstructions: Hashable, Identifiable { - - struct StepGroup: Hashable, Identifiable { - let path: String - let steps: [LocalizedStringResource] - let note: LocalizedStringResource? - var id: String { path } - - init(path: String, steps: [LocalizedStringResource], note: LocalizedStringResource? = nil) { - self.path = path - self.steps = steps - self.note = note - } - - func hash(into hasher: inout Hasher) { - id.hash(into: &hasher) - } - } - - var id: ID - var tool: LocalizedStringResource - var steps: [StepGroup] - var requiresSecret: Bool - var website: URL? - - init( - tool: LocalizedStringResource, - configPath: String, - configText: LocalizedStringResource, - requiresSecret: Bool = false, - website: URL? = nil, - note: LocalizedStringResource? = nil - ) { - self.id = .tool(String(localized: tool)) - self.tool = tool - self.steps = [StepGroup(path: configPath, steps: [configText], note: note)] - self.requiresSecret = requiresSecret - self.website = website - } - - init( - tool: LocalizedStringResource, - steps: [StepGroup], - requiresSecret: Bool = false, - website: URL? = nil - ) { - self.id = .tool(String(localized: tool)) - self.tool = tool - self.steps = steps - self.requiresSecret = true - self.website = website - } - - init(_ name: LocalizedStringResource, id: ID) { - self.id = id - tool = name - steps = [] - requiresSecret = false - } - - func hash(into hasher: inout Hasher) { - id.hash(into: &hasher) - } - - enum ID: Identifiable, Hashable { - case gettingStarted - case tool(String) - case otherShell - case otherApp - - var id: String { - switch self { - case .gettingStarted: - "getting_started" - case .tool(let name): - name - case .otherShell: - "other_shell" - case .otherApp: - "other_app" - } - } - } - -} - -#Preview { - IntegrationsView() - .frame(height: 500) -} diff --git a/Sources/Secretive/Views/NoStoresView.swift b/Sources/Secretive/Views/NoStoresView.swift deleted file mode 100644 index 497138d..0000000 --- a/Sources/Secretive/Views/NoStoresView.swift +++ /dev/null @@ -1,24 +0,0 @@ -import SwiftUI - -struct NoStoresView: View { - - var body: some View { - VStack { - Text(.noSecureStorageTitle) - .bold() - Text(.noSecureStorageDescription) - Link(.noSecureStorageYubicoLink, destination: URL(string: "https://www.yubico.com/products/compare-yubikey-5-series/")!) - }.padding() - } - -} - -#if DEBUG - -struct NoStoresView_Previews: PreviewProvider { - static var previews: some View { - NoStoresView() - } -} - -#endif diff --git a/Sources/Secretive/Views/CreateSecretView.swift b/Sources/Secretive/Views/Secrets/CreateSecretView.swift similarity index 100% rename from Sources/Secretive/Views/CreateSecretView.swift rename to Sources/Secretive/Views/Secrets/CreateSecretView.swift diff --git a/Sources/Secretive/Views/DeleteSecretView.swift b/Sources/Secretive/Views/Secrets/DeleteSecretView.swift similarity index 100% rename from Sources/Secretive/Views/DeleteSecretView.swift rename to Sources/Secretive/Views/Secrets/DeleteSecretView.swift diff --git a/Sources/Secretive/Views/EditSecretView.swift b/Sources/Secretive/Views/Secrets/EditSecretView.swift similarity index 100% rename from Sources/Secretive/Views/EditSecretView.swift rename to Sources/Secretive/Views/Secrets/EditSecretView.swift diff --git a/Sources/Secretive/Views/SecretDetailView.swift b/Sources/Secretive/Views/Secrets/SecretDetailView.swift similarity index 100% rename from Sources/Secretive/Views/SecretDetailView.swift rename to Sources/Secretive/Views/Secrets/SecretDetailView.swift diff --git a/Sources/Secretive/Views/SecretListItemView.swift b/Sources/Secretive/Views/Secrets/SecretListItemView.swift similarity index 100% rename from Sources/Secretive/Views/SecretListItemView.swift rename to Sources/Secretive/Views/Secrets/SecretListItemView.swift diff --git a/Sources/Secretive/Views/StoreListView.swift b/Sources/Secretive/Views/Secrets/StoreListView.swift similarity index 100% rename from Sources/Secretive/Views/StoreListView.swift rename to Sources/Secretive/Views/Secrets/StoreListView.swift diff --git a/Sources/Secretive/Views/ActionButtonStyle.swift b/Sources/Secretive/Views/Styles/ActionButtonStyle.swift similarity index 100% rename from Sources/Secretive/Views/ActionButtonStyle.swift rename to Sources/Secretive/Views/Styles/ActionButtonStyle.swift diff --git a/Sources/Secretive/Views/ErrorStyle.swift b/Sources/Secretive/Views/Styles/ErrorStyle.swift similarity index 100% rename from Sources/Secretive/Views/ErrorStyle.swift rename to Sources/Secretive/Views/Styles/ErrorStyle.swift diff --git a/Sources/Secretive/Views/ToolbarButtonStyle.swift b/Sources/Secretive/Views/Styles/ToolbarButtonStyle.swift similarity index 100% rename from Sources/Secretive/Views/ToolbarButtonStyle.swift rename to Sources/Secretive/Views/Styles/ToolbarButtonStyle.swift diff --git a/Sources/Secretive/Views/AgentStatusView.swift b/Sources/Secretive/Views/Views/AgentStatusView.swift similarity index 100% rename from Sources/Secretive/Views/AgentStatusView.swift rename to Sources/Secretive/Views/Views/AgentStatusView.swift diff --git a/Sources/Secretive/Views/CopyableView.swift b/Sources/Secretive/Views/Views/CopyableView.swift similarity index 100% rename from Sources/Secretive/Views/CopyableView.swift rename to Sources/Secretive/Views/Views/CopyableView.swift diff --git a/Sources/Secretive/Views/UpdateView.swift b/Sources/Secretive/Views/Views/UpdateView.swift similarity index 100% rename from Sources/Secretive/Views/UpdateView.swift rename to Sources/Secretive/Views/Views/UpdateView.swift