mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
General tidy-up related to QR codes. (#4865)
* Tidy-up Rust to Swift mapping. * Refactor out a dedicated QRCodeErrorView. * Use the new QRCodeErrorView for most error state snapshots. * Simplify QRCodeErrorView structure. Also updates the background to match the designs. * Fix a small compile error in the unit tests.
This commit is contained in:
@@ -419,6 +419,10 @@ extension AccessibilityTests {
|
||||
try await performAccessibilityAudit(named: "PollView_Previews")
|
||||
}
|
||||
|
||||
func testQRCodeErrorView() async throws {
|
||||
try await performAccessibilityAudit(named: "QRCodeErrorView_Previews")
|
||||
}
|
||||
|
||||
func testQRCodeLoginScreen() async throws {
|
||||
try await performAccessibilityAudit(named: "QRCodeLoginScreen_Previews")
|
||||
}
|
||||
|
||||
@@ -949,6 +949,7 @@
|
||||
A4B0BAD62A12ED76BD611B79 /* BadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FA515B3B0D61EF1E907D2D /* BadgeView.swift */; };
|
||||
A4B123C635F70DDD4BC2FAC9 /* BlockedUsersScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E76A706B3EEA32B882DA5E2D /* BlockedUsersScreenViewModelProtocol.swift */; };
|
||||
A4C29D373986AFE4559696D5 /* SecureBackupKeyBackupScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4525E8C0FBDD27D1ACE90952 /* SecureBackupKeyBackupScreenViewModelProtocol.swift */; };
|
||||
A4CCFCD320600E27AF60EAC9 /* QRCodeErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3082001D373607455CB08A1 /* QRCodeErrorView.swift */; };
|
||||
A4E885358D7DD5A072A06824 /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 3853B78FB8531B83936C5DA6 /* SwiftState */; };
|
||||
A51C65E5A3C9F2464A91A380 /* AuthenticationClientBuilderFactoryMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0554FEA301486A8CFA475D5A /* AuthenticationClientBuilderFactoryMock.swift */; };
|
||||
A52090A4FE0DB826578DFC03 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0724EBDFE8BB4C9E5547C57D /* Client.swift */; };
|
||||
@@ -2833,6 +2834,7 @@
|
||||
F276F31C1AEC19E52B951B62 /* SendInviteConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendInviteConfirmationView.swift; sourceTree = "<group>"; };
|
||||
F2DC502B1A566E99969D34DD /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
F2E4EF80DFB8FE7C4469B15D /* RoomDirectorySearchScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchScreen.swift; sourceTree = "<group>"; };
|
||||
F3082001D373607455CB08A1 /* QRCodeErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeErrorView.swift; sourceTree = "<group>"; };
|
||||
F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = "<group>"; };
|
||||
F320003F490B11F808ECC5E9 /* JoinedMembersBadgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinedMembersBadgeView.swift; sourceTree = "<group>"; };
|
||||
F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
@@ -6008,6 +6010,7 @@
|
||||
C844840F3DD48A154C65AE0C /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F3082001D373607455CB08A1 /* QRCodeErrorView.swift */,
|
||||
BFA9EA59D5C0DA1BFC7B3621 /* QRCodeLoginScreen.swift */,
|
||||
92DB574F954CC2B40F7BE892 /* QRCodeScannerView.swift */,
|
||||
);
|
||||
@@ -8222,6 +8225,7 @@
|
||||
C7ABEBECDC513F7887DACF66 /* ProgressMaskModifier.swift in Sources */,
|
||||
9B356742E035D90A8BB5CABE /* ProposedViewSize.swift in Sources */,
|
||||
2835FD52F3F618D07F799B3D /* Publisher.swift in Sources */,
|
||||
A4CCFCD320600E27AF60EAC9 /* QRCodeErrorView.swift in Sources */,
|
||||
4949C8C12669D1B5E082366E /* QRCodeLoginScreen.swift in Sources */,
|
||||
4D23D41B8109E010304050F8 /* QRCodeLoginScreenCoordinator.swift in Sources */,
|
||||
46FCD999E92D9717D24AAB94 /* QRCodeLoginScreenModels.swift in Sources */,
|
||||
|
||||
@@ -112,6 +112,7 @@ enum TestablePreviewsDictionary {
|
||||
"PollOptionView_Previews" : PollOptionView_Previews.self,
|
||||
"PollRoomTimelineView_Previews" : PollRoomTimelineView_Previews.self,
|
||||
"PollView_Previews" : PollView_Previews.self,
|
||||
"QRCodeErrorView_Previews" : QRCodeErrorView_Previews.self,
|
||||
"QRCodeLoginScreen_Previews" : QRCodeLoginScreen_Previews.self,
|
||||
"ReactionsSummaryView_Previews" : ReactionsSummaryView_Previews.self,
|
||||
"ReadMarkerRoomTimelineView_Previews" : ReadMarkerRoomTimelineView_Previews.self,
|
||||
|
||||
@@ -52,21 +52,20 @@ struct QRCodeLoginScreenViewStateBindings {
|
||||
enum QRCodeLoginScreenViewAction {
|
||||
case cancel
|
||||
case startScan
|
||||
case signInManually
|
||||
case openSettings
|
||||
case errorAction(QRCodeErrorView.Action)
|
||||
}
|
||||
|
||||
enum QRCodeLoginState: Equatable {
|
||||
/// Initial state where the user is informed how to perform the scan
|
||||
case initial
|
||||
/// The camera is scanning
|
||||
case scan(QRCodeLoginScanningState)
|
||||
case scan(ScanningState)
|
||||
/// Codes are being shown
|
||||
case displayCode(QRCodeLoginDisplayCodeState)
|
||||
/// Any full screen error state
|
||||
case error(QRCodeLoginErrorState)
|
||||
case error(ErrorState)
|
||||
|
||||
enum QRCodeLoginErrorState: Equatable {
|
||||
enum ErrorState: Equatable {
|
||||
case noCameraPermission
|
||||
case connectionNotSecure
|
||||
case cancelled
|
||||
@@ -77,21 +76,23 @@ enum QRCodeLoginState: Equatable {
|
||||
case unknown
|
||||
}
|
||||
|
||||
enum QRCodeLoginScanningState: Equatable {
|
||||
/// the QR code is scanning
|
||||
enum ScanningState: Equatable {
|
||||
/// The QR code is scanning.
|
||||
case scanning
|
||||
/// the QR code has been detected and is being processed
|
||||
/// The QR code has been detected and is being processed.
|
||||
case connecting
|
||||
/// the QR code was scanned, but an error occurred.
|
||||
/// The QR code was scanned, but an error occurred.
|
||||
case scanFailed(Error)
|
||||
|
||||
enum Error: Equatable {
|
||||
/// the QR code has been processed and is invalid
|
||||
/// The QR code has been processed and is invalid.
|
||||
case invalid
|
||||
/// the QR code has been processed but it is for an account provider that isn't allowed.
|
||||
/// The QR code has been processed but it is for an account provider that isn't allowed.
|
||||
case notAllowed(scannedProvider: String, allowedProviders: [String])
|
||||
/// the QR code has been processed but it belongs to a device not signed in
|
||||
/// 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 {
|
||||
@@ -101,6 +102,8 @@ enum QRCodeLoginState: Equatable {
|
||||
L10n.screenChangeServerErrorUnauthorizedHomeserverTitle(scannedProvider)
|
||||
case .deviceNotSignedIn:
|
||||
L10n.screenQrCodeLoginDeviceNotSignedInScanStateSubtitle
|
||||
case .deviceAlreadySignedIn:
|
||||
L10n.screenQrCodeLoginDeviceNotSignedInScanStateSubtitle // FIXME: Update string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +115,8 @@ enum QRCodeLoginState: Equatable {
|
||||
L10n.screenChangeServerErrorUnauthorizedHomeserverContent(allowedProviders.formatted(.list(type: .and)))
|
||||
case .deviceNotSignedIn:
|
||||
L10n.screenQrCodeLoginDeviceNotSignedInScanStateDescription
|
||||
case .deviceAlreadySignedIn:
|
||||
L10n.screenQrCodeLoginDeviceNotSignedInScanStateDescription // FIXME: Update string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,13 +35,13 @@ class QRCodeLoginScreenViewModel: QRCodeLoginScreenViewModelType, QRCodeLoginScr
|
||||
|
||||
override func process(viewAction: QRCodeLoginScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .cancel:
|
||||
case .cancel, .errorAction(.cancel):
|
||||
actionsSubject.send(.cancel)
|
||||
case .startScan:
|
||||
case .startScan, .errorAction(.startScan):
|
||||
Task { await startScanIfPossible() }
|
||||
case .openSettings:
|
||||
case .errorAction(.openSettings):
|
||||
appMediator.openAppSettings()
|
||||
case .signInManually:
|
||||
case .errorAction(.signInManually):
|
||||
actionsSubject.send(.signInManually)
|
||||
}
|
||||
}
|
||||
@@ -128,6 +128,8 @@ class QRCodeLoginScreenViewModel: QRCodeLoginScreenViewModelType, QRCodeLoginScr
|
||||
state.state = .scan(.scanFailed(.notAllowed(scannedProvider: scannedProvider, allowedProviders: allowedProviders)))
|
||||
case .deviceNotSignedIn:
|
||||
state.state = .scan(.scanFailed(.deviceNotSignedIn))
|
||||
case .deviceAlreadySignedIn:
|
||||
state.state = .scan(.scanFailed(.deviceAlreadySignedIn))
|
||||
case .cancelled:
|
||||
state.state = .error(.cancelled)
|
||||
case .connectionInsecure:
|
||||
@@ -140,7 +142,7 @@ class QRCodeLoginScreenViewModel: QRCodeLoginScreenViewModelType, QRCodeLoginScr
|
||||
state.state = .error(.expired)
|
||||
case .deviceNotSupported:
|
||||
state.state = .error(.deviceNotSupported)
|
||||
case .deviceAlreadySignedIn, .unknown:
|
||||
case .unknown:
|
||||
state.state = .error(.unknown)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
//
|
||||
// Copyright 2025 Element Creations Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct QRCodeErrorView: View {
|
||||
let errorState: QRCodeLoginState.ErrorState
|
||||
let canSignInManually: Bool
|
||||
|
||||
enum Action { case openSettings, startScan, signInManually, cancel }
|
||||
let action: (Action) -> Void
|
||||
|
||||
var title: String {
|
||||
switch errorState {
|
||||
case .noCameraPermission:
|
||||
L10n.screenQrCodeLoginNoCameraPermissionStateTitle
|
||||
case .connectionNotSecure:
|
||||
L10n.screenQrCodeLoginConnectionNoteSecureStateTitle
|
||||
case .cancelled:
|
||||
L10n.screenQrCodeLoginErrorCancelledTitle
|
||||
case .declined:
|
||||
L10n.screenQrCodeLoginErrorDeclinedTitle
|
||||
case .expired:
|
||||
L10n.screenQrCodeLoginErrorExpiredTitle
|
||||
case .linkingNotSupported:
|
||||
L10n.screenQrCodeLoginErrorLinkingNotSuportedTitle
|
||||
case .deviceNotSupported:
|
||||
L10n.screenQrCodeLoginErrorSlidingSyncNotSupportedTitle(InfoPlistReader.main.bundleDisplayName)
|
||||
case .unknown:
|
||||
L10n.commonSomethingWentWrong
|
||||
}
|
||||
}
|
||||
|
||||
var subtitle: String {
|
||||
switch errorState {
|
||||
case .noCameraPermission:
|
||||
L10n.screenQrCodeLoginNoCameraPermissionStateDescription(InfoPlistReader.main.productionAppName)
|
||||
case .connectionNotSecure:
|
||||
L10n.screenQrCodeLoginConnectionNoteSecureStateDescription
|
||||
case .cancelled:
|
||||
L10n.screenQrCodeLoginErrorCancelledSubtitle
|
||||
case .declined:
|
||||
L10n.screenQrCodeLoginErrorDeclinedSubtitle
|
||||
case .expired:
|
||||
L10n.screenQrCodeLoginErrorExpiredSubtitle
|
||||
case .linkingNotSupported:
|
||||
L10n.screenQrCodeLoginErrorLinkingNotSuportedSubtitle(InfoPlistReader.main.bundleDisplayName)
|
||||
case .deviceNotSupported:
|
||||
L10n.screenQrCodeLoginErrorSlidingSyncNotSupportedSubtitle(InfoPlistReader.main.bundleDisplayName)
|
||||
case .unknown:
|
||||
L10n.screenQrCodeLoginUnknownErrorDescription
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
FullscreenDialog {
|
||||
header
|
||||
} bottomContent: {
|
||||
footer
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var header: 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)
|
||||
}
|
||||
}
|
||||
|
||||
VStack(spacing: 24) {
|
||||
Text(L10n.screenQrCodeLoginConnectionNoteSecureStateListHeader)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.font(.compound.bodyLGSemibold)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
SFNumberedListView(items: [
|
||||
AttributedString(L10n.screenQrCodeLoginConnectionNoteSecureStateListItem1),
|
||||
AttributedString(L10n.screenQrCodeLoginConnectionNoteSecureStateListItem2),
|
||||
AttributedString(L10n.screenQrCodeLoginConnectionNoteSecureStateListItem3)
|
||||
])
|
||||
}
|
||||
}
|
||||
default:
|
||||
simpleErrorStack
|
||||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var footer: some View {
|
||||
switch errorState {
|
||||
case .noCameraPermission:
|
||||
Button(L10n.screenQrCodeLoginNoCameraPermissionButton) {
|
||||
action(.openSettings)
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
case .connectionNotSecure, .unknown, .expired, .declined, .deviceNotSupported:
|
||||
Button(L10n.screenQrCodeLoginStartOverButton) {
|
||||
action(.startScan)
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
case .cancelled:
|
||||
Button(L10n.actionTryAgain) {
|
||||
action(.startScan)
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
case .linkingNotSupported:
|
||||
VStack(spacing: 16) {
|
||||
if canSignInManually {
|
||||
Button(L10n.screenOnboardingSignInManually) {
|
||||
action(.signInManually)
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
}
|
||||
|
||||
Button(L10n.actionCancel) {
|
||||
action(.cancel)
|
||||
}
|
||||
.buttonStyle(.compound(.tertiary))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct QRCodeErrorView_Previews: PreviewProvider, TestablePreview {
|
||||
static var previews: some View {
|
||||
QRCodeErrorViewSnapshot(errorState: .noCameraPermission, canSignInManually: false)
|
||||
.previewDisplayName("No Camera Permission")
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,13 +13,21 @@ struct QRCodeLoginScreen: View {
|
||||
@ObservedObject var context: QRCodeLoginScreenViewModel.Context
|
||||
@State private var qrFrame = CGRect.zero
|
||||
|
||||
var backgroundStyle: Color {
|
||||
if case .error = context.viewState.state {
|
||||
.compound.bgCanvasDefault
|
||||
} else {
|
||||
.compound.bgSubtleSecondary
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
mainContent
|
||||
.toolbar { toolbar }
|
||||
.toolbar(.visible, for: .navigationBar)
|
||||
.background()
|
||||
.backgroundStyle(.compound.bgSubtleSecondary)
|
||||
.backgroundStyle(backgroundStyle)
|
||||
.interactiveDismissDisabled()
|
||||
}
|
||||
}
|
||||
@@ -33,8 +41,10 @@ struct QRCodeLoginScreen: View {
|
||||
qrScanContent
|
||||
case .displayCode:
|
||||
displayCodeContent
|
||||
case .error:
|
||||
errorContent
|
||||
case .error(let errorState):
|
||||
QRCodeErrorView(errorState: errorState, canSignInManually: context.viewState.canSignInManually) { action in
|
||||
context.send(viewAction: .errorAction(action))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,157 +228,6 @@ struct QRCodeLoginScreen: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var errorContent: some View {
|
||||
if case let .error(errorState) = context.viewState.state {
|
||||
FullscreenDialog {
|
||||
errorContentHeader(errorState: errorState)
|
||||
} bottomContent: {
|
||||
errorContentFooter(errorState: errorState)
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func errorContentHeader(errorState: QRCodeLoginState.QRCodeLoginErrorState) -> some View {
|
||||
switch errorState {
|
||||
case .noCameraPermission:
|
||||
VStack(spacing: 16) {
|
||||
BigIcon(icon: \.takePhotoSolid, style: .default)
|
||||
|
||||
VStack(spacing: 8) {
|
||||
Text(L10n.screenQrCodeLoginNoCameraPermissionStateTitle)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.font(.compound.headingMDBold)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Text(L10n.screenQrCodeLoginNoCameraPermissionStateDescription(InfoPlistReader.main.productionAppName))
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.font(.compound.bodyMD)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
}
|
||||
case .connectionNotSecure:
|
||||
VStack(spacing: 40) {
|
||||
VStack(spacing: 16) {
|
||||
BigIcon(icon: \.errorSolid, style: .alert)
|
||||
|
||||
VStack(spacing: 8) {
|
||||
Text(L10n.screenQrCodeLoginConnectionNoteSecureStateTitle)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.font(.compound.headingMDBold)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Text(L10n.screenQrCodeLoginConnectionNoteSecureStateDescription)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.font(.compound.bodyMD)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
}
|
||||
|
||||
VStack(spacing: 24) {
|
||||
Text(L10n.screenQrCodeLoginConnectionNoteSecureStateListHeader)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.font(.compound.bodyLGSemibold)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
SFNumberedListView(items: context.viewState.connectionNotSecureListItems)
|
||||
}
|
||||
}
|
||||
default:
|
||||
simpleErrorStack(errorState: errorState)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func simpleErrorStack(errorState: QRCodeLoginState.QRCodeLoginErrorState) -> some View {
|
||||
let title = switch errorState {
|
||||
case .cancelled:
|
||||
L10n.screenQrCodeLoginErrorCancelledTitle
|
||||
case .declined:
|
||||
L10n.screenQrCodeLoginErrorDeclinedTitle
|
||||
case .expired:
|
||||
L10n.screenQrCodeLoginErrorExpiredTitle
|
||||
case .linkingNotSupported:
|
||||
L10n.screenQrCodeLoginErrorLinkingNotSuportedTitle
|
||||
case .deviceNotSupported:
|
||||
L10n.screenQrCodeLoginErrorSlidingSyncNotSupportedTitle(InfoPlistReader.main.bundleDisplayName)
|
||||
case .unknown:
|
||||
L10n.commonSomethingWentWrong
|
||||
default:
|
||||
fatalError("This should not be displayed")
|
||||
}
|
||||
|
||||
let subtitle: String = switch errorState {
|
||||
case .cancelled:
|
||||
L10n.screenQrCodeLoginErrorCancelledSubtitle
|
||||
case .declined:
|
||||
L10n.screenQrCodeLoginErrorDeclinedSubtitle
|
||||
case .expired:
|
||||
L10n.screenQrCodeLoginErrorExpiredSubtitle
|
||||
case .linkingNotSupported:
|
||||
L10n.screenQrCodeLoginErrorLinkingNotSuportedSubtitle(InfoPlistReader.main.bundleDisplayName)
|
||||
case .deviceNotSupported:
|
||||
L10n.screenQrCodeLoginErrorSlidingSyncNotSupportedSubtitle(InfoPlistReader.main.bundleDisplayName)
|
||||
case .unknown:
|
||||
L10n.screenQrCodeLoginUnknownErrorDescription
|
||||
default:
|
||||
fatalError("This should not be displayed")
|
||||
}
|
||||
|
||||
VStack(spacing: 16) {
|
||||
BigIcon(icon: \.errorSolid, style: .alert)
|
||||
|
||||
VStack(spacing: 8) {
|
||||
Text(title)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.font(.compound.headingMDBold)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Text(subtitle)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.font(.compound.bodyMD)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func errorContentFooter(errorState: QRCodeLoginState.QRCodeLoginErrorState) -> some View {
|
||||
switch errorState {
|
||||
case .noCameraPermission:
|
||||
Button(L10n.screenQrCodeLoginNoCameraPermissionButton) {
|
||||
context.send(viewAction: .openSettings)
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
case .connectionNotSecure, .unknown, .expired, .declined, .deviceNotSupported:
|
||||
Button(L10n.screenQrCodeLoginStartOverButton) {
|
||||
context.send(viewAction: .startScan)
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
case .cancelled:
|
||||
Button(L10n.actionTryAgain) {
|
||||
context.send(viewAction: .startScan)
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
case .linkingNotSupported:
|
||||
VStack(spacing: 16) {
|
||||
if context.viewState.canSignInManually {
|
||||
Button(L10n.screenOnboardingSignInManually) {
|
||||
context.send(viewAction: .signInManually)
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
}
|
||||
|
||||
Button(L10n.actionCancel) {
|
||||
context.send(viewAction: .cancel)
|
||||
}
|
||||
.buttonStyle(.compound(.tertiary))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct QRScannerViewOverlay: View {
|
||||
@@ -420,23 +279,8 @@ struct QRCodeLoginScreen_Previews: PreviewProvider, TestablePreview {
|
||||
|
||||
static let verificationCodeStateViewModel = QRCodeLoginScreenViewModel.mock(state: .displayCode(.verificationCode("123456")))
|
||||
|
||||
// Errors
|
||||
static let noCameraPermissionStateViewModel = QRCodeLoginScreenViewModel.mock(state: .error(.noCameraPermission))
|
||||
|
||||
static let connectionNotSecureStateViewModel = QRCodeLoginScreenViewModel.mock(state: .error(.connectionNotSecure))
|
||||
|
||||
static let linkingUnsupportedStateViewModel = QRCodeLoginScreenViewModel.mock(state: .error(.linkingNotSupported))
|
||||
static let linkingUnsupportedRestrictedFlowViewModel = QRCodeLoginScreenViewModel.mock(state: .error(.linkingNotSupported), canSignInManually: false)
|
||||
|
||||
static let cancelledStateViewModel = QRCodeLoginScreenViewModel.mock(state: .error(.cancelled))
|
||||
|
||||
static let declinedStateViewModel = QRCodeLoginScreenViewModel.mock(state: .error(.declined))
|
||||
|
||||
static let expiredStateViewModel = QRCodeLoginScreenViewModel.mock(state: .error(.expired))
|
||||
|
||||
static let deviceNoSupportedViewModel = QRCodeLoginScreenViewModel.mock(state: .error(.deviceNotSupported))
|
||||
|
||||
static let unknownErrorStateViewModel = QRCodeLoginScreenViewModel.mock(state: .error(.unknown))
|
||||
// Errors (no need to test them all QRCodeErrorView covers that).
|
||||
static let errorStateViewModel = QRCodeLoginScreenViewModel.mock(state: .error(.declined))
|
||||
|
||||
static var previews: some View {
|
||||
QRCodeLoginScreen(context: initialStateViewModel.context)
|
||||
@@ -463,30 +307,7 @@ struct QRCodeLoginScreen_Previews: PreviewProvider, TestablePreview {
|
||||
QRCodeLoginScreen(context: verificationCodeStateViewModel.context)
|
||||
.previewDisplayName("Verification code")
|
||||
|
||||
QRCodeLoginScreen(context: noCameraPermissionStateViewModel.context)
|
||||
.previewDisplayName("No Camera Permission")
|
||||
|
||||
QRCodeLoginScreen(context: connectionNotSecureStateViewModel.context)
|
||||
.previewDisplayName("Connection not secure")
|
||||
|
||||
QRCodeLoginScreen(context: linkingUnsupportedStateViewModel.context)
|
||||
.previewDisplayName("Linking unsupported")
|
||||
QRCodeLoginScreen(context: linkingUnsupportedRestrictedFlowViewModel.context)
|
||||
.previewDisplayName("Linking unsupported restricted flow")
|
||||
|
||||
QRCodeLoginScreen(context: cancelledStateViewModel.context)
|
||||
.previewDisplayName("Cancelled")
|
||||
|
||||
QRCodeLoginScreen(context: declinedStateViewModel.context)
|
||||
.previewDisplayName("Declined")
|
||||
|
||||
QRCodeLoginScreen(context: expiredStateViewModel.context)
|
||||
.previewDisplayName("Expired")
|
||||
|
||||
QRCodeLoginScreen(context: deviceNoSupportedViewModel.context)
|
||||
.previewDisplayName("Device not supported")
|
||||
|
||||
QRCodeLoginScreen(context: unknownErrorStateViewModel.context)
|
||||
.previewDisplayName("Unknown error")
|
||||
QRCodeLoginScreen(context: errorStateViewModel.context)
|
||||
.previewDisplayName("Error")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
import CoreImage.CIFilterBuiltins
|
||||
import MatrixRustSDK
|
||||
import SwiftUI
|
||||
|
||||
class LinkNewDeviceService {
|
||||
typealias GenerateProgressPublisher = CurrentValuePublisher<GenerateProgress, QRCodeLoginError>
|
||||
@@ -15,9 +16,9 @@ class LinkNewDeviceService {
|
||||
|
||||
enum GenerateProgress {
|
||||
case starting
|
||||
case qrReady(QrCodeData)
|
||||
case qrReady(UIImage)
|
||||
case qrScanned(CheckCodeSenderProtocol)
|
||||
case waitingForAuthorisation(verificationURI: String)
|
||||
case waitingForAuthorisation(verificationURL: URL)
|
||||
case syncingSecrets
|
||||
case done
|
||||
}
|
||||
@@ -25,7 +26,7 @@ class LinkNewDeviceService {
|
||||
enum ScanProgress {
|
||||
case starting
|
||||
case establishingSecureChannel(checkCode: UInt8, checkCodeString: String)
|
||||
case waitingForAuth(verificationURI: String)
|
||||
case waitingForAuthorisation(verificationURL: URL)
|
||||
case syncingSecrets
|
||||
case done
|
||||
}
|
||||
@@ -38,7 +39,14 @@ class LinkNewDeviceService {
|
||||
|
||||
func generateQRCode() -> GenerateProgressPublisher {
|
||||
let progressSubject = CurrentValueSubject<GenerateProgress, QRCodeLoginError>(.starting)
|
||||
let listener = SDKListener { progressSubject.send(.init(rustProgress: $0)) }
|
||||
let listener = SDKListener {
|
||||
do {
|
||||
try progressSubject.send(.init(rustProgress: $0))
|
||||
} catch {
|
||||
MXLog.error("Invalid GenerateProgress")
|
||||
progressSubject.send(completion: .failure(.unknown))
|
||||
}
|
||||
}
|
||||
|
||||
Task {
|
||||
do {
|
||||
@@ -58,7 +66,14 @@ class LinkNewDeviceService {
|
||||
|
||||
func scanQRCode(_ scannedQRData: Data) -> ScanProgressPublisher {
|
||||
let progressSubject = CurrentValueSubject<ScanProgress, QRCodeLoginError>(.starting)
|
||||
let listener = SDKListener { progressSubject.send(.init(rustProgress: $0)) }
|
||||
let listener = SDKListener {
|
||||
do {
|
||||
try progressSubject.send(.init(rustProgress: $0))
|
||||
} catch {
|
||||
MXLog.error("Invalid ScanProgress")
|
||||
progressSubject.send(completion: .failure(.unknown))
|
||||
}
|
||||
}
|
||||
|
||||
let qrCodeData: QrCodeData
|
||||
do {
|
||||
@@ -89,12 +104,28 @@ class LinkNewDeviceService {
|
||||
}
|
||||
|
||||
extension LinkNewDeviceService.GenerateProgress: CustomStringConvertible {
|
||||
init(rustProgress: GrantGeneratedQrLoginProgress) {
|
||||
enum Error: Swift.Error {
|
||||
case invalidQRCodeData
|
||||
case invalidVerificationURI(String)
|
||||
}
|
||||
|
||||
init(rustProgress: GrantGeneratedQrLoginProgress) throws {
|
||||
self = switch rustProgress {
|
||||
case .starting: .starting
|
||||
case .qrReady(let qrCode): .qrReady(qrCode)
|
||||
case .qrReady(let qrCode):
|
||||
if let image = UIImage(qrCodeData: qrCode.toBytes()) {
|
||||
.qrReady(image)
|
||||
} else {
|
||||
throw Error.invalidQRCodeData
|
||||
}
|
||||
case .qrScanned(let checkCodeSender): .qrScanned(checkCodeSender)
|
||||
case .waitingForAuth(let verificationUri): .waitingForAuthorisation(verificationURI: verificationUri)
|
||||
case .waitingForAuth(let verificationURI):
|
||||
// verificationURI is a String; ASWebAuthenticationSession requires a URL.
|
||||
if let url = URL(string: verificationURI) {
|
||||
.waitingForAuthorisation(verificationURL: url)
|
||||
} else {
|
||||
throw Error.invalidVerificationURI(verificationURI)
|
||||
}
|
||||
case .syncingSecrets: .syncingSecrets
|
||||
case .done: .done
|
||||
}
|
||||
@@ -113,11 +144,19 @@ extension LinkNewDeviceService.GenerateProgress: CustomStringConvertible {
|
||||
}
|
||||
|
||||
extension LinkNewDeviceService.ScanProgress: CustomStringConvertible {
|
||||
init(rustProgress: GrantQrLoginProgress) {
|
||||
enum Error: Swift.Error { case invalidVerificationURI(String) }
|
||||
|
||||
init(rustProgress: GrantQrLoginProgress) throws {
|
||||
self = switch rustProgress {
|
||||
case .starting: .starting
|
||||
case .establishingSecureChannel(let checkCode, let checkCodeString): .establishingSecureChannel(checkCode: checkCode, checkCodeString: checkCodeString)
|
||||
case .waitingForAuth(let verificationUri): .waitingForAuth(verificationURI: verificationUri)
|
||||
case .waitingForAuth(let verificationURI):
|
||||
// verificationURI is a String; ASWebAuthenticationSession requires a URL.
|
||||
if let url = URL(string: verificationURI) {
|
||||
.waitingForAuthorisation(verificationURL: url)
|
||||
} else {
|
||||
throw Error.invalidVerificationURI(verificationURI)
|
||||
}
|
||||
case .syncingSecrets: .syncingSecrets
|
||||
case .done: .done
|
||||
}
|
||||
@@ -127,7 +166,7 @@ extension LinkNewDeviceService.ScanProgress: CustomStringConvertible {
|
||||
switch self {
|
||||
case .starting: "starting"
|
||||
case .establishingSecureChannel: "establishingSecureChannel"
|
||||
case .waitingForAuth: "waitingForAuth"
|
||||
case .waitingForAuthorisation: "waitingForAuthorisation"
|
||||
case .syncingSecrets: "syncingSecrets"
|
||||
case .done: "done"
|
||||
}
|
||||
@@ -146,3 +185,21 @@ private extension QRCodeLoginError {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension UIImage {
|
||||
convenience init?(qrCodeData: Data) {
|
||||
let qrContext = CIContext()
|
||||
let qrFilter = CIFilter.qrCodeGenerator()
|
||||
|
||||
qrFilter.message = qrCodeData
|
||||
qrFilter.correctionLevel = "Q"
|
||||
|
||||
guard let outputImage = qrFilter.outputImage,
|
||||
let cgImage = qrContext.createCGImage(outputImage, from: outputImage.extent) else {
|
||||
MXLog.error("Failed to generate an image from the supplied QR code data.")
|
||||
return nil
|
||||
}
|
||||
|
||||
self.init(cgImage: cgImage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -629,6 +629,12 @@ extension PreviewTests {
|
||||
}
|
||||
}
|
||||
|
||||
func testQRCodeErrorView() async throws {
|
||||
for (index, preview) in QRCodeErrorView_Previews._allPreviews.enumerated() {
|
||||
try await assertSnapshots(matching: preview, step: index)
|
||||
}
|
||||
}
|
||||
|
||||
func testQRCodeLoginScreen() async throws {
|
||||
for (index, preview) in QRCodeLoginScreen_Previews._allPreviews.enumerated() {
|
||||
try await assertSnapshots(matching: preview, step: index)
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d4bdf47c5e3c003195a5171e00ff0a7c19fdade400cc49ea1f27af3af5598445
|
||||
size 89983
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7d7fa171a38f3f4c113390bc41b9dbafec9fa429af7603894f539d3b57fe64e5
|
||||
size 96438
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:914698e75e1c93cf578cb243a8178d773ee3676979db95753a820396ad2006b4
|
||||
size 49503
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0b612f9908d71a2ab4aaa54a558fe5c6465ce38b29b5e83bbdd93870bc0eee9b
|
||||
size 62995
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:60071d118b633bf533a6587373c06feb8384d4754f5e2ab0a99e35a028d2e61f
|
||||
size 141760
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3949cf87ef273e45eb8f048d49fe149a9ae6ef20c7262ab5c8ec080218480a3d
|
||||
size 187441
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9859124c19a8d1b91a20e87b6ffc3efd2a84cfa95b5de9047140d508cabbe339
|
||||
size 102663
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:243cbd45d08feaae2d126d0a70c17835020ff0ec3836a109ccab3b560fc3b38e
|
||||
size 160568
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e85694a3b538b272d1324a72fd9a6278afceaacd28cea84fcc79bf96ca6d240c
|
||||
size 87644
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ca17a59ca6681d8cf4d5eb48ca7c301ccfee2c41f1b1942d49f282536e594b9c
|
||||
size 93400
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:371ea2f5e6db42f0611b63cbe0241733c3e13072b1a2f338ff52852a5319093e
|
||||
size 45605
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a59dd129d741059ea37497a748fd26f5fc7f76ad4258ce4327e683458c54ef05
|
||||
size 55253
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:10cde6d53a261f5f076dfc985e846985aedf560b3832dcbd38fb87323e0c6af8
|
||||
size 90581
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cfa533e3cf122c3549f0310a01d11f6fba2be7517a2461289c9dda02edba9f46
|
||||
size 98629
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0f6c297aef97f3e186fedc1c0d6e62ed55348131f3f2074706e0007b656c7177
|
||||
size 48938
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:45a9a405dffed9ae9e91d81248b7e19b365cec3509912eb258cfae85878cf455
|
||||
size 62571
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7fb48e43db6f2787eec4148c8e0e90ed3b40516485b3ecac7356994aec22dc67
|
||||
size 90924
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:89d5920bf38135a595438c7c2f17463173bdef037b5f769037d2e4f88b433ad8
|
||||
size 99821
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4b63f9d10e70129ba6dcad2722f6b9386c892d0e0f8e0d52f9e626ab71f84ff9
|
||||
size 50237
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7067c4325fda81e7ce82dd53a82cfa3531a92f907ca7215e1006c98cf98ecda7
|
||||
size 64697
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c6b9799b352e2221b57a03dbd6194a657cd2651e27d07d466fc528256a61dd08
|
||||
size 107762
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:67d79e351da63ee2289bc75a06cb7adf769c3871e21647eaa60cf42cff774e1e
|
||||
size 132987
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:01b6e8fe8d6221aeff100635f30b4ee4d38c2dd7a0fe96bbfc863309fc10dc12
|
||||
size 68507
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fbf90db455fc188bae28f52f13a0fefb3602d1349a7ea0b9f337f68376c2dd6d
|
||||
size 98104
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:40b981da4f55d7ff8b1433888f6b0303025ef3b7ad6a8c69f451b9f4d4ea7826
|
||||
size 100024
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bc31d7c0b096568c66b954ec864e3553ec48f4945cf0afdbe01c7a76583bbcfd
|
||||
size 123384
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b241ac12d4954e085c3e215f25112b61e9a49e6d4a3d343b5452b4ad8734b27d
|
||||
size 61772
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:581294e449432a5261b366cc76644492af42af0d0f595cd152da50c233f4d6d1
|
||||
size 89902
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:46f1090c3d4f2521708c6958acda398473c201417960b9fa12e3e3cd20883c07
|
||||
size 99943
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cb4c58a4448a6ea161eeb8bd32a2c26f358d63e127547a3545987baaca22709b
|
||||
size 119957
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4754d2ab51c8e3635a13108127355967e0ee2a0636590475ce03d7eb90557fba
|
||||
size 62228
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4eafd5c592e86b1737c8f78815cc1197593191454ade696721244632f45ba5fa
|
||||
size 88846
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4db2461cc7a8dc902fe6b288f75b0b4fd7fe6469c82e380f5ea153f3aae2a989
|
||||
size 90207
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:242d2050553c8e2b773cb78bcce282238208f9e525ecd8b520cbdb35e20d4b40
|
||||
size 96304
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d99a5f1997b7ed633828d27ad66c2167502feace1062f7d54d7f5e143dfe37df
|
||||
size 48900
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fda4806cbf848c50cee4f52aa7d6b2be60c589ba1c18010ce4973f9312fa9922
|
||||
size 62108
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6ad6008a604884316d6b6cb1b3247e25b6033a87cf5c366ee1238d2ad7336fd0
|
||||
size 100428
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b10951db78fdd0c357dadd6017b96c93699f143580d4b0221fe484f7549f8731
|
||||
size 109691
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1a279a6116971ff15d0a7889fb508d2e31bfea4db3be2498438c1ccd7b8a7c42
|
||||
size 53912
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d48d9ce53a65718cd23497694732539573e9b6f63a735660589812ebf123504f
|
||||
size 67601
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c77a5913eef3284747ae50aeb2252ffbae01484f960283a0986de3648946b9ec
|
||||
size 160330
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e9ce41587137a7138ec026ee37be14255a1791a80b8061598b2cf5c854c74739
|
||||
size 213100
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2d173a380e7d7d3c0b4831c922c3bf8a34f90ed1a09ec13b318924d17f2119a4
|
||||
size 108449
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:11be86f9d30026c10851dad887ccebbcbf3747dd4e719a6d3cd2c3e7a54793e0
|
||||
size 163399
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:17a79d824b61f169e74a8b8a687a863ea58720a48b8ae0092847f43325ae6efd
|
||||
size 98395
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:69d9ada1c31055265d0d2e1a13af50a4cecf0e88df5799992947443b93ee3d20
|
||||
size 104670
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:83eea65ebf7ce67b8cb7713810bc6940d66c433e3e9db3ed04b1d886ee74b94a
|
||||
size 49876
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b31a74c68ea6afd042cbad914e41c8873c016db8a56d46500d7ba9eb48467b78
|
||||
size 59588
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b898550b460e092b5e6d8e5cd50026b61ef7fc02dea61b11b9c6e31bcab6d372
|
||||
size 100661
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:91c9b162be64a92fc455d0e4b587969ee936ca0b78f4c3b42d08e7eb6106e663
|
||||
size 111044
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a77e48d541d09bde879e8a514d2c3e116bb4f86a8bfb1182cb8fe9a5b3db002b
|
||||
size 53261
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:187e5d16e7f11ec44e388e7e8ce83430b9c9b2093ac837a27633ca417424f85d
|
||||
size 67290
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:879ba9bde4429a1ebbee71bf94fb6b3e7fa97841fc09aa1560d51d1486bdd2c5
|
||||
size 88830
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ce4cc7acad160cd2436b630d204da4c473a55baba7bb2378a403bf38c96d45c1
|
||||
size 94953
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:371ea2f5e6db42f0611b63cbe0241733c3e13072b1a2f338ff52852a5319093e
|
||||
size 45605
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a59dd129d741059ea37497a748fd26f5fc7f76ad4258ce4327e683458c54ef05
|
||||
size 55253
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8992768d514f2032d1688cbc17fa20ef84f3dc0709ed4d8a824c265e709a43dd
|
||||
size 101855
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ea09999977094e146233b557ee4a522485b41cd5f800d8d52595fe7375441998
|
||||
size 110373
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:33d66fcb5ea803769361fd33b266cacca5aa61f393436fb058866bd7b6ce34f0
|
||||
size 54659
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a40cc4c26ae3d31ff2b754426384a4e11fee664a97b86862715f47336e47ab2e
|
||||
size 69002
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4bca97ae120c6d0565d669578ce6858ed7f52d34cad3deb1f259b10893b21661
|
||||
size 120586
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7537ac8a3d489aecb356700cc11fa80b2aa74c5276b562e23d984f99b71a6087
|
||||
size 143521
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:46005049fbcb338e8f2d61d30e0714ae3167b448ced78850fc32b8a2138e298b
|
||||
size 72834
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9f16898529302568e545edfe9a00ac2f7856b7de22216193063ba96efaa9e70c
|
||||
size 103065
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ad1f69dabd52d333d540b641af85307c1b01747f1083dfaab8ff633895110f46
|
||||
size 112939
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f4a3bee6fe5de50df6396d1afbcead2f10440e04d56ce7fc7891b3badac0e26c
|
||||
size 134913
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fb3407e687dc5dd007fc82891567092bf15490bc6498a29cf6bde386d9f9c4cf
|
||||
size 66134
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1ad717285b90aee453fb29b20e7d3862af36155581859ca3714b5060f0a353dc
|
||||
size 94658
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7b84fcd7258cdeaacd0156761bc0fb5de4938cfa94eb90bafbde3945742318d6
|
||||
size 114035
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6c812e8fde8c4259fa516b4b7da61e97f71edd8cae6e0430e1f6ecb0726363a1
|
||||
size 137301
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b068ab386807c2fb20340ff24c7b74b07bccf8645fbe3ea5562a44f8f57e89cb
|
||||
size 66645
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cfd7c23837fc87dfc2bb97cb84f6ed950259deb55cab2684f8310a8593231c80
|
||||
size 93432
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7c3089de3d3774202fb36cd413625b3e9ecbf2e12bab77428b2a2f54180199b4
|
||||
size 100521
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c33193ff1a4fe9c82d56837c87be8ba40759967f310e2564ba6edea8ef8551a1
|
||||
size 109194
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2a6ae461aca945e30cfba668d3195acd792023fab74ab47b0a3ce26ceb7c6a35
|
||||
size 53248
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b3fb1bbe03f8585898034bcc1204dd46ae931e4245708287ecc05c2e7f1362e3
|
||||
size 66576
|
||||
@@ -53,7 +53,7 @@ final class QRCodeLoginScreenViewModelTests: XCTestCase {
|
||||
try await deferred.fulfill()
|
||||
XCTAssertTrue(appMediatorMock.requestAuthorizationIfNeededCalled)
|
||||
|
||||
context.send(viewAction: .openSettings)
|
||||
context.send(viewAction: .errorAction(.openSettings))
|
||||
await Task.yield()
|
||||
XCTAssertTrue(appMediatorMock.openAppSettingsCalled)
|
||||
XCTAssertNil(context.qrResult)
|
||||
|
||||
Reference in New Issue
Block a user