mirror of
https://github.com/vector-im/element-ios.git
synced 2024-10-01 16:52:40 +00:00
19afad1f18
* Display QR button on login screen if HS supports * Create start screen * Add build flag * Connect start screen to the login * QR display screen * Move `LabelledDividerView` into separate file * Show display QR screen on button tap * Add swift concurreny to CameraAccessManager * Introduce `QRLoginServiceProtocol` * Use new service in screens * Introduce scan QR code screen * Remove hardcoded service availability * Remove unnecessary import * Add confirmation screen * Add loading screen * Fix ZXingObjc targets * Add failure screen * Add strings * Various UI tweaks, navigation according to the service state * Fix tests * Add string for invalid QR error * Add QR login service mode
184 lines
5.1 KiB
Swift
184 lines
5.1 KiB
Swift
//
|
|
// Copyright 2022 New Vector Ltd
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
import AVFoundation
|
|
import Combine
|
|
import Foundation
|
|
import MatrixSDK
|
|
import SwiftUI
|
|
import ZXingObjC
|
|
|
|
// MARK: - QRLoginService
|
|
|
|
class QRLoginService: NSObject, QRLoginServiceProtocol {
|
|
private let client: AuthenticationRestClient
|
|
private var isCameraReady = false
|
|
private lazy var zxCapture = ZXCapture()
|
|
|
|
private let cameraAccessManager = CameraAccessManager()
|
|
|
|
init(client: AuthenticationRestClient,
|
|
mode: QRLoginServiceMode,
|
|
state: QRLoginServiceState = .initial) {
|
|
self.client = client
|
|
self.mode = mode
|
|
self.state = state
|
|
super.init()
|
|
}
|
|
|
|
// MARK: QRLoginServiceProtocol
|
|
|
|
let mode: QRLoginServiceMode
|
|
|
|
var state: QRLoginServiceState {
|
|
didSet {
|
|
if state != oldValue {
|
|
callbacks.send(.didUpdateState)
|
|
}
|
|
}
|
|
}
|
|
|
|
let callbacks = PassthroughSubject<QRLoginServiceCallback, Never>()
|
|
|
|
func isServiceAvailable() async throws -> Bool {
|
|
guard BuildSettings.enableQRLogin else {
|
|
return false
|
|
}
|
|
return try await client.supportedMatrixVersions().supportsQRLogin
|
|
}
|
|
|
|
func generateQRCode() async throws -> QRLoginCode {
|
|
let transport = QRLoginRendezvousTransportDetails(type: "http.v1",
|
|
uri: "")
|
|
let rendezvous = QRLoginRendezvous(transport: transport,
|
|
algorithm: "m.rendezvous.v1.curve25519-aes-sha256",
|
|
key: "")
|
|
return QRLoginCode(user: client.credentials.userId,
|
|
initiator: .new,
|
|
rendezvous: rendezvous)
|
|
}
|
|
|
|
func scannerView() -> AnyView {
|
|
let frame = UIScreen.main.bounds
|
|
let view = UIView(frame: frame)
|
|
zxCapture.layer.frame = frame
|
|
view.layer.addSublayer(zxCapture.layer)
|
|
return AnyView(ViewWrapper(view: view))
|
|
}
|
|
|
|
func startScanning() {
|
|
Task { @MainActor in
|
|
if cameraAccessManager.isCameraAvailable {
|
|
let granted = await cameraAccessManager.requestCameraAccessIfNeeded()
|
|
if granted {
|
|
state = .scanningQR
|
|
zxCapture.delegate = self
|
|
zxCapture.camera = zxCapture.back()
|
|
zxCapture.start()
|
|
} else {
|
|
state = .failed(error: .noCameraAccess)
|
|
}
|
|
} else {
|
|
state = .failed(error: .noCameraAvailable)
|
|
}
|
|
}
|
|
}
|
|
|
|
func stopScanning(destroy: Bool) {
|
|
guard zxCapture.running else {
|
|
return
|
|
}
|
|
|
|
if destroy {
|
|
zxCapture.hard_stop()
|
|
} else {
|
|
zxCapture.stop()
|
|
}
|
|
}
|
|
|
|
func processScannedQR(_ data: Data) {
|
|
state = .connectingToDevice
|
|
do {
|
|
let code = try JSONDecoder().decode(QRLoginCode.self, from: data)
|
|
MXLog.debug("[QRLoginService] processScannedQR: \(code)")
|
|
// TODO: implement
|
|
} catch {
|
|
state = .failed(error: .invalidQR)
|
|
}
|
|
}
|
|
|
|
func confirmCode() {
|
|
switch state {
|
|
case .waitingForConfirmation(let code):
|
|
// TODO: implement
|
|
break
|
|
default:
|
|
return
|
|
}
|
|
}
|
|
|
|
func restart() {
|
|
state = .initial
|
|
}
|
|
|
|
func reset() {
|
|
stopScanning(destroy: false)
|
|
state = .initial
|
|
}
|
|
|
|
deinit {
|
|
stopScanning(destroy: true)
|
|
}
|
|
|
|
// MARK: Private
|
|
}
|
|
|
|
// MARK: - ZXCaptureDelegate
|
|
|
|
extension QRLoginService: ZXCaptureDelegate {
|
|
func captureCameraIsReady(_ capture: ZXCapture!) {
|
|
isCameraReady = true
|
|
}
|
|
|
|
func captureResult(_ capture: ZXCapture!, result: ZXResult!) {
|
|
guard isCameraReady,
|
|
let result = result,
|
|
result.barcodeFormat == kBarcodeFormatQRCode else {
|
|
return
|
|
}
|
|
|
|
stopScanning(destroy: false)
|
|
|
|
if let bytes = result.resultMetadata.object(forKey: kResultMetadataTypeByteSegments.rawValue) as? NSArray,
|
|
let byteArray = bytes.firstObject as? ZXByteArray {
|
|
let data = Data(bytes: UnsafeRawPointer(byteArray.array), count: Int(byteArray.length))
|
|
|
|
callbacks.send(.didScanQR(data))
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - ViewWrapper
|
|
|
|
private struct ViewWrapper: UIViewRepresentable {
|
|
var view: UIView
|
|
|
|
func makeUIView(context: Context) -> some UIView {
|
|
view
|
|
}
|
|
|
|
func updateUIView(_ uiView: UIViewType, context: Context) { }
|
|
}
|