Skip to content

Commit

Permalink
Implement AppLockScreen as per the designs. (#1925)
Browse files Browse the repository at this point in the history
Fix a bug in the unlock flow
  • Loading branch information
pixlwave authored Oct 20, 2023
1 parent d292fe8 commit ceee6b1
Show file tree
Hide file tree
Showing 21 changed files with 376 additions and 83 deletions.
4 changes: 4 additions & 0 deletions ElementX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@
64AB99285DC4437C0DDE9585 /* MenuSheetLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49ABAB186CF00B15C5521D04 /* MenuSheetLabelStyle.swift */; };
64C373ACCFA26D42BA45CFAD /* HomeScreenInvitesButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24227FF9A2797F6EA7F69CDD /* HomeScreenInvitesButton.swift */; };
64D05250CEDE8B604119F6E6 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981663D961C94270FA035FD0 /* Alert.swift */; };
64E541F88F35BD126C4AFCA1 /* AppLockScreenPINKeypad.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38345442415E07A931197C55 /* AppLockScreenPINKeypad.swift */; };
64F43D7390DA2A0AFD6BA911 /* VideoRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1941C8817E6B6971BA4415F5 /* VideoRoomTimelineView.swift */; };
64FF5CB4E35971255872E1BB /* AuthenticationServiceProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0CB536D1C3CC15AA740CC6 /* AuthenticationServiceProxyProtocol.swift */; };
651341E67C3514F9811A1EC1 /* LoginScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05F598B1B346DAF223651C91 /* LoginScreenCoordinator.swift */; };
Expand Down Expand Up @@ -1174,6 +1175,7 @@
37CA26F55123E36B50DB0B3A /* AttributedStringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringTests.swift; sourceTree = "<group>"; };
37E727F7E0BCE8A0BBFD33FF /* OnboardingScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingScreenCoordinator.swift; sourceTree = "<group>"; };
382B50F7E379B3DBBD174364 /* NotificationSettingsProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsProxyMock.swift; sourceTree = "<group>"; };
38345442415E07A931197C55 /* AppLockScreenPINKeypad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenPINKeypad.swift; sourceTree = "<group>"; };
38E521D6C2BF8DF0DFB35146 /* DeveloperOptionsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreen.swift; sourceTree = "<group>"; };
3948D16F021DFDB2CD26EAA8 /* MockBackgroundTaskService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBackgroundTaskService.swift; sourceTree = "<group>"; };
398817652FA8ABAE0A31AC6D /* ReadableFrameModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadableFrameModifier.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4400,6 +4402,7 @@
isa = PBXGroup;
children = (
56D6F88FE35A0979D2821E06 /* AppLockScreen.swift */,
38345442415E07A931197C55 /* AppLockScreenPINKeypad.swift */,
);
path = View;
sourceTree = "<group>";
Expand Down Expand Up @@ -5091,6 +5094,7 @@
06F8EDF52E33A2D36BCC1161 /* AppLockScreen.swift in Sources */,
9912F9EB2D6589141A2957B4 /* AppLockScreenCoordinator.swift in Sources */,
2DD9D0FE7CB5CFC80D071451 /* AppLockScreenModels.swift in Sources */,
64E541F88F35BD126C4AFCA1 /* AppLockScreenPINKeypad.swift in Sources */,
97969EF0B9C412CD38E5CA93 /* AppLockScreenViewModel.swift in Sources */,
E79D79CDAFE8BEBCC3AECA54 /* AppLockScreenViewModelProtocol.swift in Sources */,
1D623953F970D11F6F38499C /* AppLockService.swift in Sources */,
Expand Down
16 changes: 16 additions & 0 deletions ElementX/Resources/Localizations/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"a11y_notifications_mentions_only" = "Mentions only";
"a11y_notifications_muted" = "Muted";
"a11y_pause" = "Pause";
"a11y_pin_field" = "PIN field";
"a11y_play" = "Play";
"a11y_poll" = "Poll";
"a11y_poll_end" = "Ended poll";
Expand Down Expand Up @@ -95,6 +96,7 @@
"common_editing" = "Editing";
"common_emote" = "* %1$@ %2$@";
"common_encryption_enabled" = "Encryption enabled";
"common_enter_your_pin" = "Enter your PIN";
"common_error" = "Error";
"common_everyone" = "Everyone";
"common_file" = "File";
Expand Down Expand Up @@ -130,6 +132,7 @@
"common_rich_text_editor" = "Rich text editor";
"common_room_name" = "Room name";
"common_room_name_placeholder" = "e.g. your project name";
"common_screen_lock" = "Screen lock";
"common_search_for_someone" = "Search for someone";
"common_search_results" = "Search results";
"common_security" = "Security";
Expand All @@ -151,6 +154,7 @@
"common_unable_to_decrypt" = "Unable to decrypt";
"common_unable_to_invite_message" = "Invites couldn't be sent to one or more users.";
"common_unable_to_invite_title" = "Unable to send invite(s)";
"common_unlock" = "Unlock";
"common_unmute" = "Unmute";
"common_unsupported_event" = "Unsupported event";
"common_username" = "Username";
Expand Down Expand Up @@ -260,6 +264,18 @@
"screen_analytics_prompt_third_party_sharing" = "We won't share your data with third parties";
"screen_analytics_prompt_title" = "Help improve %1$@";
"screen_analytics_settings_share_data" = "Share analytics data";
"screen_app_lock_forgot_pin" = "Forgot PIN?";
"screen_app_lock_settings_change_pin" = "Change PIN code";
"screen_app_lock_settings_enable_biometric_unlock" = "Allow biometric unlock";
"screen_app_lock_settings_enable_face_id_ios" = "Allow Face ID";
"screen_app_lock_settings_enable_optic_id_ios" = "Allow Optic ID";
"screen_app_lock_settings_enable_touch_id_ios" = "Allow Touch ID";
"screen_app_lock_settings_remove_pin" = "Remove PIN";
"screen_app_lock_settings_remove_pin_alert_message" = "Are you sure you want to remove PIN?";
"screen_app_lock_settings_remove_pin_alert_title" = "Remove PIN?";
"screen_app_lock_signout_alert_message" = "You’ll need to re-login and create a new PIN to proceed";
"screen_app_lock_signout_alert_title" = "You are being signed out";
"screen_app_lock_subtitle" = "You have 3 attempts to unlock";
"screen_bug_report_attach_screenshot" = "Attach screenshot";
"screen_bug_report_contact_me" = "You may contact me if you have any follow up questions.";
"screen_bug_report_contact_me_title" = "Contact me";
Expand Down
32 changes: 32 additions & 0 deletions ElementX/Resources/Localizations/en.lproj/Localizable.stringsdict
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>a11y_digits_entered</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@COUNT@</string>
<key>COUNT</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%1$d digit entered</string>
<key>other</key>
<string>%1$d digits entered</string>
</dict>
</dict>
<key>common_member_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
Expand Down Expand Up @@ -146,6 +162,22 @@
<string>%1$d room changes</string>
</dict>
</dict>
<key>screen_app_lock_subtitle_wrong_pin</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@COUNT@</string>
<key>COUNT</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Wrong PIN. You have %1$d more chance</string>
<key>other</key>
<string>Wrong PIN. You have %1$d more chances</string>
</dict>
</dict>
<key>screen_room_member_list_header_title</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
Expand Down
12 changes: 0 additions & 12 deletions ElementX/Resources/Localizations/en.lproj/Untranslated.strings
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,6 @@
/* Used for testing */
"untranslated" = "Untranslated";

"screen_app_lock_title" = "%@ is locked";
"screen_app_lock_settings_change_pin" = "Change PIN code";
"screen_app_lock_settings_remove_pin" = "Remove PIN";
"screen_app_lock_settings_enable_touch_id_ios" = "Allow Touch ID";
"screen_app_lock_settings_enable_face_id_ios" = "Allow Face ID";
"screen_app_lock_settings_enable_optic_id_ios" = "Allow Optic ID";
"screen_app_lock_settings_enable_biometric_unlock" = "Allow biometric unlock";
"screen_app_lock_settings_remove_pin_alert_title" = "Remove PIN?";
"screen_app_lock_settings_remove_pin_alert_message" = "Are you sure you want to remove PIN?";
"common_unlock" = "Unlock";
"common_screen_lock" = "Screen lock";

// MARK: - Soft logout

"soft_logout_signin_title" = "Sign in";
Expand Down
4 changes: 0 additions & 4 deletions ElementX/Sources/Application/Application.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@ struct Application: App {
openURLInSystemBrowser($0)
}
}
.introspect(.window, on: .supportedVersions) { window in
// Workaround for SwiftUI not consistently applying the tint colour to Alerts/Confirmation Dialogs.
window.tintColor = .compound.textActionPrimary
}
.task {
appCoordinator.start()
}
Expand Down
3 changes: 3 additions & 0 deletions ElementX/Sources/Application/Windowing/WindowManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,18 @@ class WindowManager {
/// Configures the window manager to operate on the supplied scene.
func configure(with windowScene: UIWindowScene) {
mainWindow = windowScene.keyWindow
mainWindow.tintColor = .compound.textActionPrimary

overlayWindow = UIWindow(windowScene: windowScene)
overlayWindow.tintColor = .compound.textActionPrimary
overlayWindow.backgroundColor = .clear
// We don't support user interaction on our indicators so disable interaction, to pass
// touches through to the main window. If this changes, there's another solution here:
// https://www.fivestars.blog/articles/swiftui-windows/
overlayWindow.isUserInteractionEnabled = false

alternateWindow = UIWindow(windowScene: windowScene)
alternateWindow.tintColor = .compound.textActionPrimary

delegate?.windowManagerDidConfigureWindows(self)
}
Expand Down
10 changes: 8 additions & 2 deletions ElementX/Sources/FlowCoordinators/AppLockFlowCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,14 @@ class AppLockFlowCoordinator: CoordinatorProtocol {
}

private func applicationWillEnterForeground() {
guard appLockService.isEnabled, appLockService.computeNeedsUnlock(willEnterForegroundAt: .now) else { return }
showUnlockScreen()
guard appLockService.isEnabled else { return }

if appLockService.computeNeedsUnlock(willEnterForegroundAt: .now) {
showUnlockScreen()
} else {
// Reveal the app again if within the grace period.
actionsSubject.send(.unlockApp)
}
}

/// Displays the unlock flow with the app's placeholder view to hide obscure the view hierarchy in the app switcher.
Expand Down
24 changes: 0 additions & 24 deletions ElementX/Sources/Generated/Strings+Untranslated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,6 @@ import Foundation
// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
public enum UntranslatedL10n {
/// Screen lock
public static var commonScreenLock: String { return UntranslatedL10n.tr("Untranslated", "common_screen_lock") }
/// Unlock
public static var commonUnlock: String { return UntranslatedL10n.tr("Untranslated", "common_unlock") }
/// Change PIN code
public static var screenAppLockSettingsChangePin: String { return UntranslatedL10n.tr("Untranslated", "screen_app_lock_settings_change_pin") }
/// Allow biometric unlock
public static var screenAppLockSettingsEnableBiometricUnlock: String { return UntranslatedL10n.tr("Untranslated", "screen_app_lock_settings_enable_biometric_unlock") }
/// Allow Face ID
public static var screenAppLockSettingsEnableFaceIdIos: String { return UntranslatedL10n.tr("Untranslated", "screen_app_lock_settings_enable_face_id_ios") }
/// Allow Optic ID
public static var screenAppLockSettingsEnableOpticIdIos: String { return UntranslatedL10n.tr("Untranslated", "screen_app_lock_settings_enable_optic_id_ios") }
/// Allow Touch ID
public static var screenAppLockSettingsEnableTouchIdIos: String { return UntranslatedL10n.tr("Untranslated", "screen_app_lock_settings_enable_touch_id_ios") }
/// Remove PIN
public static var screenAppLockSettingsRemovePin: String { return UntranslatedL10n.tr("Untranslated", "screen_app_lock_settings_remove_pin") }
/// Are you sure you want to remove PIN?
public static var screenAppLockSettingsRemovePinAlertMessage: String { return UntranslatedL10n.tr("Untranslated", "screen_app_lock_settings_remove_pin_alert_message") }
/// Remove PIN?
public static var screenAppLockSettingsRemovePinAlertTitle: String { return UntranslatedL10n.tr("Untranslated", "screen_app_lock_settings_remove_pin_alert_title") }
/// %@ is locked
public static func screenAppLockTitle(_ p1: Any) -> String {
return UntranslatedL10n.tr("Untranslated", "screen_app_lock_title", String(describing: p1))
}
/// Clear all data currently stored on this device?
/// Sign in again to access your account data and messages.
public static var softLogoutClearDataDialogContent: String { return UntranslatedL10n.tr("Untranslated", "soft_logout_clear_data_dialog_content") }
Expand Down
40 changes: 40 additions & 0 deletions ElementX/Sources/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import Foundation
public enum L10n {
/// Delete
public static var a11yDelete: String { return L10n.tr("Localizable", "a11y_delete") }
/// Plural format key: "%#@COUNT@"
public static func a11yDigitsEntered(_ p1: Int) -> String {
return L10n.tr("Localizable", "a11y_digits_entered", p1)
}
/// Hide password
public static var a11yHidePassword: String { return L10n.tr("Localizable", "a11y_hide_password") }
/// Mentions only
Expand All @@ -20,6 +24,8 @@ public enum L10n {
public static var a11yNotificationsMuted: String { return L10n.tr("Localizable", "a11y_notifications_muted") }
/// Pause
public static var a11yPause: String { return L10n.tr("Localizable", "a11y_pause") }
/// PIN field
public static var a11yPinField: String { return L10n.tr("Localizable", "a11y_pin_field") }
/// Play
public static var a11yPlay: String { return L10n.tr("Localizable", "a11y_play") }
/// Poll
Expand Down Expand Up @@ -206,6 +212,8 @@ public enum L10n {
}
/// Encryption enabled
public static var commonEncryptionEnabled: String { return L10n.tr("Localizable", "common_encryption_enabled") }
/// Enter your PIN
public static var commonEnterYourPin: String { return L10n.tr("Localizable", "common_enter_your_pin") }
/// Error
public static var commonError: String { return L10n.tr("Localizable", "common_error") }
/// Everyone
Expand Down Expand Up @@ -296,6 +304,8 @@ public enum L10n {
public static var commonRoomName: String { return L10n.tr("Localizable", "common_room_name") }
/// e.g. your project name
public static var commonRoomNamePlaceholder: String { return L10n.tr("Localizable", "common_room_name_placeholder") }
/// Screen lock
public static var commonScreenLock: String { return L10n.tr("Localizable", "common_screen_lock") }
/// Search for someone
public static var commonSearchForSomeone: String { return L10n.tr("Localizable", "common_search_for_someone") }
/// Search results
Expand Down Expand Up @@ -338,6 +348,8 @@ public enum L10n {
public static var commonUnableToInviteMessage: String { return L10n.tr("Localizable", "common_unable_to_invite_message") }
/// Unable to send invite(s)
public static var commonUnableToInviteTitle: String { return L10n.tr("Localizable", "common_unable_to_invite_title") }
/// Unlock
public static var commonUnlock: String { return L10n.tr("Localizable", "common_unlock") }
/// Unmute
public static var commonUnmute: String { return L10n.tr("Localizable", "common_unmute") }
/// Unsupported event
Expand Down Expand Up @@ -634,6 +646,34 @@ public enum L10n {
public static var screenAnalyticsSettingsReadTermsContentLink: String { return L10n.tr("Localizable", "screen_analytics_settings_read_terms_content_link") }
/// Share analytics data
public static var screenAnalyticsSettingsShareData: String { return L10n.tr("Localizable", "screen_analytics_settings_share_data") }
/// Forgot PIN?
public static var screenAppLockForgotPin: String { return L10n.tr("Localizable", "screen_app_lock_forgot_pin") }
/// Change PIN code
public static var screenAppLockSettingsChangePin: String { return L10n.tr("Localizable", "screen_app_lock_settings_change_pin") }
/// Allow biometric unlock
public static var screenAppLockSettingsEnableBiometricUnlock: String { return L10n.tr("Localizable", "screen_app_lock_settings_enable_biometric_unlock") }
/// Allow Face ID
public static var screenAppLockSettingsEnableFaceIdIos: String { return L10n.tr("Localizable", "screen_app_lock_settings_enable_face_id_ios") }
/// Allow Optic ID
public static var screenAppLockSettingsEnableOpticIdIos: String { return L10n.tr("Localizable", "screen_app_lock_settings_enable_optic_id_ios") }
/// Allow Touch ID
public static var screenAppLockSettingsEnableTouchIdIos: String { return L10n.tr("Localizable", "screen_app_lock_settings_enable_touch_id_ios") }
/// Remove PIN
public static var screenAppLockSettingsRemovePin: String { return L10n.tr("Localizable", "screen_app_lock_settings_remove_pin") }
/// Are you sure you want to remove PIN?
public static var screenAppLockSettingsRemovePinAlertMessage: String { return L10n.tr("Localizable", "screen_app_lock_settings_remove_pin_alert_message") }
/// Remove PIN?
public static var screenAppLockSettingsRemovePinAlertTitle: String { return L10n.tr("Localizable", "screen_app_lock_settings_remove_pin_alert_title") }
/// You’ll need to re-login and create a new PIN to proceed
public static var screenAppLockSignoutAlertMessage: String { return L10n.tr("Localizable", "screen_app_lock_signout_alert_message") }
/// You are being signed out
public static var screenAppLockSignoutAlertTitle: String { return L10n.tr("Localizable", "screen_app_lock_signout_alert_title") }
/// You have 3 attempts to unlock
public static var screenAppLockSubtitle: String { return L10n.tr("Localizable", "screen_app_lock_subtitle") }
/// Plural format key: "%#@COUNT@"
public static func screenAppLockSubtitleWrongPin(_ p1: Int) -> String {
return L10n.tr("Localizable", "screen_app_lock_subtitle_wrong_pin", p1)
}
/// Attach screenshot
public static var screenBugReportAttachScreenshot: String { return L10n.tr("Localizable", "screen_bug_report_attach_screenshot") }
/// You may contact me if you have any follow up questions.
Expand Down
1 change: 1 addition & 0 deletions ElementX/Sources/Other/AccessibilityIdentifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ struct A11yIdentifiers {
let secureBackup = "settings-secure_backup"
let notifications = "settings-notifications"
let analytics = "settings-analytics"
let screenLock = "settings-screen_lock"
let reportBug = "settings-report_bug"
let about = "settings_about"
let advancedSettings = "settings_advanced-settings"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,45 @@ enum AppLockScreenViewModelAction {
}

struct AppLockScreenViewState: BindableState {
private let maximumAttempts = 3

/// The number of times the user attempted to enter their PIN.
var numberOfPINAttempts = 0

var bindings: AppLockScreenViewStateBindings

/// The number of digits the user has entered so far.
var numberOfDigitsEntered: Int { bindings.pinCode.count }
/// Whether the subtitle is in a warning state or not.
var isSubtitleWarning: Bool { numberOfPINAttempts > 0 }
/// The string shown in the screen's subtitle.
var subtitle: String {
if !isSubtitleWarning {
return L10n.screenAppLockSubtitle
} else {
return L10n.screenAppLockSubtitleWrongPin(maximumAttempts - numberOfPINAttempts)
}
}
}

struct AppLockScreenViewStateBindings { }
struct AppLockScreenViewStateBindings {
/// The PIN code entered by the user.
var pinCode = ""
var alertInfo: AlertInfo<AppLockScreenAlertType>?
}

enum AppLockScreenViewAction: CustomStringConvertible {
enum AppLockScreenAlertType {
/// The user has failed too many times, they're being signed out.
case forceSignOut
/// The user has forgotten their PIN, confirm they're happy to sign out.
case confirmResetPIN
}

enum AppLockScreenViewAction {
/// Attempt to unlock the app with the supplied PIN code.
case submitPINCode(String)

var description: String {
switch self {
case .submitPINCode:
return "submitPINCode"
}
}
case submitPINCode
/// Clears the PIN code after a failure animation.
case clearPINCode
/// The user didn't heed the warnings and can't remember their PIN.
case forgotPIN
}
Loading

0 comments on commit ceee6b1

Please sign in to comment.