diff --git a/Chronos.xcodeproj/project.pbxproj b/Chronos.xcodeproj/project.pbxproj index deccd08..dcbadb1 100644 --- a/Chronos.xcodeproj/project.pbxproj +++ b/Chronos.xcodeproj/project.pbxproj @@ -52,8 +52,8 @@ 6B9D74652C14ADDC008E6582 /* StateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B9D74642C14ADDC008E6582 /* StateService.swift */; }; 6B9D74682C1553A1008E6582 /* SecureBytes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B9D74672C1553A1008E6582 /* SecureBytes.swift */; }; 6BA5DA5C2B9E94F8009908E5 /* SecureEnclaveService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA5DA5B2B9E94F8009908E5 /* SecureEnclaveService.swift */; }; - 6BB37D7A2C456B32008DA122 /* ImportSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB37D792C456B32008DA122 /* ImportSelectionView.swift */; }; - 6BB37D7C2C457059008DA122 /* SourceSelectedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB37D7B2C457059008DA122 /* SourceSelectedView.swift */; }; + 6BB37D7A2C456B32008DA122 /* ImportSourceListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB37D792C456B32008DA122 /* ImportSourceListView.swift */; }; + 6BB37D7C2C457059008DA122 /* ImportSourceDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB37D7B2C457059008DA122 /* ImportSourceDetailView.swift */; }; 6BB37D7E2C466B60008DA122 /* ImportService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB37D7D2C466B60008DA122 /* ImportService.swift */; }; 6BB37D812C46C751008DA122 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 6BB37D802C46C751008DA122 /* SwiftyJSON */; }; 6BB37D832C480B07008DA122 /* ImportConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB37D822C480B07008DA122 /* ImportConfirmationView.swift */; }; @@ -121,8 +121,8 @@ 6B9D74642C14ADDC008E6582 /* StateService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateService.swift; sourceTree = ""; }; 6B9D74672C1553A1008E6582 /* SecureBytes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBytes.swift; sourceTree = ""; }; 6BA5DA5B2B9E94F8009908E5 /* SecureEnclaveService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureEnclaveService.swift; sourceTree = ""; }; - 6BB37D792C456B32008DA122 /* ImportSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportSelectionView.swift; sourceTree = ""; }; - 6BB37D7B2C457059008DA122 /* SourceSelectedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSelectedView.swift; sourceTree = ""; }; + 6BB37D792C456B32008DA122 /* ImportSourceListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportSourceListView.swift; sourceTree = ""; }; + 6BB37D7B2C457059008DA122 /* ImportSourceDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportSourceDetailView.swift; sourceTree = ""; }; 6BB37D7D2C466B60008DA122 /* ImportService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportService.swift; sourceTree = ""; }; 6BB37D822C480B07008DA122 /* ImportConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportConfirmationView.swift; sourceTree = ""; }; 6BB37D842C483066008DA122 /* ImportFailureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportFailureView.swift; sourceTree = ""; }; @@ -362,8 +362,8 @@ 6BB37D782C4569E9008DA122 /* Import */ = { isa = PBXGroup; children = ( - 6BB37D792C456B32008DA122 /* ImportSelectionView.swift */, - 6BB37D7B2C457059008DA122 /* SourceSelectedView.swift */, + 6BB37D792C456B32008DA122 /* ImportSourceListView.swift */, + 6BB37D7B2C457059008DA122 /* ImportSourceDetailView.swift */, 6BB37D822C480B07008DA122 /* ImportConfirmationView.swift */, 6BB37D842C483066008DA122 /* ImportFailureView.swift */, ); @@ -591,7 +591,7 @@ 6B65F80B2C21C2EA00AC8606 /* VaultSetupView.swift in Sources */, 6B5E41CE2BD790F80045DBC6 /* EncryptedToken.swift in Sources */, 6B4B48F32BD7BB3C007D357D /* Token.swift in Sources */, - 6BB37D7C2C457059008DA122 /* SourceSelectedView.swift in Sources */, + 6BB37D7C2C457059008DA122 /* ImportSourceDetailView.swift in Sources */, 6BF53E572C32EF7700356461 /* EncryptedExportConfirmPasswordView.swift in Sources */, 6B842DD52BE33E2E00056F0F /* RestoreBackupView.swift in Sources */, 6BA5DA5C2B9E94F8009908E5 /* SecureEnclaveService.swift in Sources */, @@ -603,7 +603,7 @@ 6BF53E522C317F1C00356461 /* ExportSelectionView.swift in Sources */, 6BB37D7E2C466B60008DA122 /* ImportService.swift in Sources */, 6B65F8082C21C17200AC8606 /* VaultSelectionView.swift in Sources */, - 6BB37D7A2C456B32008DA122 /* ImportSelectionView.swift in Sources */, + 6BB37D7A2C456B32008DA122 /* ImportSourceListView.swift in Sources */, 6B3962A02BF6423B000410B0 /* CryptoService.swift in Sources */, 6B27317A2B53E23800F30621 /* UpdateTokenView.swift in Sources */, 6B27317C2B53F0B200F30621 /* TokenRowView.swift in Sources */, diff --git a/Chronos/App/Tabs/Settings/Import/ImportConfirmationView.swift b/Chronos/App/Tabs/Settings/Import/ImportConfirmationView.swift index 48ca114..7fd1ad7 100644 --- a/Chronos/App/Tabs/Settings/Import/ImportConfirmationView.swift +++ b/Chronos/App/Tabs/Settings/Import/ImportConfirmationView.swift @@ -7,8 +7,8 @@ struct ImportConfirmationView: View { @EnvironmentObject var importNav: ExportNavigation - let cryptoService = Container.shared.cryptoService() - let vaultService = Container.shared.vaultService() + private let cryptoService = Container.shared.cryptoService() + private let vaultService = Container.shared.vaultService() var body: some View { VStack { @@ -21,41 +21,41 @@ struct ImportConfirmationView: View { Spacer() - Button { - for token in tokens { - let newEncToken = cryptoService.encryptToken(token: token) - vaultService.insertEncryptedToken(newEncToken) - } - - importNav.showSheet = false - - AlertKitAPI.present( - title: "Successfully imported tokens", - icon: .done, - style: .iOS17AppleMusic, - haptic: .success - ) - } label: { + Button(action: importTokens) { Text("Import") .bold() - .frame(minWidth: 0, maxWidth: .infinity) + .frame(maxWidth: .infinity) .frame(height: 32) } .buttonStyle(.bordered) - Button { - importNav.showSheet = false - } label: { + Button(action: { importNav.showSheet = false }) { Text("Cancel") .bold() - .frame(minWidth: 0, maxWidth: .infinity) + .frame(maxWidth: .infinity) .frame(height: 32) } .buttonStyle(.borderless) } .navigationTitle("Confirm Import") - .padding([.horizontal], 24) - .padding([.bottom], 32) + .padding(.horizontal, 24) + .padding(.bottom, 32) .navigationBarTitleDisplayMode(.inline) } + + private func importTokens() { + for token in tokens { + let newEncToken = cryptoService.encryptToken(token: token) + vaultService.insertEncryptedToken(newEncToken) + } + + importNav.showSheet = false + + AlertKitAPI.present( + title: "Successfully imported tokens", + icon: .done, + style: .iOS17AppleMusic, + haptic: .success + ) + } } diff --git a/Chronos/App/Tabs/Settings/Import/ImportSourceDetailView.swift b/Chronos/App/Tabs/Settings/Import/ImportSourceDetailView.swift new file mode 100644 index 0000000..48d6de0 --- /dev/null +++ b/Chronos/App/Tabs/Settings/Import/ImportSourceDetailView.swift @@ -0,0 +1,73 @@ +import Factory +import SwiftUI + +struct ImportSourceDetailView: View { + @State var importSource: ImportSource + + @State private var showFileImporter = false + @State private var showImportConfirmation = false + @State private var tokens: [Token]? + + @EnvironmentObject private var importNav: ExportNavigation + + private let importService = Container.shared.importService() + + var body: some View { + VStack { + Image(systemName: "square.and.arrow.down") + .font(.system(size: 44)) + .padding(.bottom, 16) + + Text(importSource.desc) + .multilineTextAlignment(.center) + + Spacer() + + Button(action: { showFileImporter = true }) { + Text("Select file") + .bold() + .frame(maxWidth: .infinity) + .frame(height: 32) + } + .buttonStyle(.bordered) + .fileImporter( + isPresented: $showFileImporter, + allowedContentTypes: [.json], + allowsMultipleSelection: false, + onCompletion: handleFileImport + ) + .sheet(isPresented: $showImportConfirmation) { + NavigationStack { + if let tokens = tokens { + ImportConfirmationView(tokens: tokens) + } else { + ImportFailureView() + } + } + } + + Button(action: { importNav.showSheet = false }) { + Text("Cancel") + .bold() + .frame(maxWidth: .infinity) + .frame(height: 32) + } + .buttonStyle(.borderless) + } + .padding(.horizontal, 24) + .padding(.bottom, 32) + .navigationTitle("Import from \(importSource.name)") + } + + private func handleFileImport(result: Result<[URL], Error>) { + switch result { + case let .success(fileUrls): + if let fileUrl = fileUrls.first { + tokens = importService.importTokens(importSource: importSource, url: fileUrl) + showImportConfirmation = true + } + case let .failure(error): + print(error) + } + } +} diff --git a/Chronos/App/Tabs/Settings/Import/ImportSelectionView.swift b/Chronos/App/Tabs/Settings/Import/ImportSourceListView.swift similarity index 92% rename from Chronos/App/Tabs/Settings/Import/ImportSelectionView.swift rename to Chronos/App/Tabs/Settings/Import/ImportSourceListView.swift index bd6b47a..ebdfbd6 100644 --- a/Chronos/App/Tabs/Settings/Import/ImportSelectionView.swift +++ b/Chronos/App/Tabs/Settings/Import/ImportSourceListView.swift @@ -6,7 +6,7 @@ struct ImportSource: Identifiable { var desc: String } -struct ImportSelectionView: View { +struct ImportSourceListView: View { let importSources: [ImportSource] = [ ImportSource(id: "chronos", name: "Chronos", desc: "Export your tokens from Chronos to an unencrypted JSON file, then select the file below."), ImportSource(id: "raivo", name: "Raivo", desc: "Export your tokens from Raivo using \"Export OTPs to ZIP archive\" option. Extract the JSON file from the archive, then select the file below."), @@ -23,7 +23,7 @@ struct ImportSelectionView: View { List(importSources) { importSource in NavigationLink { - SourceSelectedView(importSource: importSource) + ImportSourceDetailView(importSource: importSource) } label: { Text(importSource.name) .fontWeight(.semibold) diff --git a/Chronos/App/Tabs/Settings/Import/SourceSelectedView.swift b/Chronos/App/Tabs/Settings/Import/SourceSelectedView.swift deleted file mode 100644 index a6de012..0000000 --- a/Chronos/App/Tabs/Settings/Import/SourceSelectedView.swift +++ /dev/null @@ -1,68 +0,0 @@ -import Factory -import SwiftUI - -struct SourceSelectedView: View { - @State var importSource: ImportSource - @State var showFileImporter: Bool = false - @State var showImportConfirmation: Bool = false - @State var tokens: [Token]? - - @EnvironmentObject var importNav: ExportNavigation - - let importService = Container.shared.importService() - - var body: some View { - VStack { - Image(systemName: "square.and.arrow.down") - .font(.system(size: 44)) - .padding(.bottom, 16) - - Text(importSource.desc) - .multilineTextAlignment(.center) - - Spacer() - - Button { - showFileImporter = true - } label: { - Text("Select file") - .bold() - .frame(minWidth: 0, maxWidth: .infinity) - .frame(height: 32) - } - .buttonStyle(.bordered) - .fileImporter(isPresented: $showFileImporter, allowedContentTypes: [.json], allowsMultipleSelection: false, onCompletion: { results in - switch results { - case let .success(fileurls): - let fileUrl = fileurls.first - tokens = importService.importTokens(importSource: importSource, url: fileUrl!) - showImportConfirmation = true - case let .failure(error): - print(error) - } - }) - - Button { - importNav.showSheet = false - } label: { - Text("Cancel") - .bold() - .frame(minWidth: 0, maxWidth: .infinity) - .frame(height: 32) - } - .buttonStyle(.borderless) - } - .padding([.horizontal], 24) - .padding([.bottom], 32) - .navigationTitle("Import from \(importSource.name)") - .sheet(isPresented: $showImportConfirmation, content: { - NavigationStack { - if let tokens = tokens { - ImportConfirmationView(tokens: tokens) - } else { - ImportFailureView() - } - } - }) - } -} diff --git a/Chronos/App/Tabs/Settings/SettingsTab.swift b/Chronos/App/Tabs/Settings/SettingsTab.swift index 97a37ac..e672448 100644 --- a/Chronos/App/Tabs/Settings/SettingsTab.swift +++ b/Chronos/App/Tabs/Settings/SettingsTab.swift @@ -67,7 +67,7 @@ struct SettingsTab: View { .frame(maxWidth: .infinity) } .sheet(isPresented: $importNav.showSheet, content: { - ImportSelectionView() + ImportSourceListView() .environmentObject(importNav) }) }