diff --git a/BitwardenResources/Localizations/en.lproj/Localizable.strings b/BitwardenResources/Localizations/en.lproj/Localizable.strings index ee1902efb3..4476690b83 100644 --- a/BitwardenResources/Localizations/en.lproj/Localizable.strings +++ b/BitwardenResources/Localizations/en.lproj/Localizable.strings @@ -1251,3 +1251,19 @@ "ClearFieldName" = "Clear %1$@"; "SelectDate" = "Select date"; "PremiumRequiredTOTPDescriptionLong" = "Authenticator key (TOTP) is a Premium feature. Your current plan does not include access to this feature."; +/* Toast shown after a login item is saved. */ +"LoginSaved" = "Login saved"; +/* Toast shown after a card item is saved. */ +"CardSaved" = "Card saved"; +/* Toast shown after an identity item is saved. */ +"IdentitySaved" = "Identity saved"; +/* Toast shown after a secure note item is saved. */ +"SecureNoteSaved" = "Secure note saved"; +/* Toast shown after an SSH key item is saved. */ +"SSHKeySaved" = "SSH key saved"; +/* Toast shown after a bank account item is saved. */ +"BankAccountSaved" = "Bank account saved"; +/* Toast shown after a driver's license item is saved. */ +"LicenseSaved" = "License saved"; +/* Toast shown after a passport item is saved. */ +"PassportSaved" = "Passport saved"; diff --git a/BitwardenShared/Core/Vault/Models/Enum/CipherType.swift b/BitwardenShared/Core/Vault/Models/Enum/CipherType.swift index 1e5880a39c..76d589fb37 100644 --- a/BitwardenShared/Core/Vault/Models/Enum/CipherType.swift +++ b/BitwardenShared/Core/Vault/Models/Enum/CipherType.swift @@ -111,4 +111,18 @@ extension CipherType { [.text, .hidden, .boolean] } } + + /// The title of the toast shown after an item of this type is saved. + var savedToastTitle: String { + switch self { + case .bankAccount: Localizations.bankAccountSaved + case .card: Localizations.cardSaved + case .driversLicense: Localizations.licenseSaved + case .identity: Localizations.identitySaved + case .login: Localizations.loginSaved + case .passport: Localizations.passportSaved + case .secureNote: Localizations.secureNoteSaved + case .sshKey: Localizations.sshKeySaved + } + } } diff --git a/BitwardenShared/Core/Vault/Models/Enum/CipherTypeTests.swift b/BitwardenShared/Core/Vault/Models/Enum/CipherTypeTests.swift index e0010891dc..4fd3e73569 100644 --- a/BitwardenShared/Core/Vault/Models/Enum/CipherTypeTests.swift +++ b/BitwardenShared/Core/Vault/Models/Enum/CipherTypeTests.swift @@ -64,4 +64,16 @@ class CipherTypeTests: BitwardenTestCase { XCTAssertEqual(CipherType.passport.rawValue, 8) XCTAssertTrue(CipherType.allCases.contains(.passport)) } + + /// `savedToastTitle` returns the correct values. + func test_savedToastTitle() { + XCTAssertEqual(CipherType.bankAccount.savedToastTitle, Localizations.bankAccountSaved) + XCTAssertEqual(CipherType.card.savedToastTitle, Localizations.cardSaved) + XCTAssertEqual(CipherType.driversLicense.savedToastTitle, Localizations.licenseSaved) + XCTAssertEqual(CipherType.identity.savedToastTitle, Localizations.identitySaved) + XCTAssertEqual(CipherType.login.savedToastTitle, Localizations.loginSaved) + XCTAssertEqual(CipherType.passport.savedToastTitle, Localizations.passportSaved) + XCTAssertEqual(CipherType.secureNote.savedToastTitle, Localizations.secureNoteSaved) + XCTAssertEqual(CipherType.sshKey.savedToastTitle, Localizations.sshKeySaved) + } } diff --git a/BitwardenShared/UI/Vault/Vault/VaultGroup/VaultGroupProcessor.swift b/BitwardenShared/UI/Vault/Vault/VaultGroup/VaultGroupProcessor.swift index 81a98dd463..70e3217295 100644 --- a/BitwardenShared/UI/Vault/Vault/VaultGroup/VaultGroupProcessor.swift +++ b/BitwardenShared/UI/Vault/Vault/VaultGroup/VaultGroupProcessor.swift @@ -384,6 +384,10 @@ extension VaultGroupProcessor: CipherItemOperationDelegate { displayToastAndRefresh(toastTitle: Localizations.itemDeleted) } + func itemSaved(type: CipherType) { + displayToastAndRefresh(toastTitle: type.savedToastTitle) + } + func itemSoftDeleted() { displayToastAndRefresh(toastTitle: Localizations.itemSoftDeleted) } diff --git a/BitwardenShared/UI/Vault/Vault/VaultGroup/VaultGroupProcessorTests.swift b/BitwardenShared/UI/Vault/Vault/VaultGroup/VaultGroupProcessorTests.swift index 3fe346e24c..a8049b726b 100644 --- a/BitwardenShared/UI/Vault/Vault/VaultGroup/VaultGroupProcessorTests.swift +++ b/BitwardenShared/UI/Vault/Vault/VaultGroup/VaultGroupProcessorTests.swift @@ -132,6 +132,16 @@ class VaultGroupProcessorTests: BitwardenTestCase { // swiftlint:disable:this ty waitFor(vaultRepository.fetchSyncCalled) } + /// `itemSaved(type:)` delegate method shows the toast for the saved item's type. + @MainActor + func test_delegate_itemSaved() { + XCTAssertNil(subject.state.toast) + + subject.itemSaved(type: .driversLicense) + XCTAssertEqual(subject.state.toast, Toast(title: Localizations.licenseSaved)) + waitFor(vaultRepository.fetchSyncCalled) + } + /// `itemSoftDeleted()` delegate method shows the expected toast. @MainActor func test_delegate_itemSoftDeleted() { diff --git a/BitwardenShared/UI/Vault/Vault/VaultList/VaultListProcessor.swift b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListProcessor.swift index 0e043e602d..e9a711cfc0 100644 --- a/BitwardenShared/UI/Vault/Vault/VaultList/VaultListProcessor.swift +++ b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListProcessor.swift @@ -859,6 +859,10 @@ extension VaultListProcessor: CipherItemOperationDelegate { state.toast = Toast(title: Localizations.itemDeleted) } + func itemSaved(type: CipherType) { + state.toast = Toast(title: type.savedToastTitle) + } + func itemSoftDeleted() { state.toast = Toast(title: Localizations.itemSoftDeleted) } diff --git a/BitwardenShared/UI/Vault/Vault/VaultList/VaultListProcessorTests.swift b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListProcessorTests.swift index c929992f14..cc5b1f9f97 100644 --- a/BitwardenShared/UI/Vault/Vault/VaultList/VaultListProcessorTests.swift +++ b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListProcessorTests.swift @@ -232,6 +232,15 @@ class VaultListProcessorTests: BitwardenTestCase { // swiftlint:disable:this typ XCTAssertEqual(subject.state.toast, Toast(title: Localizations.itemDeleted)) } + /// `itemSaved(type:)` delegate method shows the toast for the saved item's type. + @MainActor + func test_delegate_itemSaved() { + XCTAssertNil(subject.state.toast) + + subject.itemSaved(type: .driversLicense) + XCTAssertEqual(subject.state.toast, Toast(title: Localizations.licenseSaved)) + } + /// `itemSoftDeleted()` delegate method shows the expected toast. @MainActor func test_delegate_itemSoftDeleted() { diff --git a/BitwardenShared/UI/Vault/VaultItem/AddEditItem/AddEditItemProcessor.swift b/BitwardenShared/UI/Vault/VaultItem/AddEditItem/AddEditItemProcessor.swift index 67f29133b0..6f4fea9be7 100644 --- a/BitwardenShared/UI/Vault/VaultItem/AddEditItem/AddEditItemProcessor.swift +++ b/BitwardenShared/UI/Vault/VaultItem/AddEditItem/AddEditItemProcessor.swift @@ -26,6 +26,12 @@ protocol CipherItemOperationDelegate: AnyObject { /// Called when the cipher item has been successfully restored. func itemRestored() + /// Called when the cipher item has been successfully saved, whether it was added or updated. + /// + /// - Parameter type: The type of the cipher item that was saved. + /// + func itemSaved(type: CipherType) + /// Called when the cipher item has been successfully soft deleted. func itemSoftDeleted() @@ -49,6 +55,8 @@ extension CipherItemOperationDelegate { func itemRestored() {} + func itemSaved(type _: CipherType) {} + func itemSoftDeleted() {} func itemUnarchived() {} @@ -917,6 +925,7 @@ final class AddEditItemProcessor: StateProcessor