mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 23:32:41 +00:00
QR Login additional flags (#6825)
This commit is contained in:
parent
1ee8c9ca19
commit
359bfc0445
16 changed files with 125 additions and 27 deletions
|
@ -422,5 +422,11 @@ final class BuildSettings: NSObject {
|
|||
static let newAppLayoutEnabled = true
|
||||
|
||||
// MARK: - QR Login
|
||||
static let enableQRLogin = false
|
||||
|
||||
/// Flag indicating whether the QR login enabled from login screen
|
||||
static let qrLoginEnabledFromNotAuthenticated = false
|
||||
/// Flag indicating whether the QR login enabled from Device Manager screen
|
||||
static let qrLoginEnabledFromAuthenticated = false
|
||||
/// Flag indicating whether displaying QRs enabled for the QR login screens
|
||||
static let qrLoginEnableDisplayingQRs = false
|
||||
}
|
||||
|
|
|
@ -54,12 +54,23 @@ class QRLoginService: NSObject, QRLoginServiceProtocol {
|
|||
let callbacks = PassthroughSubject<QRLoginServiceCallback, Never>()
|
||||
|
||||
func isServiceAvailable() async throws -> Bool {
|
||||
guard BuildSettings.enableQRLogin else {
|
||||
return false
|
||||
switch mode {
|
||||
case .authenticated:
|
||||
guard BuildSettings.qrLoginEnabledFromAuthenticated else {
|
||||
return false
|
||||
}
|
||||
case .notAuthenticated:
|
||||
guard BuildSettings.qrLoginEnabledFromNotAuthenticated else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return try await client.supportedMatrixVersions().supportsQRLogin
|
||||
}
|
||||
|
||||
func canDisplayQR() -> Bool {
|
||||
BuildSettings.qrLoginEnableDisplayingQRs
|
||||
}
|
||||
|
||||
func generateQRCode() async throws -> QRLoginCode {
|
||||
let transport = QRLoginRendezvousTransportDetails(type: "http.v1",
|
||||
uri: "")
|
||||
|
|
|
@ -19,10 +19,14 @@ import Foundation
|
|||
import SwiftUI
|
||||
|
||||
class MockQRLoginService: QRLoginServiceProtocol {
|
||||
private let mockCanDisplayQR: Bool
|
||||
|
||||
init(withState state: QRLoginServiceState = .initial,
|
||||
mode: QRLoginServiceMode = .notAuthenticated) {
|
||||
mode: QRLoginServiceMode = .notAuthenticated,
|
||||
canDisplayQR: Bool = true) {
|
||||
self.state = state
|
||||
self.mode = mode
|
||||
self.mockCanDisplayQR = canDisplayQR
|
||||
}
|
||||
|
||||
// MARK: - QRLoginServiceProtocol
|
||||
|
@ -43,6 +47,10 @@ class MockQRLoginService: QRLoginServiceProtocol {
|
|||
true
|
||||
}
|
||||
|
||||
func canDisplayQR() -> Bool {
|
||||
mockCanDisplayQR
|
||||
}
|
||||
|
||||
func generateQRCode() async throws -> QRLoginCode {
|
||||
let transport = QRLoginRendezvousTransportDetails(type: "http.v1",
|
||||
uri: "https://matrix.org")
|
||||
|
|
|
@ -82,6 +82,7 @@ protocol QRLoginServiceProtocol {
|
|||
var state: QRLoginServiceState { get }
|
||||
var callbacks: PassthroughSubject<QRLoginServiceCallback, Never> { get }
|
||||
func isServiceAvailable() async throws -> Bool
|
||||
func canDisplayQR() -> Bool
|
||||
func generateQRCode() async throws -> QRLoginCode
|
||||
|
||||
// MARK: QR Scanner
|
||||
|
|
|
@ -43,6 +43,7 @@ enum AuthenticationQRLoginScanViewModelResult: Equatable {
|
|||
// MARK: View
|
||||
|
||||
struct AuthenticationQRLoginScanViewState: BindableState {
|
||||
var canShowDisplayQRButton: Bool
|
||||
var serviceState: QRLoginServiceState
|
||||
var scannerView: AnyView?
|
||||
}
|
||||
|
|
|
@ -34,7 +34,8 @@ class AuthenticationQRLoginScanViewModel: AuthenticationQRLoginScanViewModelType
|
|||
|
||||
init(qrLoginService: QRLoginServiceProtocol) {
|
||||
self.qrLoginService = qrLoginService
|
||||
super.init(initialViewState: AuthenticationQRLoginScanViewState(serviceState: .initial))
|
||||
super.init(initialViewState: .init(canShowDisplayQRButton: qrLoginService.canDisplayQR(),
|
||||
serviceState: .initial))
|
||||
|
||||
qrLoginService.callbacks.sink { callback in
|
||||
switch callback {
|
||||
|
|
|
@ -26,6 +26,8 @@ enum MockAuthenticationQRLoginScanScreenState: MockScreenState, CaseIterable {
|
|||
case scanning
|
||||
case noCameraAvailable
|
||||
case noCameraAccess
|
||||
case noCameraAvailableNoDisplayQR
|
||||
case noCameraAccessNoDisplayQR
|
||||
|
||||
/// The associated screen
|
||||
var screenType: Any.Type {
|
||||
|
@ -35,21 +37,27 @@ enum MockAuthenticationQRLoginScanScreenState: MockScreenState, CaseIterable {
|
|||
/// A list of screen state definitions
|
||||
static var allCases: [MockAuthenticationQRLoginScanScreenState] {
|
||||
// Each of the presence statuses
|
||||
[.scanning, .noCameraAvailable, .noCameraAccess]
|
||||
[.scanning, .noCameraAvailable, .noCameraAccess, .noCameraAvailableNoDisplayQR, .noCameraAccessNoDisplayQR]
|
||||
}
|
||||
|
||||
/// Generate the view struct for the screen state.
|
||||
var screenView: ([Any], AnyView) {
|
||||
let viewModel: AuthenticationQRLoginScanViewModel
|
||||
let service: QRLoginServiceProtocol
|
||||
|
||||
switch self {
|
||||
case .scanning:
|
||||
viewModel = .init(qrLoginService: MockQRLoginService(withState: .scanningQR))
|
||||
service = MockQRLoginService(withState: .scanningQR)
|
||||
case .noCameraAvailable:
|
||||
viewModel = .init(qrLoginService: MockQRLoginService(withState: .failed(error: .noCameraAvailable)))
|
||||
service = MockQRLoginService(withState: .failed(error: .noCameraAvailable))
|
||||
case .noCameraAccess:
|
||||
viewModel = .init(qrLoginService: MockQRLoginService(withState: .failed(error: .noCameraAccess)))
|
||||
service = MockQRLoginService(withState: .failed(error: .noCameraAccess))
|
||||
case .noCameraAvailableNoDisplayQR:
|
||||
service = MockQRLoginService(withState: .failed(error: .noCameraAvailable), canDisplayQR: false)
|
||||
case .noCameraAccessNoDisplayQR:
|
||||
service = MockQRLoginService(withState: .failed(error: .noCameraAccess), canDisplayQR: false)
|
||||
}
|
||||
|
||||
let viewModel = AuthenticationQRLoginScanViewModel(qrLoginService: service)
|
||||
|
||||
// can simulate service and viewModel actions here if needs be.
|
||||
|
||||
|
|
|
@ -50,4 +50,28 @@ class AuthenticationQRLoginScanUITests: MockScreenTestCase {
|
|||
XCTAssertTrue(displayQRButton.exists)
|
||||
XCTAssertTrue(displayQRButton.isEnabled)
|
||||
}
|
||||
|
||||
func testNoCameraAvailableNoDisplayQR() {
|
||||
app.goToScreenWithIdentifier(MockAuthenticationQRLoginScanScreenState.noCameraAvailableNoDisplayQR.title)
|
||||
|
||||
XCTAssertTrue(app.staticTexts["titleLabel"].exists)
|
||||
XCTAssertTrue(app.staticTexts["subtitleLabel"].exists)
|
||||
|
||||
let displayQRButton = app.buttons["displayQRButton"]
|
||||
XCTAssertFalse(displayQRButton.exists)
|
||||
}
|
||||
|
||||
func testNoCameraAccessNoDisplayQR() {
|
||||
app.goToScreenWithIdentifier(MockAuthenticationQRLoginScanScreenState.noCameraAccessNoDisplayQR.title)
|
||||
|
||||
XCTAssertTrue(app.staticTexts["titleLabel"].exists)
|
||||
XCTAssertTrue(app.staticTexts["subtitleLabel"].exists)
|
||||
|
||||
let openSettingsButton = app.buttons["openSettingsButton"]
|
||||
XCTAssertTrue(openSettingsButton.exists)
|
||||
XCTAssertTrue(openSettingsButton.isEnabled)
|
||||
|
||||
let displayQRButton = app.buttons["displayQRButton"]
|
||||
XCTAssertFalse(displayQRButton.exists)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,10 @@ class AuthenticationQRLoginScanViewModelTests: XCTestCase {
|
|||
context = viewModel.context
|
||||
}
|
||||
|
||||
func testDisplayQRButtonVisibility() {
|
||||
XCTAssertTrue(viewModel.context.viewState.canShowDisplayQRButton)
|
||||
}
|
||||
|
||||
func testGoToSettings() {
|
||||
var result: AuthenticationQRLoginScanViewModelResult?
|
||||
|
||||
|
|
|
@ -167,13 +167,15 @@ struct AuthenticationQRLoginScanScreen: View {
|
|||
.accessibilityIdentifier("openSettingsButton")
|
||||
}
|
||||
|
||||
LabelledDivider(label: VectorL10n.authenticationQrLoginStartNeedAlternative)
|
||||
if context.viewState.canShowDisplayQRButton {
|
||||
LabelledDivider(label: VectorL10n.authenticationQrLoginStartNeedAlternative)
|
||||
|
||||
Button(action: displayQR) {
|
||||
Text(VectorL10n.authenticationQrLoginStartDisplayQr)
|
||||
Button(action: displayQR) {
|
||||
Text(VectorL10n.authenticationQrLoginStartDisplayQr)
|
||||
}
|
||||
.buttonStyle(SecondaryActionButtonStyle(font: theme.fonts.bodySB))
|
||||
.accessibilityIdentifier("displayQRButton")
|
||||
}
|
||||
.buttonStyle(SecondaryActionButtonStyle(font: theme.fonts.bodySB))
|
||||
.accessibilityIdentifier("displayQRButton")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,9 @@ enum AuthenticationQRLoginStartViewModelResult {
|
|||
|
||||
// MARK: View
|
||||
|
||||
struct AuthenticationQRLoginStartViewState: BindableState { }
|
||||
struct AuthenticationQRLoginStartViewState: BindableState {
|
||||
var canShowDisplayQRButton: Bool
|
||||
}
|
||||
|
||||
enum AuthenticationQRLoginStartViewAction {
|
||||
case scanQR
|
||||
|
|
|
@ -33,7 +33,7 @@ class AuthenticationQRLoginStartViewModel: AuthenticationQRLoginStartViewModelTy
|
|||
|
||||
init(qrLoginService: QRLoginServiceProtocol) {
|
||||
self.qrLoginService = qrLoginService
|
||||
super.init(initialViewState: AuthenticationQRLoginStartViewState())
|
||||
super.init(initialViewState: AuthenticationQRLoginStartViewState(canShowDisplayQRButton: qrLoginService.canDisplayQR()))
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
|
|
@ -23,7 +23,8 @@ enum MockAuthenticationQRLoginStartScreenState: MockScreenState, CaseIterable {
|
|||
// A case for each state you want to represent
|
||||
// with specific, minimal associated data that will allow you
|
||||
// mock that screen.
|
||||
case `default`
|
||||
case displayQREnabled
|
||||
case displayQRDisabled
|
||||
|
||||
/// The associated screen
|
||||
var screenType: Any.Type {
|
||||
|
@ -33,12 +34,21 @@ enum MockAuthenticationQRLoginStartScreenState: MockScreenState, CaseIterable {
|
|||
/// A list of screen state definitions
|
||||
static var allCases: [MockAuthenticationQRLoginStartScreenState] {
|
||||
// Each of the presence statuses
|
||||
[.default]
|
||||
[.displayQREnabled, .displayQRDisabled]
|
||||
}
|
||||
|
||||
/// Generate the view struct for the screen state.
|
||||
var screenView: ([Any], AnyView) {
|
||||
let viewModel = AuthenticationQRLoginStartViewModel(qrLoginService: MockQRLoginService())
|
||||
let service: QRLoginServiceProtocol
|
||||
|
||||
switch self {
|
||||
case .displayQREnabled:
|
||||
service = MockQRLoginService(canDisplayQR: true)
|
||||
case .displayQRDisabled:
|
||||
service = MockQRLoginService(canDisplayQR: false)
|
||||
}
|
||||
|
||||
let viewModel = AuthenticationQRLoginStartViewModel(qrLoginService: service)
|
||||
|
||||
// can simulate service and viewModel actions here if needs be.
|
||||
|
||||
|
|
|
@ -18,8 +18,8 @@ import RiotSwiftUI
|
|||
import XCTest
|
||||
|
||||
class AuthenticationQRLoginStartUITests: MockScreenTestCase {
|
||||
func testDefault() {
|
||||
app.goToScreenWithIdentifier(MockAuthenticationQRLoginStartScreenState.default.title)
|
||||
func testDisplayQREnabled() {
|
||||
app.goToScreenWithIdentifier(MockAuthenticationQRLoginStartScreenState.displayQREnabled.title)
|
||||
|
||||
XCTAssertTrue(app.staticTexts["titleLabel"].exists)
|
||||
XCTAssertTrue(app.staticTexts["subtitleLabel"].exists)
|
||||
|
@ -32,4 +32,18 @@ class AuthenticationQRLoginStartUITests: MockScreenTestCase {
|
|||
XCTAssertTrue(displayQRButton.exists)
|
||||
XCTAssertTrue(displayQRButton.isEnabled)
|
||||
}
|
||||
|
||||
func testDisplayQRDisabled() {
|
||||
app.goToScreenWithIdentifier(MockAuthenticationQRLoginStartScreenState.displayQRDisabled.title)
|
||||
|
||||
XCTAssertTrue(app.staticTexts["titleLabel"].exists)
|
||||
XCTAssertTrue(app.staticTexts["subtitleLabel"].exists)
|
||||
|
||||
let scanQRButton = app.buttons["scanQRButton"]
|
||||
XCTAssertTrue(scanQRButton.exists)
|
||||
XCTAssertTrue(scanQRButton.isEnabled)
|
||||
|
||||
let displayQRButton = app.buttons["displayQRButton"]
|
||||
XCTAssertFalse(displayQRButton.exists)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,10 @@ class AuthenticationQRLoginStartViewModelTests: XCTestCase {
|
|||
context = viewModel.context
|
||||
}
|
||||
|
||||
func testDisplayQRButtonVisibility() {
|
||||
XCTAssertTrue(viewModel.context.viewState.canShowDisplayQRButton)
|
||||
}
|
||||
|
||||
func testScanQR() {
|
||||
var result: AuthenticationQRLoginStartViewModelResult?
|
||||
|
||||
|
|
|
@ -88,13 +88,15 @@ struct AuthenticationQRLoginStartScreen: View {
|
|||
.padding(.bottom, 8)
|
||||
.accessibilityIdentifier("scanQRButton")
|
||||
|
||||
LabelledDivider(label: VectorL10n.authenticationQrLoginStartNeedAlternative)
|
||||
if context.viewState.canShowDisplayQRButton {
|
||||
LabelledDivider(label: VectorL10n.authenticationQrLoginStartNeedAlternative)
|
||||
|
||||
Button(action: displayQR) {
|
||||
Text(VectorL10n.authenticationQrLoginStartDisplayQr)
|
||||
Button(action: displayQR) {
|
||||
Text(VectorL10n.authenticationQrLoginStartDisplayQr)
|
||||
}
|
||||
.buttonStyle(SecondaryActionButtonStyle(font: theme.fonts.bodySB))
|
||||
.accessibilityIdentifier("displayQRButton")
|
||||
}
|
||||
.buttonStyle(SecondaryActionButtonStyle(font: theme.fonts.bodySB))
|
||||
.accessibilityIdentifier("displayQRButton")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue