Final tweaks to the existing QR code screens to match the designs. (#4874)

* Simplify QR errors even more and use the correct error state for deviceAlreadySignedIn.

* Remove the pre-defined padding on TitleAndIcon and use it in the QRCodeLoginScreen too.

* Update FullscreenDialog to allow different horizontal padding in each section.
This commit is contained in:
Doug
2025-12-17 17:23:55 +00:00
committed by GitHub
parent 117185de87
commit 1a62f2cca6
94 changed files with 325 additions and 362 deletions
@@ -176,7 +176,6 @@
"common_advanced_settings" = "Advanced settings";
"common_an_image" = "an image";
"common_analytics" = "Analytics";
"common_android_shortcuts_remove_reason_left_room" = "You left the room";
"common_android_shortcuts_remove_reason_session_logged_out" = "You were logged out of the session";
"common_appearance" = "Appearance";
"common_audio" = "Audio";
@@ -589,32 +588,21 @@
"screen_leave_space_subtitle_only_last_admin" = "You will not be removed from the following room(s) because you're the only administrator:";
"screen_leave_space_title" = "Leave %1$@?";
"screen_leave_space_title_last_admin" = "You are the only admin for %1$@";
"screen_link_new_device_desktop_scanning_title" = "Scan the QR code";
"screen_link_new_device_desktop_step1" = "Open %1$@ on a laptop or desktop computer";
"screen_link_new_device_desktop_step3" = "Scan the QR code with this device";
"screen_link_new_device_desktop_submit" = "Ready to scan";
"screen_link_new_device_desktop_title" = "Open %1$@ on a desktop computer to get the QR code";
"screen_link_new_device_enter_number_error_numbers_do_not_match" = "The numbers don’t match";
"screen_link_new_device_enter_number_notice" = "Enter 2-digit code";
"screen_link_new_device_enter_number_subtitle" = "This will verify that the connection to your other device is secure.";
"screen_link_new_device_enter_number_title" = "Enter the number shown on your other device";
"screen_link_new_device_error_app_not_supported_subtitle" = "Your account provider does not support %1$@.";
"screen_link_new_device_error_app_not_supported_title" = "%1$@ not supported";
"screen_link_new_device_error_not_supported_subtitle" = "Your account provider doesn’t support signing into a new device with a QR code.";
"screen_link_new_device_error_not_supported_title" = "QR code not supported";
"screen_link_new_device_error_request_cancelled_subtitle" = "The sign in was cancelled on the other device.";
"screen_link_new_device_error_request_cancelled_title" = "Sign in request cancelled";
"screen_link_new_device_error_request_timeout_subtitle" = "Sign in expired. Please try again.";
"screen_link_new_device_error_request_timeout_title" = "The sign in was not completed in time";
"screen_link_new_device_mobile_step1" = "Open %1$@ on the other device";
"screen_link_new_device_mobile_step2" = "Select %1$@";
"screen_link_new_device_mobile_step2_action" = "“Sign in with QR code”";
"screen_link_new_device_mobile_step3" = "Scan the QR code shown here with the other device";
"screen_link_new_device_mobile_title" = "Open %1$@ on the other device";
"screen_link_new_device_root_desktop_computer" = "Desktop computer";
"screen_link_new_device_root_loading_qr_code" = "Loading QR code…";
"screen_link_new_device_root_mobile_device" = "Mobile device";
"screen_link_new_device_root_title" = "What type of device do you want to link?";
"screen_link_new_device_wrong_number_subtitle" = "Please try again and make sure that you’ve entered the 2-digit code correctly. If the numbers still don’t match then contact your account provider.";
"screen_manage_authorized_spaces_header" = "Spaces where members can join the room without an invitation.";
"screen_manage_authorized_spaces_title" = "Manage spaces";
"screen_manage_authorized_spaces_unknown_space" = "(Unknown space)";
@@ -985,6 +973,8 @@
"screen_qr_code_login_error_cancelled_title" = "Sign in request cancelled";
"screen_qr_code_login_error_declined_subtitle" = "The sign in was declined on the other device.";
"screen_qr_code_login_error_declined_title" = "Sign in declined";
"screen_qr_code_login_error_device_already_signed_in_subtitle" = "You don’t need to do anything else.";
"screen_qr_code_login_error_device_already_signed_in_title" = "Your other device is already signed in";
"screen_qr_code_login_error_expired_subtitle" = "Sign in expired. Please try again.";
"screen_qr_code_login_error_expired_title" = "The sign in was not completed in time";
"screen_qr_code_login_error_linking_not_suported_subtitle" = "Your other device does not support signing in to %@ with a QR code.\n\nTry signing in manually, or scan the QR code with another device.";
@@ -1005,7 +995,6 @@
"screen_qr_code_login_no_camera_permission_state_description" = "You need to give permission for %1$@ to use your device’s camera in order to continue.";
"screen_qr_code_login_no_camera_permission_state_title" = "Allow camera access to scan the QR code";
"screen_qr_code_login_scanning_state_title" = "Scan the QR code";
"screen_qr_code_login_start_over_button" = "Start over";
"screen_qr_code_login_unknown_error_description" = "An unexpected error occurred. Please try again.";
"screen_qr_code_login_verify_code_loading" = "Waiting for your other device";
"screen_qr_code_login_verify_code_subtitle" = "Your account provider may ask for the following code to verify the sign in.";
@@ -1332,7 +1321,6 @@
"troubleshoot_notifications_test_blocked_users_description" = "Checking blocked users";
"troubleshoot_notifications_test_blocked_users_quick_fix" = "View blocked users";
"troubleshoot_notifications_test_blocked_users_result_none" = "No users are blocked.";
"troubleshoot_notifications_test_blocked_users_title" = "Blocked users";
"troubleshoot_notifications_entry_point_section" = "Troubleshoot";
"troubleshoot_notifications_screen_action" = "Run tests";
"troubleshoot_notifications_screen_action_again" = "Run tests again";
@@ -1381,6 +1369,7 @@
"troubleshoot_notifications_test_unified_push_title" = "Check UnifiedPush";
"a11y_poll" = "Poll";
"banner_set_up_recovery_submit" = "Set up recovery";
"common_android_shortcuts_remove_reason_left_room" = "You left the room";
"dialog_file_too_large_to_upload_subtitle" = "The max file size allowed is: %1$@";
"dialog_default_video_quality_selector_title" = "Video upload quality";
"dialog_title_error" = "Error";
@@ -1400,6 +1389,19 @@
"screen_home_tab_spaces" = "Spaces";
"screen_join_room_decline_and_block_button_title" = "Decline and block";
"screen_knock_requests_list_title" = "Requests to join";
"screen_link_new_device_desktop_scanning_title" = "Scan the QR code";
"screen_link_new_device_desktop_step3" = "Scan the QR code with this device";
"screen_link_new_device_desktop_submit" = "Ready to scan";
"screen_link_new_device_error_app_not_supported_subtitle" = "Your account provider does not support %1$@.";
"screen_link_new_device_error_app_not_supported_title" = "%1$@ not supported";
"screen_link_new_device_error_not_supported_title" = "QR code not supported";
"screen_link_new_device_error_request_cancelled_subtitle" = "The sign in was cancelled on the other device.";
"screen_link_new_device_error_request_cancelled_title" = "Sign in request cancelled";
"screen_link_new_device_error_request_timeout_subtitle" = "Sign in expired. Please try again.";
"screen_link_new_device_error_request_timeout_title" = "The sign in was not completed in time";
"screen_link_new_device_mobile_step2" = "Select %1$@";
"screen_link_new_device_mobile_title" = "Open %1$@ on the other device";
"screen_link_new_device_wrong_number_title" = "The numbers don’t match";
"screen_pinned_timeline_screen_title_empty" = "Pinned messages";
"screen_report_room_title" = "Report room";
"screen_room_mentions_at_room_title" = "Everyone";
@@ -1450,6 +1452,7 @@
"screen_notification_settings_mentions_section_title" = "Mentions";
"screen_onboarding_sign_in_to" = "Sign in to %1$@";
"screen_qr_code_login_invalid_scan_state_retry_button" = "Try again";
"screen_qr_code_login_start_over_button" = "Start over";
"screen_recovery_key_change_generate_key_description" = "Do not share this with anyone!";
"screen_recovery_key_confirm_title" = "Enter your recovery key";
"screen_report_content_block_user" = "Block user";
@@ -1496,5 +1499,6 @@
"screen_signout_preference_item" = "Sign out";
"screen_signout_save_recovery_key_title" = "Have you saved your recovery key?";
"timeline_decryption_failure_historical_event_user_not_joined" = "You don't have access to this message";
"troubleshoot_notifications_test_blocked_users_title" = "Blocked users";
"troubleshoot_notifications_entry_point_push_history_title" = "Push history";
"troubleshoot_notifications_entry_point_title" = "Troubleshoot notifications";
@@ -176,7 +176,6 @@
"common_advanced_settings" = "Advanced settings";
"common_an_image" = "an image";
"common_analytics" = "Analytics";
"common_android_shortcuts_remove_reason_left_room" = "You left the room";
"common_android_shortcuts_remove_reason_session_logged_out" = "You were logged out of the session";
"common_appearance" = "Appearance";
"common_audio" = "Audio";
@@ -589,32 +588,21 @@
"screen_leave_space_subtitle_only_last_admin" = "You will not be removed from the following room(s) because you're the only administrator:";
"screen_leave_space_title" = "Leave %1$@?";
"screen_leave_space_title_last_admin" = "You are the only admin for %1$@";
"screen_link_new_device_desktop_scanning_title" = "Scan the QR code";
"screen_link_new_device_desktop_step1" = "Open %1$@ on a laptop or desktop computer";
"screen_link_new_device_desktop_step3" = "Scan the QR code with this device";
"screen_link_new_device_desktop_submit" = "Ready to scan";
"screen_link_new_device_desktop_title" = "Open %1$@ on a desktop computer to get the QR code";
"screen_link_new_device_enter_number_error_numbers_do_not_match" = "The numbers don’t match";
"screen_link_new_device_enter_number_notice" = "Enter 2-digit code";
"screen_link_new_device_enter_number_subtitle" = "This will verify that the connection to your other device is secure.";
"screen_link_new_device_enter_number_title" = "Enter the number shown on your other device";
"screen_link_new_device_error_app_not_supported_subtitle" = "Your account provider does not support %1$@.";
"screen_link_new_device_error_app_not_supported_title" = "%1$@ not supported";
"screen_link_new_device_error_not_supported_subtitle" = "Your account provider doesn’t support signing into a new device with a QR code.";
"screen_link_new_device_error_not_supported_title" = "QR code not supported";
"screen_link_new_device_error_request_cancelled_subtitle" = "The sign in was cancelled on the other device.";
"screen_link_new_device_error_request_cancelled_title" = "Sign in request cancelled";
"screen_link_new_device_error_request_timeout_subtitle" = "Sign in expired. Please try again.";
"screen_link_new_device_error_request_timeout_title" = "The sign in was not completed in time";
"screen_link_new_device_mobile_step1" = "Open %1$@ on the other device";
"screen_link_new_device_mobile_step2" = "Select %1$@";
"screen_link_new_device_mobile_step2_action" = "“Sign in with QR code”";
"screen_link_new_device_mobile_step3" = "Scan the QR code shown here with the other device";
"screen_link_new_device_mobile_title" = "Open %1$@ on the other device";
"screen_link_new_device_root_desktop_computer" = "Desktop computer";
"screen_link_new_device_root_loading_qr_code" = "Loading QR code…";
"screen_link_new_device_root_mobile_device" = "Mobile device";
"screen_link_new_device_root_title" = "What type of device do you want to link?";
"screen_link_new_device_wrong_number_subtitle" = "Please try again and make sure that you’ve entered the 2-digit code correctly. If the numbers still don’t match then contact your account provider.";
"screen_manage_authorized_spaces_header" = "Spaces where members can join the room without an invitation.";
"screen_manage_authorized_spaces_title" = "Manage spaces";
"screen_manage_authorized_spaces_unknown_space" = "(Unknown space)";
@@ -985,6 +973,8 @@
"screen_qr_code_login_error_cancelled_title" = "Sign in request cancelled";
"screen_qr_code_login_error_declined_subtitle" = "The sign in was declined on the other device.";
"screen_qr_code_login_error_declined_title" = "Sign in declined";
"screen_qr_code_login_error_device_already_signed_in_subtitle" = "You don’t need to do anything else.";
"screen_qr_code_login_error_device_already_signed_in_title" = "Your other device is already signed in";
"screen_qr_code_login_error_expired_subtitle" = "Sign in expired. Please try again.";
"screen_qr_code_login_error_expired_title" = "The sign in was not completed in time";
"screen_qr_code_login_error_linking_not_suported_subtitle" = "Your other device does not support signing in to %@ with a QR code.\n\nTry signing in manually, or scan the QR code with another device.";
@@ -1005,7 +995,6 @@
"screen_qr_code_login_no_camera_permission_state_description" = "You need to give permission for %1$@ to use your device’s camera in order to continue.";
"screen_qr_code_login_no_camera_permission_state_title" = "Allow camera access to scan the QR code";
"screen_qr_code_login_scanning_state_title" = "Scan the QR code";
"screen_qr_code_login_start_over_button" = "Start over";
"screen_qr_code_login_unknown_error_description" = "An unexpected error occurred. Please try again.";
"screen_qr_code_login_verify_code_loading" = "Waiting for your other device";
"screen_qr_code_login_verify_code_subtitle" = "Your account provider may ask for the following code to verify the sign in.";
@@ -1332,7 +1321,6 @@
"troubleshoot_notifications_test_blocked_users_description" = "Checking blocked users";
"troubleshoot_notifications_test_blocked_users_quick_fix" = "View blocked users";
"troubleshoot_notifications_test_blocked_users_result_none" = "No users are blocked.";
"troubleshoot_notifications_test_blocked_users_title" = "Blocked users";
"troubleshoot_notifications_entry_point_section" = "Troubleshoot";
"troubleshoot_notifications_screen_action" = "Run tests";
"troubleshoot_notifications_screen_action_again" = "Run tests again";
@@ -1381,6 +1369,7 @@
"troubleshoot_notifications_test_unified_push_title" = "Check UnifiedPush";
"a11y_poll" = "Poll";
"banner_set_up_recovery_submit" = "Set up recovery";
"common_android_shortcuts_remove_reason_left_room" = "You left the room";
"dialog_file_too_large_to_upload_subtitle" = "The max file size allowed is: %1$@";
"dialog_default_video_quality_selector_title" = "Video upload quality";
"dialog_title_error" = "Error";
@@ -1400,6 +1389,19 @@
"screen_home_tab_spaces" = "Spaces";
"screen_join_room_decline_and_block_button_title" = "Decline and block";
"screen_knock_requests_list_title" = "Requests to join";
"screen_link_new_device_desktop_scanning_title" = "Scan the QR code";
"screen_link_new_device_desktop_step3" = "Scan the QR code with this device";
"screen_link_new_device_desktop_submit" = "Ready to scan";
"screen_link_new_device_error_app_not_supported_subtitle" = "Your account provider does not support %1$@.";
"screen_link_new_device_error_app_not_supported_title" = "%1$@ not supported";
"screen_link_new_device_error_not_supported_title" = "QR code not supported";
"screen_link_new_device_error_request_cancelled_subtitle" = "The sign in was cancelled on the other device.";
"screen_link_new_device_error_request_cancelled_title" = "Sign in request cancelled";
"screen_link_new_device_error_request_timeout_subtitle" = "Sign in expired. Please try again.";
"screen_link_new_device_error_request_timeout_title" = "The sign in was not completed in time";
"screen_link_new_device_mobile_step2" = "Select %1$@";
"screen_link_new_device_mobile_title" = "Open %1$@ on the other device";
"screen_link_new_device_wrong_number_title" = "The numbers don’t match";
"screen_pinned_timeline_screen_title_empty" = "Pinned messages";
"screen_report_room_title" = "Report room";
"screen_room_mentions_at_room_title" = "Everyone";
@@ -1450,6 +1452,7 @@
"screen_notification_settings_mentions_section_title" = "Mentions";
"screen_onboarding_sign_in_to" = "Sign in to %1$@";
"screen_qr_code_login_invalid_scan_state_retry_button" = "Try again";
"screen_qr_code_login_start_over_button" = "Start over";
"screen_recovery_key_change_generate_key_description" = "Do not share this with anyone!";
"screen_recovery_key_confirm_title" = "Enter your recovery key";
"screen_report_content_block_user" = "Block user";
@@ -1496,5 +1499,6 @@
"screen_signout_preference_item" = "Sign out";
"screen_signout_save_recovery_key_title" = "Have you saved your recovery key?";
"timeline_decryption_failure_historical_event_user_not_joined" = "You don't have access to this message";
"troubleshoot_notifications_test_blocked_users_title" = "Blocked users";
"troubleshoot_notifications_entry_point_push_history_title" = "Push history";
"troubleshoot_notifications_entry_point_title" = "Troubleshoot notifications";
+8
View File
@@ -1956,6 +1956,10 @@ internal enum L10n {
internal static var screenLinkNewDeviceRootMobileDevice: String { return L10n.tr("Localizable", "screen_link_new_device_root_mobile_device") }
/// What type of device do you want to link?
internal static var screenLinkNewDeviceRootTitle: String { return L10n.tr("Localizable", "screen_link_new_device_root_title") }
/// Please try again and make sure that you’ve entered the 2-digit code correctly. If the numbers still don’t match then contact your account provider.
internal static var screenLinkNewDeviceWrongNumberSubtitle: String { return L10n.tr("Localizable", "screen_link_new_device_wrong_number_subtitle") }
/// The numbers don’t match
internal static var screenLinkNewDeviceWrongNumberTitle: String { return L10n.tr("Localizable", "screen_link_new_device_wrong_number_title") }
/// This account has been deactivated.
internal static var screenLoginErrorDeactivatedAccount: String { return L10n.tr("Localizable", "screen_login_error_deactivated_account") }
/// Incorrect username and/or password
@@ -2188,6 +2192,10 @@ internal enum L10n {
internal static var screenQrCodeLoginErrorDeclinedSubtitle: String { return L10n.tr("Localizable", "screen_qr_code_login_error_declined_subtitle") }
/// Sign in declined
internal static var screenQrCodeLoginErrorDeclinedTitle: String { return L10n.tr("Localizable", "screen_qr_code_login_error_declined_title") }
/// You don’t need to do anything else.
internal static var screenQrCodeLoginErrorDeviceAlreadySignedInSubtitle: String { return L10n.tr("Localizable", "screen_qr_code_login_error_device_already_signed_in_subtitle") }
/// Your other device is already signed in
internal static var screenQrCodeLoginErrorDeviceAlreadySignedInTitle: String { return L10n.tr("Localizable", "screen_qr_code_login_error_device_already_signed_in_title") }
/// Sign in expired. Please try again.
internal static var screenQrCodeLoginErrorExpiredSubtitle: String { return L10n.tr("Localizable", "screen_qr_code_login_error_expired_subtitle") }
/// The sign in was not completed in time
@@ -21,8 +21,10 @@ struct FullscreenDialog<Content: View, BottomContent: View>: View {
/// Padding applied to the top of the content automatically. Use `UIConstants` for preset values.
var topPadding: CGFloat = UIConstants.titleTopPaddingToNavigationBar
/// Padding applied to the content and buttons automatically
/// Padding applied to the content automatically
var horizontalPadding: CGFloat = 16
/// Padding applied to the buttons automatically
var bottomHorizontalPadding: CGFloat = 16
/// The spacing between the content and the buttons.
var spacing: CGFloat = 16
@@ -83,7 +85,7 @@ struct FullscreenDialog<Content: View, BottomContent: View>: View {
VStack(spacing: 0) {
bottomContent()
.readableFrame()
.padding(.horizontal, horizontalPadding)
.padding(.horizontal, bottomHorizontalPadding)
.padding(.top, spacing)
.padding(.bottom, UIConstants.actionButtonBottomPadding)
@@ -113,7 +115,7 @@ struct FullscreenDialog<Content: View, BottomContent: View>: View {
bottomContent()
.readableFrame()
.padding(.horizontal, horizontalPadding)
.padding(.horizontal, bottomHorizontalPadding)
.padding(.bottom, UIConstants.actionButtonBottomPadding)
Spacer()
@@ -12,7 +12,7 @@ struct LinkNewDeviceScreen: View {
@Bindable var context: LinkNewDeviceScreenViewModel.Context
var body: some View {
FullscreenDialog(topPadding: 0) {
FullscreenDialog(topPadding: 24, horizontalPadding: 24) {
mainContent
} bottomContent: {
buttons
@@ -31,7 +31,6 @@ struct LinkNewDeviceScreen: View {
TitleAndIcon(title: L10n.screenLinkNewDeviceRootTitle,
icon: \.computer,
iconStyle: .default)
.padding(.horizontal, -16) // Remove the FullscreenDialog's default padding in this state.
case .notSupported:
TitleAndIcon(title: L10n.screenLinkNewDeviceErrorNotSupportedTitle,
subtitle: L10n.screenLinkNewDeviceErrorNotSupportedSubtitle,
@@ -65,7 +65,7 @@ enum QRCodeLoginState: Equatable {
/// Any full screen error state
case error(ErrorState)
enum ErrorState: Equatable {
enum ErrorState: Equatable, CaseIterable {
case noCameraPermission
case connectionNotSecure
case cancelled
@@ -73,6 +73,8 @@ enum QRCodeLoginState: Equatable {
case expired
case linkingNotSupported
case deviceNotSupported
/// Expected a QR code for a new device, however the processed code belongs to a device that is already signed in.
case deviceAlreadySignedIn
case unknown
}
@@ -91,8 +93,6 @@ enum QRCodeLoginState: Equatable {
case notAllowed(scannedProvider: String, allowedProviders: [String])
/// The QR code has been processed but it belongs to a device not signed in.
case deviceNotSignedIn
/// Expected a QR code for a new device, however the processed code belongs to a device that is already signed in.
case deviceAlreadySignedIn
var title: String {
switch self {
@@ -102,8 +102,6 @@ enum QRCodeLoginState: Equatable {
L10n.screenChangeServerErrorUnauthorizedHomeserverTitle(scannedProvider)
case .deviceNotSignedIn:
L10n.screenQrCodeLoginDeviceNotSignedInScanStateSubtitle
case .deviceAlreadySignedIn:
L10n.screenQrCodeLoginDeviceNotSignedInScanStateSubtitle // FIXME: Update string
}
}
@@ -115,8 +113,6 @@ enum QRCodeLoginState: Equatable {
L10n.screenChangeServerErrorUnauthorizedHomeserverContent(allowedProviders.formatted(.list(type: .and)))
case .deviceNotSignedIn:
L10n.screenQrCodeLoginDeviceNotSignedInScanStateDescription
case .deviceAlreadySignedIn:
L10n.screenQrCodeLoginDeviceNotSignedInScanStateDescription // FIXME: Update string
}
}
}
@@ -129,7 +129,7 @@ class QRCodeLoginScreenViewModel: QRCodeLoginScreenViewModelType, QRCodeLoginScr
case .deviceNotSignedIn:
state.state = .scan(.scanFailed(.deviceNotSignedIn))
case .deviceAlreadySignedIn:
state.state = .scan(.scanFailed(.deviceAlreadySignedIn))
state.state = .error(.deviceAlreadySignedIn)
case .cancelled:
state.state = .error(.cancelled)
case .connectionInsecure:
@@ -31,6 +31,8 @@ struct QRCodeErrorView: View {
L10n.screenQrCodeLoginErrorLinkingNotSuportedTitle
case .deviceNotSupported:
L10n.screenQrCodeLoginErrorSlidingSyncNotSupportedTitle(InfoPlistReader.main.bundleDisplayName)
case .deviceAlreadySignedIn:
L10n.screenQrCodeLoginErrorDeviceAlreadySignedInTitle
case .unknown:
L10n.commonSomethingWentWrong
}
@@ -52,56 +54,43 @@ struct QRCodeErrorView: View {
L10n.screenQrCodeLoginErrorLinkingNotSuportedSubtitle(InfoPlistReader.main.bundleDisplayName)
case .deviceNotSupported:
L10n.screenQrCodeLoginErrorSlidingSyncNotSupportedSubtitle(InfoPlistReader.main.bundleDisplayName)
case .deviceAlreadySignedIn:
L10n.screenQrCodeLoginErrorDeviceAlreadySignedInSubtitle
case .unknown:
L10n.screenQrCodeLoginUnknownErrorDescription
}
}
var body: some View {
FullscreenDialog {
header
} bottomContent: {
footer
var icon: KeyPath<CompoundIcons, Image> {
switch errorState {
case .noCameraPermission: \.takePhotoSolid
case .deviceAlreadySignedIn: \.checkCircleSolid
default: \.errorSolid
}
}
var iconStyle: BigIcon.Style {
switch errorState {
case .noCameraPermission: .defaultSolid
case .deviceAlreadySignedIn: .successSolid
default: .alertSolid
}
}
var body: some View {
FullscreenDialog(topPadding: 24, horizontalPadding: 24) {
mainContent
} bottomContent: {
buttons
}
.padding(.horizontal, 24)
}
@ViewBuilder
private var header: some View {
private var mainContent: some View {
switch errorState {
case .noCameraPermission:
VStack(spacing: 16) {
BigIcon(icon: \.takePhotoSolid, style: .defaultSolid)
VStack(spacing: 8) {
Text(title)
.foregroundColor(.compound.textPrimary)
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
Text(subtitle)
.foregroundColor(.compound.textSecondary)
.font(.compound.bodyMD)
.multilineTextAlignment(.center)
}
}
case .connectionNotSecure:
VStack(spacing: 40) {
VStack(spacing: 16) {
BigIcon(icon: \.errorSolid, style: .alertSolid)
VStack(spacing: 8) {
Text(title)
.foregroundColor(.compound.textPrimary)
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
Text(subtitle)
.foregroundColor(.compound.textSecondary)
.font(.compound.bodyMD)
.multilineTextAlignment(.center)
}
}
header
VStack(spacing: 24) {
Text(L10n.screenQrCodeLoginConnectionNoteSecureStateListHeader)
@@ -117,31 +106,19 @@ struct QRCodeErrorView: View {
}
}
default:
simpleErrorStack
header
}
}
@ViewBuilder
private var simpleErrorStack: some View {
VStack(spacing: 16) {
BigIcon(icon: \.errorSolid, style: .alertSolid)
VStack(spacing: 8) {
Text(title)
.foregroundColor(.compound.textPrimary)
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
Text(subtitle)
.foregroundColor(.compound.textSecondary)
.font(.compound.bodyMD)
.multilineTextAlignment(.center)
}
}
var header: some View {
TitleAndIcon(title: title,
subtitle: subtitle,
icon: icon,
iconStyle: iconStyle)
}
@ViewBuilder
private var footer: some View {
private var buttons: some View {
switch errorState {
case .noCameraPermission:
Button(L10n.screenQrCodeLoginNoCameraPermissionButton) {
@@ -172,6 +149,11 @@ struct QRCodeErrorView: View {
}
.buttonStyle(.compound(.tertiary))
}
case .deviceAlreadySignedIn:
Button(L10n.actionContinue) {
action(.cancel)
}
.buttonStyle(.compound(.primary))
}
}
}
@@ -180,42 +162,34 @@ struct QRCodeErrorView: View {
struct QRCodeErrorView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
QRCodeErrorViewSnapshot(errorState: .noCameraPermission, canSignInManually: false)
.previewDisplayName("No Camera Permission")
ForEach(QRCodeLoginState.ErrorState.allCases, id: \.self) { errorState in
NavigationStack {
QRCodeErrorView(errorState: errorState, canSignInManually: true) { _ in }
.toolbar(.visible, for: .navigationBar)
}
.previewDisplayName(errorState.previewDisplayName)
}
QRCodeErrorViewSnapshot(errorState: .connectionNotSecure, canSignInManually: false)
.previewDisplayName("Connection not secure")
QRCodeErrorViewSnapshot(errorState: .linkingNotSupported, canSignInManually: true)
.previewDisplayName("Linking unsupported")
QRCodeErrorViewSnapshot(errorState: .linkingNotSupported, canSignInManually: false)
.previewDisplayName("Linking unsupported restricted flow")
QRCodeErrorViewSnapshot(errorState: .cancelled, canSignInManually: false)
.previewDisplayName("Cancelled")
QRCodeErrorViewSnapshot(errorState: .declined, canSignInManually: false)
.previewDisplayName("Declined")
QRCodeErrorViewSnapshot(errorState: .expired, canSignInManually: false)
.previewDisplayName("Expired")
QRCodeErrorViewSnapshot(errorState: .deviceNotSupported, canSignInManually: false)
.previewDisplayName("Device not supported")
QRCodeErrorViewSnapshot(errorState: .unknown, canSignInManually: false)
.previewDisplayName("Unknown error")
NavigationStack {
QRCodeErrorView(errorState: .linkingNotSupported, canSignInManually: false) { _ in }
.toolbar(.visible, for: .navigationBar)
}
.previewDisplayName("Linking unsupported restricted flow")
}
}
private struct QRCodeErrorViewSnapshot: View {
let errorState: QRCodeLoginState.ErrorState
let canSignInManually: Bool
var body: some View {
NavigationStack {
QRCodeErrorView(errorState: errorState, canSignInManually: canSignInManually) { _ in }
.toolbar(.visible, for: .navigationBar)
private extension QRCodeLoginState.ErrorState {
var previewDisplayName: String {
switch self {
case .noCameraPermission: "No Camera Permission"
case .connectionNotSecure: "Connection not secure"
case .linkingNotSupported: "Linking unsupported"
case .cancelled: "Cancelled"
case .declined: "Declined"
case .expired: "Expired"
case .deviceNotSupported: "Device not supported"
case .deviceAlreadySignedIn: "Device already signed in"
case .unknown: "Unknown error"
}
}
}
@@ -49,24 +49,12 @@ struct QRCodeLoginScreen: View {
}
private var initialContent: some View {
FullscreenDialog {
FullscreenDialog(topPadding: 24, horizontalPadding: 24) {
VStack(alignment: .leading, spacing: 40) {
VStack(spacing: 16) {
BigIcon(icon: \.computer, style: .default)
VStack(spacing: 8) {
Text(L10n.screenQrCodeLoginInitialStateTitle(InfoPlistReader.main.productionAppName))
.foregroundColor(.compound.textPrimary)
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
Text(L10n.screenQrCodeLoginInitialStateSubtitle)
.font(.compound.bodyMD)
.multilineTextAlignment(.center)
.foregroundColor(.compound.textSecondary)
}
}
.padding(.horizontal, 24)
TitleAndIcon(title: L10n.screenQrCodeLoginInitialStateTitle(InfoPlistReader.main.productionAppName),
subtitle: L10n.screenQrCodeLoginInitialStateSubtitle,
icon: \.computer,
iconStyle: .default)
SFNumberedListView(items: context.viewState.initialStateListItems)
}
@@ -81,17 +69,21 @@ struct QRCodeLoginScreen: View {
@ViewBuilder
private var displayCodeContent: some View {
if case let .displayCode(displayCodeState) = context.viewState.state {
FullscreenDialog {
FullscreenDialog(topPadding: 24) {
VStack(spacing: 32) {
VStack(spacing: 40) {
VStack(spacing: 24) {
displayCodeHeader(state: displayCodeState)
.padding(.horizontal, 8)
PINTextField(pinCode: .constant(displayCodeState.code),
maxLength: displayCodeState.code.count,
size: .small)
.disabled(true)
}
VStack(spacing: 4) {
ProgressView()
Text(L10n.screenQrCodeLoginVerifyCodeLoading)
.foregroundColor(.compound.textSecondary)
.font(.compound.bodySM)
@@ -104,63 +96,36 @@ struct QRCodeLoginScreen: View {
}
.buttonStyle(.compound(.secondary))
}
.padding(.horizontal, 24)
}
}
private func displayCodeHeader(state: QRCodeLoginState.QRCodeLoginDisplayCodeState) -> some View {
VStack(spacing: 16) {
switch state {
case .deviceCode:
BigIcon(icon: \.computer, style: .default)
VStack(spacing: 8) {
Text(L10n.screenQrCodeLoginDeviceCodeTitle)
.foregroundColor(.compound.textPrimary)
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
Text(L10n.screenQrCodeLoginDeviceCodeSubtitle)
.foregroundColor(.compound.textSecondary)
.font(.compound.bodyMD)
.multilineTextAlignment(.center)
}
case .verificationCode:
BigIcon(icon: \.lock, style: .default)
VStack(spacing: 8) {
Text(L10n.screenQrCodeLoginVerifyCodeTitle)
.foregroundColor(.compound.textPrimary)
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
Text(L10n.screenQrCodeLoginVerifyCodeSubtitle)
.foregroundColor(.compound.textSecondary)
.font(.compound.bodyMD)
.multilineTextAlignment(.center)
}
}
switch state {
case .deviceCode:
TitleAndIcon(title: L10n.screenQrCodeLoginDeviceCodeTitle,
subtitle: L10n.screenQrCodeLoginDeviceCodeSubtitle,
icon: \.computer,
iconStyle: .default)
case .verificationCode:
TitleAndIcon(title: L10n.screenQrCodeLoginVerifyCodeTitle,
subtitle: L10n.screenQrCodeLoginVerifyCodeSubtitle,
icon: \.lock,
iconStyle: .default)
}
}
private var qrScanContent: some View {
FullscreenDialog {
FullscreenDialog(topPadding: 24) {
VStack(spacing: 40) {
VStack(spacing: 16) {
BigIcon(icon: \.takePhotoSolid, style: .default)
Text(L10n.screenQrCodeLoginScanningStateTitle)
.foregroundColor(.compound.textPrimary)
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
}
TitleAndIcon(title: L10n.screenQrCodeLoginScanningStateTitle,
icon: \.takePhotoSolid,
iconStyle: .default)
qrScanner
}
} bottomContent: {
qrScanFooter
}
.padding(.horizontal, 24)
}
@ViewBuilder