mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
parent
eda7d59518
commit
9bc24e2038
@ -901,6 +901,18 @@ class ApplicationMock: ApplicationProtocol {
|
||||
openReceivedInvocations.append(url)
|
||||
openClosure?(url)
|
||||
}
|
||||
//MARK: - openAppSettings
|
||||
|
||||
var openAppSettingsCallsCount = 0
|
||||
var openAppSettingsCalled: Bool {
|
||||
return openAppSettingsCallsCount > 0
|
||||
}
|
||||
var openAppSettingsClosure: (() -> Void)?
|
||||
|
||||
func openAppSettings() {
|
||||
openAppSettingsCallsCount += 1
|
||||
openAppSettingsClosure?()
|
||||
}
|
||||
}
|
||||
class AudioConverterMock: AudioConverterProtocol {
|
||||
|
||||
|
@ -25,6 +25,7 @@ struct HeroImage: View {
|
||||
case normal
|
||||
case positive
|
||||
case subtle
|
||||
case critical
|
||||
|
||||
var foregroundColor: Color {
|
||||
switch self {
|
||||
@ -34,6 +35,8 @@ struct HeroImage: View {
|
||||
return .compound.iconSuccessPrimary
|
||||
case .subtle:
|
||||
return .compound.iconSecondary
|
||||
case .critical:
|
||||
return .compound.iconCriticalPrimary
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,6 +48,8 @@ struct HeroImage: View {
|
||||
return .compound.bgSuccessSubtle
|
||||
case .subtle:
|
||||
return .compound.bgSubtlePrimary
|
||||
case .critical:
|
||||
return .compound.bgCanvasDefault
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,11 +49,7 @@ final class StaticLocationScreenCoordinator: CoordinatorProtocol {
|
||||
case .close:
|
||||
actionsSubject.send(.close)
|
||||
case .openSystemSettings:
|
||||
guard let url = URL(string: UIApplication.openSettingsURLString),
|
||||
UIApplication.shared.canOpenURL(url) else {
|
||||
return
|
||||
}
|
||||
UIApplication.shared.open(url)
|
||||
UIApplication.shared.openAppSettings()
|
||||
case .sendLocation(let geoURI, let isUserLocation):
|
||||
actionsSubject.send(.selectedLocation(geoURI, isUserLocation: isUserLocation))
|
||||
}
|
||||
|
@ -40,7 +40,8 @@ final class QRCodeLoginScreenCoordinator: CoordinatorProtocol {
|
||||
}
|
||||
|
||||
init(parameters: QRCodeLoginScreenCoordinatorParameters) {
|
||||
viewModel = QRCodeLoginScreenViewModel(qrCodeLoginService: parameters.qrCodeLoginService)
|
||||
viewModel = QRCodeLoginScreenViewModel(qrCodeLoginService: parameters.qrCodeLoginService,
|
||||
application: UIApplication.shared)
|
||||
orientationManager = parameters.orientationManager
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ enum QRCodeLoginScreenViewModelAction {
|
||||
struct QRCodeLoginScreenViewState: BindableState {
|
||||
var state: QRCodeLoginState = .initial
|
||||
|
||||
private let listItem3AttributedText = {
|
||||
private static let initialStateListItem3AttributedText = {
|
||||
let boldPlaceholder = "{bold}"
|
||||
var finalString = AttributedString(L10n.screenQrCodeLoginInitialStateItem3(boldPlaceholder))
|
||||
var boldString = AttributedString(L10n.screenQrCodeLoginInitialStateItem3Action)
|
||||
@ -32,7 +32,7 @@ struct QRCodeLoginScreenViewState: BindableState {
|
||||
return finalString
|
||||
}()
|
||||
|
||||
private let listItem4AttributedText = {
|
||||
private static let initialStateListItem4AttributedText = {
|
||||
let boldPlaceholder = "{bold}"
|
||||
var finalString = AttributedString(L10n.screenQrCodeLoginInitialStateItem4(boldPlaceholder))
|
||||
var boldString = AttributedString(L10n.screenQrCodeLoginInitialStateItem4Action)
|
||||
@ -41,19 +41,24 @@ struct QRCodeLoginScreenViewState: BindableState {
|
||||
return finalString
|
||||
}()
|
||||
|
||||
var listItems: [AttributedString] {
|
||||
[
|
||||
AttributedString(L10n.screenQrCodeLoginInitialStateItem1(InfoPlistReader.main.productionAppName)),
|
||||
AttributedString(L10n.screenQrCodeLoginInitialStateItem2),
|
||||
listItem3AttributedText,
|
||||
listItem4AttributedText
|
||||
]
|
||||
}
|
||||
let initialStateListItems = [
|
||||
AttributedString(L10n.screenQrCodeLoginInitialStateItem1(InfoPlistReader.main.productionAppName)),
|
||||
AttributedString(L10n.screenQrCodeLoginInitialStateItem2),
|
||||
initialStateListItem3AttributedText,
|
||||
initialStateListItem4AttributedText
|
||||
]
|
||||
|
||||
let connectionNotSecureListItems = [
|
||||
AttributedString(L10n.screenQrCodeLoginConnectionNoteSecureStateListItem1),
|
||||
AttributedString(L10n.screenQrCodeLoginConnectionNoteSecureStateListItem2),
|
||||
AttributedString(L10n.screenQrCodeLoginConnectionNoteSecureStateListItem3)
|
||||
]
|
||||
}
|
||||
|
||||
enum QRCodeLoginScreenViewAction {
|
||||
case cancel
|
||||
case startScan
|
||||
case openSettings
|
||||
}
|
||||
|
||||
enum QRCodeLoginState: Equatable {
|
||||
@ -66,6 +71,8 @@ enum QRCodeLoginState: Equatable {
|
||||
|
||||
enum QRCodeLoginErrorState: Equatable {
|
||||
case noCameraPermission
|
||||
case connectionNotSecure
|
||||
case unknown
|
||||
}
|
||||
|
||||
enum QRCodeLoginScanningState: Equatable {
|
||||
|
@ -22,14 +22,17 @@ typealias QRCodeLoginScreenViewModelType = StateStoreViewModel<QRCodeLoginScreen
|
||||
|
||||
class QRCodeLoginScreenViewModel: QRCodeLoginScreenViewModelType, QRCodeLoginScreenViewModelProtocol {
|
||||
private let qrCodeLoginService: QRCodeLoginServiceProtocol
|
||||
private let application: ApplicationProtocol
|
||||
|
||||
private let actionsSubject: PassthroughSubject<QRCodeLoginScreenViewModelAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<QRCodeLoginScreenViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(qrCodeLoginService: QRCodeLoginServiceProtocol) {
|
||||
init(qrCodeLoginService: QRCodeLoginServiceProtocol,
|
||||
application: ApplicationProtocol) {
|
||||
self.qrCodeLoginService = qrCodeLoginService
|
||||
self.application = application
|
||||
super.init(initialViewState: QRCodeLoginScreenViewState())
|
||||
}
|
||||
|
||||
@ -41,6 +44,8 @@ class QRCodeLoginScreenViewModel: QRCodeLoginScreenViewModelType, QRCodeLoginScr
|
||||
actionsSubject.send(.cancel)
|
||||
case .startScan:
|
||||
Task { await startScanIfPossible() }
|
||||
case .openSettings:
|
||||
application.openAppSettings()
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,6 +56,7 @@ class QRCodeLoginScreenViewModel: QRCodeLoginScreenViewModelType, QRCodeLoginScr
|
||||
/// Only for mocking initial states
|
||||
fileprivate init(state: QRCodeLoginState) {
|
||||
qrCodeLoginService = QRCodeLoginServiceMock(configuration: .init())
|
||||
application = ApplicationMock()
|
||||
super.init(initialViewState: .init(state: state))
|
||||
}
|
||||
}
|
||||
|
@ -40,8 +40,7 @@ struct QRCodeLoginScreen: View {
|
||||
case .scan:
|
||||
qrScanContent
|
||||
case .error:
|
||||
// TODO: Handle error states
|
||||
EmptyView()
|
||||
errorContent
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +57,7 @@ struct QRCodeLoginScreen: View {
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
|
||||
SFNumberedListView(items: context.viewState.listItems)
|
||||
SFNumberedListView(items: context.viewState.initialStateListItems)
|
||||
}
|
||||
} bottomContent: {
|
||||
Button(L10n.actionContinue) {
|
||||
@ -107,7 +106,7 @@ struct QRCodeLoginScreen: View {
|
||||
case .invalid:
|
||||
VStack(spacing: 16) {
|
||||
Button(L10n.screenQrCodeLoginInvalidScanStateRetryButton) {
|
||||
// TODO: Implement try again
|
||||
context.send(viewAction: .startScan)
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
|
||||
@ -125,7 +124,7 @@ struct QRCodeLoginScreen: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var qrScanner: some View {
|
||||
QRCodeScannerView()
|
||||
.aspectRatio(1.0, contentMode: .fill)
|
||||
@ -136,7 +135,7 @@ struct QRCodeLoginScreen: View {
|
||||
QRScannerViewOverlay(length: qrFrame.height)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ToolbarContentBuilder
|
||||
private var toolbar: some ToolbarContent {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
@ -145,6 +144,98 @@ 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) {
|
||||
HeroImage(icon: \.takePhotoSolid, style: .subtle)
|
||||
|
||||
VStack(spacing: 8) {
|
||||
Text(L10n.screenQrCodeLoginNoCameraPermissionStateTitle)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.font(.compound.headingMDBold)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Text(L10n.screenQrCodeLoginNoCameraPermissionStateDescription)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.font(.compound.bodyMD)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
}
|
||||
case .connectionNotSecure:
|
||||
VStack(spacing: 40) {
|
||||
VStack(spacing: 16) {
|
||||
HeroImage(icon: \.error, style: .critical)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
case .unknown:
|
||||
VStack(spacing: 16) {
|
||||
HeroImage(icon: \.error, style: .critical)
|
||||
|
||||
VStack(spacing: 8) {
|
||||
Text(L10n.commonSomethingWentWrong)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.font(.compound.headingMDBold)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Text(L10n.screenQrCodeLoginUnknownErrorDescription)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.font(.compound.bodyMD)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
Button(L10n.screenQrCodeLoginStartOverButton) {
|
||||
context.send(viewAction: .startScan)
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct QRScannerViewOverlay: View {
|
||||
@ -183,6 +274,12 @@ struct QRCodeLoginScreen_Previews: PreviewProvider, TestablePreview {
|
||||
|
||||
static let invalidStateViewModel = QRCodeLoginScreenViewModel.mock(state: .scan(.invalid))
|
||||
|
||||
static let noCameraPermissionStateViewModel = QRCodeLoginScreenViewModel.mock(state: .error(.noCameraPermission))
|
||||
|
||||
static let connectionNotSecureStateViewModel = QRCodeLoginScreenViewModel.mock(state: .error(.connectionNotSecure))
|
||||
|
||||
static let unknownErrorStateViewModel = QRCodeLoginScreenViewModel.mock(state: .error(.unknown))
|
||||
|
||||
static var previews: some View {
|
||||
QRCodeLoginScreen(context: initialStateViewModel.context)
|
||||
.previewDisplayName("Initial")
|
||||
@ -195,5 +292,14 @@ struct QRCodeLoginScreen_Previews: PreviewProvider, TestablePreview {
|
||||
|
||||
QRCodeLoginScreen(context: invalidStateViewModel.context)
|
||||
.previewDisplayName("Invalid")
|
||||
|
||||
QRCodeLoginScreen(context: noCameraPermissionStateViewModel.context)
|
||||
.previewDisplayName("No Camera Permission")
|
||||
|
||||
QRCodeLoginScreen(context: connectionNotSecureStateViewModel.context)
|
||||
.previewDisplayName("Connection not secure")
|
||||
|
||||
QRCodeLoginScreen(context: unknownErrorStateViewModel.context)
|
||||
.previewDisplayName("Unknown error")
|
||||
}
|
||||
}
|
||||
|
@ -619,8 +619,7 @@ class RoomScreenInteractionHandler {
|
||||
}
|
||||
|
||||
private func openSystemSettings() {
|
||||
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
|
||||
application.open(url)
|
||||
application.openAppSettings()
|
||||
}
|
||||
|
||||
private func displayMediaActionIfPossible(timelineItem: RoomTimelineItemProtocol) async -> RoomTimelineControllerAction {
|
||||
|
@ -24,6 +24,8 @@ protocol ApplicationProtocol {
|
||||
func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier)
|
||||
|
||||
func open(_ url: URL)
|
||||
|
||||
func openAppSettings()
|
||||
|
||||
var backgroundTimeRemaining: TimeInterval { get }
|
||||
|
||||
@ -34,4 +36,11 @@ extension UIApplication: ApplicationProtocol {
|
||||
func open(_ url: URL) {
|
||||
open(url, options: [:], completionHandler: nil)
|
||||
}
|
||||
|
||||
func openAppSettings() {
|
||||
guard let url = URL(string: UIApplication.openSettingsURLString) else {
|
||||
return
|
||||
}
|
||||
open(url)
|
||||
}
|
||||
}
|
||||
|
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPad-en-GB.Connection-not-secure.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPad-en-GB.Connection-not-secure.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPad-en-GB.No-Camera-Permission.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPad-en-GB.No-Camera-Permission.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPad-en-GB.Unknown-error.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPad-en-GB.Unknown-error.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPad-pseudo.Connection-not-secure.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPad-pseudo.Connection-not-secure.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPad-pseudo.No-Camera-Permission.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPad-pseudo.No-Camera-Permission.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPad-pseudo.Unknown-error.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPad-pseudo.Unknown-error.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPhone-15-en-GB.Connection-not-secure.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPhone-15-en-GB.Connection-not-secure.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPhone-15-en-GB.No-Camera-Permission.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPhone-15-en-GB.No-Camera-Permission.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPhone-15-en-GB.Unknown-error.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPhone-15-en-GB.Unknown-error.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPhone-15-pseudo.Connection-not-secure.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPhone-15-pseudo.Connection-not-secure.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPhone-15-pseudo.No-Camera-Permission.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPhone-15-pseudo.No-Camera-Permission.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPhone-15-pseudo.Unknown-error.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/__Snapshots__/PreviewTests/test_qRCodeLoginScreen-iPhone-15-pseudo.Unknown-error.png
(Stored with Git LFS)
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user