Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into autofill/anya-logins-…
Browse files Browse the repository at this point in the history
…to-passwords

# Conflicts:
#	LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift
  • Loading branch information
amddg44 committed Dec 14, 2023
2 parents 750a488 + 9dc6d12 commit 883f457
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 62 deletions.
36 changes: 23 additions & 13 deletions DuckDuckGo/Preferences/Model/SyncPreferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel {
}

@Published var shouldShowErrorMessage: Bool = false
@Published private(set) var errorMessage: String?
@Published private(set) var syncErrorMessage: SyncErrorMessage?

@Published var isCreatingAccount: Bool = false

Expand Down Expand Up @@ -120,7 +120,7 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel {
}
.store(in: &cancellables)

$errorMessage
$syncErrorMessage
.map { $0 != nil }
.receive(on: DispatchQueue.main)
.assign(to: \.shouldShowErrorMessage, onWeaklyHeld: self)
Expand Down Expand Up @@ -179,7 +179,8 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel {
UserDefaults.standard.set(false, forKey: UserDefaultsWrapper<Bool>.Key.syncBookmarksPaused.rawValue)
UserDefaults.standard.set(false, forKey: UserDefaultsWrapper<Bool>.Key.syncCredentialsPaused.rawValue)
} catch {
errorMessage = String(describing: error)
managementDialogModel.syncErrorMessage
= SyncErrorMessage(type: .unableToDeleteData, description: error.localizedDescription)
}
}
}
Expand Down Expand Up @@ -328,7 +329,8 @@ extension SyncPreferences: ManagementDialogModelDelegate {
UserDefaults.standard.set(false, forKey: UserDefaultsWrapper<Bool>.Key.syncBookmarksPaused.rawValue)
UserDefaults.standard.set(false, forKey: UserDefaultsWrapper<Bool>.Key.syncCredentialsPaused.rawValue)
} catch {
managementDialogModel.errorMessage = String(describing: error)
managementDialogModel.syncErrorMessage
= SyncErrorMessage(type: .unableToDeleteData, description: error.localizedDescription)
}
}
}
Expand All @@ -338,9 +340,11 @@ extension SyncPreferences: ManagementDialogModelDelegate {
do {
self.devices = []
let devices = try await syncService.updateDeviceName(name)
managementDialogModel.endFlow()
mapDevices(devices)
} catch {
managementDialogModel.errorMessage = String(describing: error)
managementDialogModel.syncErrorMessage
= SyncErrorMessage(type: .unableToUpdateDeviceName, description: error.localizedDescription)
}
}
}
Expand Down Expand Up @@ -380,7 +384,8 @@ extension SyncPreferences: ManagementDialogModelDelegate {
Pixel.fire(.syncSignupDirect)
presentDialog(for: .saveRecoveryCode(recoveryCode ?? ""))
} catch {
managementDialogModel.errorMessage = String(describing: error)
managementDialogModel.syncErrorMessage
= SyncErrorMessage(type: .unableToSync, description: error.localizedDescription)
}
}
}
Expand All @@ -405,7 +410,8 @@ extension SyncPreferences: ManagementDialogModelDelegate {
}
} catch {
if syncService.account == nil {
managementDialogModel.errorMessage = String(describing: error)
managementDialogModel.syncErrorMessage
= SyncErrorMessage(type: .unableToSync, description: error.localizedDescription)
}
}
}
Expand All @@ -420,7 +426,8 @@ extension SyncPreferences: ManagementDialogModelDelegate {
Task { @MainActor in
do {
guard let syncCode = try? SyncCode.decodeBase64String(recoveryCode) else {
managementDialogModel.errorMessage = "Invalid code"
managementDialogModel.syncErrorMessage
= SyncErrorMessage(type: .invalidCode, description: "")
return
}
presentDialog(for: .prepareToSync)
Expand All @@ -447,11 +454,13 @@ extension SyncPreferences: ManagementDialogModelDelegate {

// The UI will update when the devices list changes.
} else {
managementDialogModel.errorMessage = "Invalid code"
managementDialogModel.syncErrorMessage
= SyncErrorMessage(type: .invalidCode, description: "")
return
}
} catch {
managementDialogModel.errorMessage = String(describing: error)
managementDialogModel.syncErrorMessage
= SyncErrorMessage(type: .unableToSync, description: error.localizedDescription)
}
}
}
Expand Down Expand Up @@ -481,7 +490,8 @@ extension SyncPreferences: ManagementDialogModelDelegate {
do {
try data.writeFileWithProgress(to: location)
} catch {
managementDialogModel.errorMessage = String(describing: error)
managementDialogModel.syncErrorMessage
= SyncErrorMessage(type: .unableCreateRecoveryPDF, description: error.localizedDescription)
}
}

