diff --git a/ElementX/Sources/FlowCoordinators/AppLockFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/AppLockFlowCoordinator.swift index 1fa2fcb96a..4d235cfae0 100644 --- a/ElementX/Sources/FlowCoordinators/AppLockFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/AppLockFlowCoordinator.swift @@ -49,13 +49,6 @@ class AppLockFlowCoordinator: CoordinatorProtocol { // Set the initial background state. showPlaceholder() - appLockService.disabledPublisher - .sink { - // When the service is disabled via a force logout, we need to remove the activity indicator. - ServiceLocator.shared.userIndicatorController.retractAllIndicators() - } - .store(in: &cancellables) - notificationCenter.publisher(for: UIApplication.didEnterBackgroundNotification) .sink { [weak self] _ in self?.applicationDidEnterBackground() @@ -131,7 +124,6 @@ class AppLockFlowCoordinator: CoordinatorProtocol { case .appUnlocked: actionsSubject.send(.unlockApp) case .forceLogout: - ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(type: .modal, title: L10n.commonSigningOut, persistent: true)) actionsSubject.send(.forceLogout) } } diff --git a/ElementX/Sources/Screens/AppLock/AppLockScreen/AppLockScreenModels.swift b/ElementX/Sources/Screens/AppLock/AppLockScreen/AppLockScreenModels.swift index 947f177a33..225d24b9d6 100644 --- a/ElementX/Sources/Screens/AppLock/AppLockScreen/AppLockScreenModels.swift +++ b/ElementX/Sources/Screens/AppLock/AppLockScreen/AppLockScreenModels.swift @@ -29,6 +29,8 @@ struct AppLockScreenViewState: BindableState { /// The number of times the user attempted to enter their PIN. var numberOfPINAttempts = 0 + /// An overlay indicator shown when the user is being logged out. + var forcedLogoutIndicator: UserIndicator? var bindings: AppLockScreenViewStateBindings diff --git a/ElementX/Sources/Screens/AppLock/AppLockScreen/AppLockScreenViewModel.swift b/ElementX/Sources/Screens/AppLock/AppLockScreen/AppLockScreenViewModel.swift index de25584b56..9b25b101a1 100644 --- a/ElementX/Sources/Screens/AppLock/AppLockScreen/AppLockScreenViewModel.swift +++ b/ElementX/Sources/Screens/AppLock/AppLockScreen/AppLockScreenViewModel.swift @@ -76,7 +76,7 @@ class AppLockScreenViewModel: AppLockScreenViewModelType, AppLockScreenViewModel state.bindings.alertInfo = .init(id: .confirmResetPIN, title: L10n.screenAppLockSignoutAlertTitle, message: L10n.screenAppLockSignoutAlertMessage, - primaryButton: .init(title: L10n.actionOk) { self.actionsSubject.send(.forceLogout) }, + primaryButton: .init(title: L10n.actionOk) { self.forceLogout() }, secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil)) } @@ -90,7 +90,12 @@ class AppLockScreenViewModel: AppLockScreenViewModelType, AppLockScreenViewModel state.bindings.alertInfo = .init(id: .forcedLogout, title: L10n.screenAppLockSignoutAlertTitle, message: L10n.screenAppLockSignoutAlertMessage, - primaryButton: .init(title: L10n.actionOk) { self.actionsSubject.send(.forceLogout) }) + primaryButton: .init(title: L10n.actionOk) { self.forceLogout() }) } } + + private func forceLogout() { + state.forcedLogoutIndicator = UserIndicator(type: .modal, title: L10n.commonSigningOut, persistent: true) + actionsSubject.send(.forceLogout) + } } diff --git a/ElementX/Sources/Screens/AppLock/AppLockScreen/View/AppLockScreen.swift b/ElementX/Sources/Screens/AppLock/AppLockScreen/View/AppLockScreen.swift index c5cab8f0a5..8f226a7d15 100644 --- a/ElementX/Sources/Screens/AppLock/AppLockScreen/View/AppLockScreen.swift +++ b/ElementX/Sources/Screens/AppLock/AppLockScreen/View/AppLockScreen.swift @@ -58,6 +58,11 @@ struct AppLockScreen: View { } .background() .environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault)) + .disabled(context.viewState.forcedLogoutIndicator != nil) + .overlay { + context.viewState.forcedLogoutIndicator.map(UserIndicatorModalView.init) + .animation(.elementDefault, value: context.viewState.forcedLogoutIndicator) + } .alert(item: $context.alertInfo) } diff --git a/ElementX/Sources/Services/AppLock/AppLockService.swift b/ElementX/Sources/Services/AppLock/AppLockService.swift index 754345e52a..794478dcf0 100644 --- a/ElementX/Sources/Services/AppLock/AppLockService.swift +++ b/ElementX/Sources/Services/AppLock/AppLockService.swift @@ -56,9 +56,6 @@ class AppLockService: AppLockServiceProtocol { var numberOfPINAttempts: AnyPublisher { appSettings.$appLockNumberOfPINAttempts } - private var disabledSubject: PassthroughSubject = .init() - var disabledPublisher: AnyPublisher { disabledSubject.eraseToAnyPublisher() } - init(keychainController: KeychainControllerProtocol, appSettings: AppSettings, context: LAContext = .init()) { self.keychainController = keychainController self.appSettings = appSettings @@ -108,7 +105,6 @@ class AppLockService: AppLockServiceProtocol { keychainController.removePINCode() keychainController.removePINCodeBiometricState() appSettings.appLockNumberOfPINAttempts = 0 - disabledSubject.send() } func applicationDidEnterBackground() { diff --git a/ElementX/Sources/Services/AppLock/AppLockServiceProtocol.swift b/ElementX/Sources/Services/AppLock/AppLockServiceProtocol.swift index 3ba26eb13f..fdd3289646 100644 --- a/ElementX/Sources/Services/AppLock/AppLockServiceProtocol.swift +++ b/ElementX/Sources/Services/AppLock/AppLockServiceProtocol.swift @@ -45,9 +45,6 @@ protocol AppLockServiceProtocol: AnyObject { /// to re-enter their PIN code to re-enable the feature (i.e. to accept a new face or fingerprint). var biometricUnlockTrusted: Bool { get } - /// A publisher that advertises when the service has been disabled. - var disabledPublisher: AnyPublisher { get } - /// Sets the user's PIN code used to unlock the app. func setupPINCode(_ pinCode: String) -> Result /// Validates the supplied PIN code is long enough, only contains digits and isn't a weak choice. diff --git a/UITests/Sources/AppLockUITests.swift b/UITests/Sources/AppLockUITests.swift index ceb173ca24..e8a6dd761e 100644 --- a/UITests/Sources/AppLockUITests.swift +++ b/UITests/Sources/AppLockUITests.swift @@ -23,6 +23,9 @@ class AppLockUITests: XCTestCase { enum Step { static let placeholder = 0 static let lockScreen = 1 + static let failedUnlock = 2 + static let logoutAlert = 3 + static let forcedLogout = 4 static let unlocked = 99 } @@ -76,6 +79,33 @@ class AppLockUITests: XCTestCase { try await app.assertScreenshot(.appLockFlow, step: Step.unlocked) } + func testWrongPIN() async throws { + // Given an app with screen lock enabled that is ready to unlock. + let client = try UITestsSignalling.Client(mode: .tests) + app = Application.launch(.appLockFlow) + await client.waitForApp() + + try await app.assertScreenshot(.appLockFlow, step: Step.unlocked) + try client.send(.notification(name: UIApplication.didEnterBackgroundNotification)) + try client.send(.notification(name: UIApplication.willEnterForegroundNotification)) + try await app.assertScreenshot(.appLockFlow, step: Step.lockScreen) + + // When entering an incorrect PIN + enterWrongPIN() + + // Then the app should remain locked with a warning. + try await app.assertScreenshot(.appLockFlow, step: Step.failedUnlock) + + // When entering it incorrectly twice more. + enterWrongPIN() + enterWrongPIN() + + // Then then the app should sign the user out. + try await app.assertScreenshot(.appLockFlow, step: Step.logoutAlert) + app.alerts.element.buttons[A11yIdentifiers.alertInfo.primaryButton].tap() + try await app.assertScreenshot(.appLockFlow, step: Step.forcedLogout) + } + // MARK: - Helpers func enterPIN() { @@ -84,4 +114,11 @@ class AppLockUITests: XCTestCase { app.buttons[A11yIdentifiers.appLockScreen.numpad(2)].tap() app.buttons[A11yIdentifiers.appLockScreen.numpad(3)].tap() } + + func enterWrongPIN() { + app.buttons[A11yIdentifiers.appLockScreen.numpad(0)].tap() + app.buttons[A11yIdentifiers.appLockScreen.numpad(0)].tap() + app.buttons[A11yIdentifiers.appLockScreen.numpad(0)].tap() + app.buttons[A11yIdentifiers.appLockScreen.numpad(0)].tap() + } } diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.appLockFlow-2.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.appLockFlow-2.png new file mode 100644 index 0000000000..b6896bd3af --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.appLockFlow-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4b9b5472f7c7405f57edc584676201fbcd47ad065ec76e793a9ab62686485d7 +size 101990 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.appLockFlow-3.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.appLockFlow-3.png new file mode 100644 index 0000000000..dbb3483074 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.appLockFlow-3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d58a22f5e6181851fb781fa3adc0b8c0cace95485cd1a0ad4c44ce0b443bc36 +size 243400 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.appLockFlow-4.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.appLockFlow-4.png new file mode 100644 index 0000000000..083df9e8c2 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.appLockFlow-4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:837433d70e1a6ce95d3ca397ebc62d6cc16ce75e40ed2500a71f4dc23bf6ea63 +size 112869 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.appLockFlow-2.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.appLockFlow-2.png new file mode 100644 index 0000000000..2b3005a66e --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.appLockFlow-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef32e70559dffc81857e5f45fcb6a19340dac24f053d17ac35ee9dba079bd5d8 +size 119421 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.appLockFlow-3.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.appLockFlow-3.png new file mode 100644 index 0000000000..f60cba6ea7 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.appLockFlow-3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1360bca0f412008082064d36fe3e21f06050e18f7086c6ae6ec3e8b338d86e0e +size 383427 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.appLockFlow-4.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.appLockFlow-4.png new file mode 100644 index 0000000000..0ef121fcd7 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.appLockFlow-4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e9327687e1dbf64c1f1a10bfb90180fe440d2eb577955830cda186bc6fafc6ca +size 137089 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.appLockFlow-2.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.appLockFlow-2.png new file mode 100644 index 0000000000..5fb5fc682b --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.appLockFlow-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3520c84239be1f3ec57a402661b3470de1b411035386b80173eb27f318ead21 +size 109005 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.appLockFlow-3.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.appLockFlow-3.png new file mode 100644 index 0000000000..bc1aae0767 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.appLockFlow-3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02fb11ea0d6ae4ca5c2c274100872aca790e1319e80f2d9c5e28491391fa5120 +size 300086 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.appLockFlow-4.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.appLockFlow-4.png new file mode 100644 index 0000000000..661d92b8af --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.appLockFlow-4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7dba3b14bee44e309ce7bb4df3e5b909dca683c7f3c1ed87640dfab49bdf6d0 +size 117251 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.appLockFlow-2.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.appLockFlow-2.png new file mode 100644 index 0000000000..becefdd755 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.appLockFlow-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4df8f45fc81a28c27fe326162cf5a86660a28fc2a76ef8215cb9563a5e23a766 +size 127605 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.appLockFlow-3.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.appLockFlow-3.png new file mode 100644 index 0000000000..ffc7ae431f --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.appLockFlow-3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:596d65ea705c802888e29e42094961cb2d3fc480a8fab8d6e8cea105fc233f1c +size 499030 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.appLockFlow-4.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.appLockFlow-4.png new file mode 100644 index 0000000000..c683306842 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.appLockFlow-4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6b6f2d9135a6994cf1fc719c5ac8a54f7aaeaa50807e6045bf44ad40d3438a5 +size 142119 diff --git a/changelog.d/pr-2063.bugfix b/changelog.d/pr-2063.bugfix new file mode 100644 index 0000000000..be62aab626 --- /dev/null +++ b/changelog.d/pr-2063.bugfix @@ -0,0 +1 @@ +Fix a regression where the forced logout indicator was presented on the hidden overlay window. \ No newline at end of file