-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement encrypted export (#22)
* WIP * Added encrypted export
- Loading branch information
1 parent
ecec243
commit e90b640
Showing
8 changed files
with
429 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
128 changes: 128 additions & 0 deletions
128
Chronos/App/Tabs/Settings/Export/EncryptedExport/EncryptedExportConfirmPasswordView.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import Factory | ||
import SwiftUI | ||
|
||
struct EncryptedExportConfirmPasswordView: View { | ||
let exportService = Container.shared.exportService() | ||
@StateObject private var viewModel = ConfirmPasswordViewModel() | ||
|
||
@EnvironmentObject var exportNav: ExportNavigation | ||
|
||
@State var password: String | ||
@State private var verifyPassword: String = "" | ||
@State private var passwordInvalidMsg: String = "" | ||
@State private var isPasswordValid: Bool = false | ||
@State private var exportDisabled: Bool = false | ||
|
||
@FocusState private var focusedField: FocusedField? | ||
|
||
var body: some View { | ||
VStack { | ||
Spacer() | ||
|
||
Text("Confirm password") | ||
.padding(.top, 24) | ||
Group { | ||
SecureField("", text: $verifyPassword) | ||
.multilineTextAlignment(.center) | ||
.background(Color.clear) | ||
.focused($focusedField, equals: .verifyPassword) | ||
.submitLabel(.done) | ||
.onSubmit { | ||
doSubmit() | ||
} | ||
.onChange(of: verifyPassword) { _, _ in | ||
validatePasswords() | ||
} | ||
.onAppear { | ||
focusedField = .verifyPassword | ||
} | ||
} | ||
.frame(height: 48) | ||
.background(Color(.systemGray6)) | ||
.cornerRadius(8) | ||
|
||
if !isPasswordValid { | ||
Text(passwordInvalidMsg) | ||
.multilineTextAlignment(.center) | ||
.foregroundStyle(.red) | ||
.font(.subheadline) | ||
.padding(.top, 4) | ||
} | ||
|
||
Spacer() | ||
} | ||
.padding([.horizontal], 24) | ||
.navigationTitle("Encrypted Export") | ||
.background(Color(red: 0.04, green: 0, blue: 0.11)) | ||
.navigationBarTitleDisplayMode(.inline) | ||
.navigationBarItems(trailing: | ||
Button { | ||
doSubmit() | ||
} label: { | ||
Text("Export") | ||
} | ||
.disabled(!isPasswordValid || exportDisabled) | ||
.sheet(isPresented: $viewModel.showEncryptedExportSheet) { | ||
if let fileUrl = viewModel.exportFileUrl { | ||
ActivityView(fileUrl: fileUrl) | ||
.presentationDetents([.medium, .large]) | ||
.presentationDragIndicator(Visibility.hidden) | ||
.onDisappear { | ||
exportNav.showSheet = false | ||
} | ||
} else { | ||
VStack { | ||
Image(systemName: "xmark.circle") | ||
.fontWeight(.light) | ||
.font(.system(size: 64)) | ||
.padding(.bottom, 8) | ||
Text("An error occurred while during the export process") | ||
} | ||
} | ||
} | ||
) | ||
} | ||
|
||
func doSubmit() { | ||
if !isPasswordValid { | ||
UINotificationFeedbackGenerator().notificationOccurred(.error) | ||
exportDisabled = false | ||
return | ||
} | ||
|
||
exportDisabled = true | ||
viewModel.exportToEncryptedZip(password: password) | ||
} | ||
|
||
private func validatePasswords() { | ||
if password.count < 10 { | ||
isPasswordValid = false | ||
passwordInvalidMsg = "Passwords must be at least 10 characters long" | ||
} else if password != verifyPassword { | ||
isPasswordValid = false | ||
passwordInvalidMsg = "Passwords do not match" | ||
} else { | ||
isPasswordValid = true | ||
passwordInvalidMsg = "" | ||
} | ||
} | ||
} | ||
|
||
class ConfirmPasswordViewModel: ObservableObject { | ||
@Published var exportFileUrl: URL? | ||
@Published var showEncryptedExportSheet: Bool = false | ||
|
||
let exportService = Container.shared.exportService() | ||
|
||
func exportToEncryptedZip(password: String) { | ||
guard let fileUrl = exportService.exportToEncryptedZip(password: password) else { | ||
exportFileUrl = nil | ||
showEncryptedExportSheet = false | ||
|
||
return | ||
} | ||
|
||
exportFileUrl = fileUrl | ||
showEncryptedExportSheet = true | ||
} | ||
} |
79 changes: 79 additions & 0 deletions
79
Chronos/App/Tabs/Settings/Export/EncryptedExport/EncryptedExportPasswordView.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import Factory | ||
import SwiftUI | ||
|
||
struct EncryptedExportPasswordView: View { | ||
@State private var password: String = "" | ||
|
||
@State private var passwordInvalidMsg: String = "" | ||
@State private var isPasswordValid: Bool = false | ||
@State private var navigateToNextPage: Bool = false | ||
|
||
@FocusState private var focusedField: FocusedField? | ||
|
||
var body: some View { | ||
VStack { | ||
Image(systemName: "lock.square") | ||
.font(.system(size: 44)) | ||
.padding(.bottom, 16) | ||
|
||
Text("This password is used to securely encrypt your data. Choose a memorable, random, and unique password with at least 10 characters.") | ||
.fixedSize(horizontal: false, vertical: true) | ||
.multilineTextAlignment(.center) | ||
|
||
Text("Password") | ||
.padding(.top, 32) | ||
|
||
Group { | ||
SecureField("", text: $password) | ||
.multilineTextAlignment(.center) | ||
.background(Color.clear) | ||
.focused($focusedField, equals: .password) | ||
.submitLabel(.next) | ||
.onChange(of: password) { _, _ in | ||
validatePasswords() | ||
} | ||
.onAppear { | ||
focusedField = .password | ||
} | ||
.onSubmit { | ||
doSubmit() | ||
} | ||
} | ||
.frame(height: 48) | ||
.background(Color(.systemGray6)) | ||
.cornerRadius(8) | ||
|
||
Spacer() | ||
} | ||
.padding([.horizontal], 24) | ||
.navigationTitle("Encrypted Export") | ||
.background(Color(red: 0.04, green: 0, blue: 0.11)) | ||
.navigationBarTitleDisplayMode(.inline) | ||
.scrollIndicators(.never) | ||
.navigationBarItems(trailing: Button("Next", action: { | ||
doSubmit() | ||
}).disabled(!isPasswordValid)) | ||
.navigationDestination(isPresented: $navigateToNextPage) { | ||
EncryptedExportConfirmPasswordView(password: password) | ||
} | ||
} | ||
|
||
func doSubmit() { | ||
if !isPasswordValid { | ||
UINotificationFeedbackGenerator().notificationOccurred(.error) | ||
return | ||
} | ||
|
||
navigateToNextPage = true | ||
} | ||
|
||
private func validatePasswords() { | ||
if password.count < 10 { | ||
isPasswordValid = false | ||
passwordInvalidMsg = "Passwords must be at least 10 characters long" | ||
} else { | ||
isPasswordValid = true | ||
passwordInvalidMsg = "" | ||
} | ||
} | ||
} |
Oops, something went wrong.