From 785e1d191667c54b05d17351ce29ff3fd393cbcb Mon Sep 17 00:00:00 2001 From: Bradley Mackey Date: Tue, 15 Sep 2026 09:30:14 +0400 Subject: [PATCH] Unobfuscate TOTP previews when the scene becomes active MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TOTP previews obfuscate on background/inactive but had no unobfuscate path — unobfuscateForPrivacy() existed only for HOTP — so a transient inactive (Control Centre, system alert) left codes hidden until the next timer emission. Mirror the HOTP repository/generator semantics. Co-Authored-By: Claude Fable 5 --- .../Previews/TOTPPreviewViewRepository.swift | 7 +++++++ .../Previews/OTP/TOTPPreviewViewGenerator.swift | 3 +++ .../OTPCode/TOTPPreviewViewRepositoryImplTests.swift | 12 ++++++++++++ .../TOTPPreviewViewGeneratorTests.swift | 4 +++- 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Vault/Sources/VaultFeed/Presentation/Previews/TOTPPreviewViewRepository.swift b/Vault/Sources/VaultFeed/Presentation/Previews/TOTPPreviewViewRepository.swift index 11e7a458e..56c8c0db4 100644 --- a/Vault/Sources/VaultFeed/Presentation/Previews/TOTPPreviewViewRepository.swift +++ b/Vault/Sources/VaultFeed/Presentation/Previews/TOTPPreviewViewRepository.swift @@ -11,6 +11,7 @@ public protocol TOTPPreviewViewRepository: VaultItemCache, VaultItemCopyActionHa func restartAllTimers() func stopAllTimers() func obfuscateForPrivacy() + func unobfuscateForPrivacy() } public final class TOTPPreviewViewRepositoryImpl: TOTPPreviewViewRepository { @@ -78,6 +79,12 @@ public final class TOTPPreviewViewRepositoryImpl: TOTPPreviewViewRepository { viewModel.update(.obfuscated(.privacy)) } } + + public func unobfuscateForPrivacy() { + for viewModel in viewModelCache.values { + viewModel.updateRemovePrivacyObfuscation() + } + } } extension TOTPPreviewViewRepositoryImpl: VaultItemCopyActionHandler { diff --git a/Vault/Sources/VaultiOS/Views/Previews/OTP/TOTPPreviewViewGenerator.swift b/Vault/Sources/VaultiOS/Views/Previews/OTP/TOTPPreviewViewGenerator.swift index 9ab878c43..e670e6214 100644 --- a/Vault/Sources/VaultiOS/Views/Previews/OTP/TOTPPreviewViewGenerator.swift +++ b/Vault/Sources/VaultiOS/Views/Previews/OTP/TOTPPreviewViewGenerator.swift @@ -44,6 +44,9 @@ public final class TOTPPreviewViewGenerator: Va repository.obfuscateForPrivacy() repository.stopAllTimers() case .active: + // Unobfuscate before restarting so the codes reappear + // immediately rather than waiting for the next timer tick. + repository.unobfuscateForPrivacy() repository.restartAllTimers() @unknown default: break diff --git a/Vault/Tests/VaultFeedTests/Presentation/OTPCode/TOTPPreviewViewRepositoryImplTests.swift b/Vault/Tests/VaultFeedTests/Presentation/OTPCode/TOTPPreviewViewRepositoryImplTests.swift index cfa29de92..8bc24e554 100644 --- a/Vault/Tests/VaultFeedTests/Presentation/OTPCode/TOTPPreviewViewRepositoryImplTests.swift +++ b/Vault/Tests/VaultFeedTests/Presentation/OTPCode/TOTPPreviewViewRepositoryImplTests.swift @@ -150,6 +150,18 @@ struct TOTPPreviewViewRepositoryImplTests { #expect(viewModel.code == .obfuscated(.privacy)) } + @Test + func unobfuscateForPrivacy_unobfuscatesCodesHiddenForPrivacy() { + let sut = makeSUT() + let viewModel = sut.previewViewModel(metadata: anyVaultItemMetadata(), code: anyTOTPCode()) + viewModel.update(.visible("123456")) + viewModel.update(.obfuscated(.privacy)) + + sut.unobfuscateForPrivacy() + + #expect(viewModel.code == .visible("123456")) + } + @Test func vaultItemCacheClear_removesItemsMatchingIDFromCache() async { let sut = makeSUT() diff --git a/Vault/Tests/VaultiOSTests/TOTPPreviewViewGeneratorTests.swift b/Vault/Tests/VaultiOSTests/TOTPPreviewViewGeneratorTests.swift index 2ceab6380..8b1e8b624 100644 --- a/Vault/Tests/VaultiOSTests/TOTPPreviewViewGeneratorTests.swift +++ b/Vault/Tests/VaultiOSTests/TOTPPreviewViewGeneratorTests.swift @@ -63,7 +63,7 @@ final class TOTPPreviewViewGeneratorTests { } @Test - func scenePhaseDidChange_activeRestartsAllTimers() { + func scenePhaseDidChange_activeUnobfuscatesAndRestartsAllTimers() { let factory = makeTOTPPreviewViewFactoryMock() let repository = TOTPPreviewViewRepositoryMock() let sut = makeSUT(factory: factory, repository: repository) @@ -71,6 +71,7 @@ final class TOTPPreviewViewGeneratorTests { sut.scenePhaseDidChange(to: .active) #expect(repository.restartAllTimersCallCount == 1) + #expect(repository.unobfuscateForPrivacyCallCount == 1) #expect(repository.obfuscateForPrivacyCallCount == 0) #expect(repository.stopAllTimersCallCount == 0) } @@ -85,6 +86,7 @@ final class TOTPPreviewViewGeneratorTests { #expect(repository.restartAllTimersCallCount == 0) #expect(repository.obfuscateForPrivacyCallCount == 1) + #expect(repository.unobfuscateForPrivacyCallCount == 0) #expect(repository.stopAllTimersCallCount == 1) }