Expand All @@ -495,8 +505,8 @@ extension SyncPreferences: ManagementDialogModelDelegate {
refreshDevices()
managementDialogModel.endFlow()
} catch {
managementDialogModel.errorMessage = String(describing: error)
}
managementDialogModel.syncErrorMessage
= SyncErrorMessage(type: .unableToRemoveDevice, description: error.localizedDescription) }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -777,9 +777,9 @@ final class PasswordManagementViewController: NSViewController {
}

menu.items = [
createMenuItem(title: UserText.pmNewCard, action: #selector(createNewCreditCard), imageName: "CreditCardGlyph"),
createMenuItem(title: UserText.pmNewLogin, action: #selector(createNewLogin), imageName: "LoginGlyph"),
createMenuItem(title: UserText.pmNewIdentity, action: #selector(createNewIdentity), imageName: "IdentityGlyph")
createMenuItem(title: UserText.pmNewIdentity, action: #selector(createNewIdentity), imageName: "IdentityGlyph"),
createMenuItem(title: UserText.pmNewCard, action: #selector(createNewCreditCard), imageName: "CreditCardGlyph"),
]

return menu
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ public final class ManagementDialogModel: ObservableObject {
public var codeToDisplay: String?

@Published public var shouldShowErrorMessage: Bool = false
@Published public var errorMessage: String?
@Published public var syncErrorMessage: SyncErrorMessage?

public weak var delegate: ManagementDialogModelDelegate?

public init() {
shouldShowErrorMessageCancellable = $errorMessage
shouldShowErrorMessageCancellable = $syncErrorMessage
.map { $0 != nil }
.receive(on: DispatchQueue.main)
.sink { [weak self] hasError in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public protocol ManagementViewModel: ObservableObject {
var isSyncEnabled: Bool { get }
var isCreatingAccount: Bool { get }
var shouldShowErrorMessage: Bool { get set }
var errorMessage: String? { get }
var syncErrorMessage: SyncErrorMessage? { get }
var isSyncBookmarksPaused: Bool { get }
var isSyncCredentialsPaused: Bool { get }

Expand All @@ -48,3 +48,49 @@ public protocol ManagementViewModel: ObservableObject {
func recoverDataPressed()
func turnOffSyncPressed()
}

public enum SyncErrorType {
case unableToSync
case unableToGetDevices
case unableToUpdateDeviceName
case unableToTurnSyncOff
case unableToDeleteData
case unableToRemoveDevice
case invalidCode
case unableCreateRecoveryPDF

var title: String {
return UserText.syncErrorAlertTitle
}

var description: String {
switch self {
case .unableToSync:
return UserText.unableToSyncDescription
case .unableToGetDevices:
return UserText.unableToGetDevicesDescription
case .unableToUpdateDeviceName:
return UserText.unableToUpdateDeviceNameDescription
case .unableToTurnSyncOff:
return UserText.unableToTurnSyncOffDescription
case .unableToDeleteData:
return UserText.unableToDeleteDataDescription
case .unableToRemoveDevice:
return UserText.unableToRemoveDeviceDescription
case .invalidCode:
return UserText.invalidCodeDescription
case .unableCreateRecoveryPDF:
return UserText.unableCreateRecoveryPdfDescription
}
}
}

public struct SyncErrorMessage {
var type: SyncErrorType
var errorDescription: String

public init(type: SyncErrorType, description: String) {
self.type = type
self.errorDescription = description
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ struct DeviceDetailsView: View {
let device: SyncDevice

@State var deviceName = ""
@State private var isLoading = false

var canSave: Bool {
!deviceName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
Expand All @@ -35,39 +36,42 @@ struct DeviceDetailsView: View {
func submit() {
guard canSave else { return }
model.delegate?.updateDeviceName(deviceName)
model.endFlow()
}

var body: some View {
SyncDialog {
VStack(spacing: 20) {
SyncUIViews.TextHeader(text: UserText.deviceDetailsTitle)

HStack {
Text(UserText.deviceDetailsLabel)
.font(.system(size: 13, weight: .semibold))
TextField(UserText.deviceDetailsPrompt, text: $deviceName, onCommit: submit)
.textFieldStyle(RoundedBorderTextFieldStyle())
if isLoading {
ProgressView()
.padding()
} else {
SyncDialog {
VStack(spacing: 20) {
SyncUIViews.TextHeader(text: UserText.deviceDetailsTitle)
HStack {
Text(UserText.deviceDetailsLabel)
.font(.system(size: 13, weight: .semibold))
TextField(UserText.deviceDetailsPrompt, text: $deviceName, onCommit: submit)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding(.horizontal, 10)
.padding(.vertical, 14.5)
.roundedBorder()
}
.padding(.horizontal, 10)
.padding(.vertical, 14.5)
.roundedBorder()
}
} buttons: {
Button(UserText.cancel) {
model.endFlow()
} buttons: {
Button(UserText.cancel) {
model.endFlow()
}
.buttonStyle(DismissActionButtonStyle())
Button(UserText.ok) {
submit()
isLoading = true
}
.disabled(!canSave)
.buttonStyle(DefaultActionButtonStyle(enabled: canSave))
}
.buttonStyle(DismissActionButtonStyle())
Button(UserText.ok) {
submit()
.frame(width: 360, height: 178)
.onAppear {
deviceName = device.name
}
.disabled(!canSave)
.buttonStyle(DefaultActionButtonStyle(enabled: canSave))

}
.frame(width: 360, height: 178)
.onAppear {
deviceName = device.name
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,14 @@ struct SaveRecoveryPDFView: View {
SyncUIViews.TextDetailMultiline(text: UserText.recoveryPDFExplanation)
}
VStack(alignment: .leading, spacing: 20) {
HStack {
QRCode(string: code, size: CGSize(width: 56, height: 56))
Text(code)
.kerning(2)
.multilineTextAlignment(.leading)
.lineSpacing(5)
.lineLimit(3)
.font(Font.custom("SF Mono", size: 12))
.fixedSize(horizontal: false, vertical: true)
}
.frame(width: 340)
Text(code)
.kerning(2)
.multilineTextAlignment(.leading)
.lineSpacing(5)
.lineLimit(3)
.font(Font.custom("SF Mono", size: 12))
.fixedSize(horizontal: false, vertical: true)
.frame(width: 340)
HStack {
Button {
viewModel.delegate?.copyCode()
Expand Down
21 changes: 18 additions & 3 deletions LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementDialog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ public struct ManagementDialog: View {
@ObservedObject public var model: ManagementDialogModel
@ObservedObject public var recoveryCodeModel: RecoveryCodeViewModel

var errorTitle: String {
return model.syncErrorMessage?.type.title ?? "Sync Error"
}

var errorDescription: String {
guard let typeDescription = model.syncErrorMessage?.type.description,
let errorDescription = model.syncErrorMessage?.errorDescription
else {
return ""
}
return typeDescription + "\n" + errorDescription
}

public init(model: ManagementDialogModel, recoveryCodeModel: RecoveryCodeViewModel = .init()) {
self.model = model
self.recoveryCodeModel = recoveryCodeModel
Expand All @@ -45,9 +58,11 @@ public struct ManagementDialog: View {
content
.alert(isPresented: $model.shouldShowErrorMessage) {
Alert(
title: Text("Unable to turn on Sync"),
message: Text(model.errorMessage ?? "An error occurred"),
dismissButton: .default(Text(UserText.ok))
title: Text(errorTitle),
message: Text(errorDescription),
dismissButton: .default(Text(UserText.ok)) {
model.endFlow()
}
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,5 @@ public struct ManagementView<ViewModel>: View where ViewModel: ManagementViewMod
}
}
}
.alert(isPresented: $model.shouldShowErrorMessage) {
Alert(title: Text("Unable to turn on Sync"), message: Text(model.errorMessage ?? "An error occurred"), dismissButton: .default(Text(UserText.ok)))
}
}
}
9 changes: 9 additions & 0 deletions LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,15 @@ enum UserText {
static let credentialsLimitExceededDescription = NSLocalizedString("prefrences.sync.credentials-limit-exceeded-description", value: "Logins limit exceeded. Delete some to resume syncing.", comment: "Description for sync credentials limits exceeded warning")
static let bookmarksLimitExceededAction = NSLocalizedString("prefrences.sync.bookmarks-limit-exceeded-action", value: "Manage Bookmarks", comment: "Button title for sync bookmarks limits exceeded warning to manage bookmarks")
static let credentialsLimitExceededAction = NSLocalizedString("prefrences.sync.credentials-limit-exceeded-action", value: "Manage passwords...", comment: "Button title for sync credentials limits exceeded warning to manage logins")
static let syncErrorAlertTitle = NSLocalizedString("alert.sync-error", value: "Sync Error", comment: "Title for sync error alert")
static let unableToSyncDescription = NSLocalizedString("alert.unable-to-sync-description", value: "Unable to sync.", comment: "Description for unable to sync error")
static let unableToGetDevicesDescription = NSLocalizedString("alert.unable-to-get-devices-description", value: "Unable to retrieve the list of connected devices.", comment: "Description for unable to get devices error")
static let unableToUpdateDeviceNameDescription = NSLocalizedString("alert.unable-to-update-device-name-description", value: "Unable to update the name of the device.", comment: "Description for unable to update device name error")
static let unableToTurnSyncOffDescription = NSLocalizedString("alert.unable-to-turn-sync-off-description", value: "Unable to turn sync off.", comment: "Description for unable to turn sync off error")
static let unableToDeleteDataDescription = NSLocalizedString("alert.unable-to-delete-data-description", value: "Unable to delete data on the server.", comment: "Description for unable to delete data error")
static let unableToRemoveDeviceDescription = NSLocalizedString("alert.unable-to-remove-device-description", value: "Unable to remove the specified device from the synchronized devices.", comment: "Description for unable to remove device error")
static let invalidCodeDescription = NSLocalizedString("alert.invalid-code-description", value: "The code used is invalid.", comment: "Description for invalid code error")
static let unableCreateRecoveryPdfDescription = NSLocalizedString("alert.unable-to-create-recovery-pdf-description", value: "There was a problem creating the recovery PDF.", comment: "Description for unable to create recovery pdf error")

static let fetchFaviconsOnboardingTitle = NSLocalizedString("prefrences.sync.fetch-favicons-onboarding-title", value: "Download Missing Icons?", comment: "Title for fetch favicons onboarding dialog")
static let fetchFaviconsOnboardingMessage = NSLocalizedString("prefrences.sync.fetch-favicons-onboarding-message", value: "Do you want this device to automatically download icons for any new bookmarks synced from your other devices? This will expose the download to your network any time a bookmark is synced.", comment: "Text for fetch favicons onboarding dialog")
Expand Down

0 comments on commit 883f457

Please sign in to comment.