mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
QR Code scan view (#2674)
This commit is contained in:
@@ -418,6 +418,12 @@
|
||||
"screen_invites_decline_direct_chat_title" = "Decline chat";
|
||||
"screen_invites_empty_list" = "No Invites";
|
||||
"screen_invites_invited_you" = "%1$@ (%2$@) invited you";
|
||||
"screen_join_room_join_action" = "Join room";
|
||||
"screen_join_room_knock_action" = "Knock to join";
|
||||
"screen_join_room_subtitle_knock" = "Click the button below and a room administrator will be notified. You’ll be able to join the conversation once approved.";
|
||||
"screen_join_room_subtitle_no_preview" = "You must be a member of this room to view the message history.";
|
||||
"screen_join_room_title_knock" = "Want to join this room?";
|
||||
"screen_join_room_title_no_preview" = "Preview is not available";
|
||||
"screen_key_backup_disable_confirmation_action_turn_off" = "Turn off";
|
||||
"screen_key_backup_disable_confirmation_description" = "You will lose your encrypted messages if you are signed out of all devices.";
|
||||
"screen_key_backup_disable_confirmation_title" = "Are you sure you want to turn off backup?";
|
||||
@@ -476,6 +482,7 @@
|
||||
"screen_polls_history_filter_ongoing" = "Ongoing";
|
||||
"screen_polls_history_filter_past" = "Past";
|
||||
"screen_polls_history_title" = "Polls";
|
||||
"screen_qr_code_login_connecting_subtitle" = "Establishing connection";
|
||||
"screen_qr_code_login_initial_state_item_1" = "Open Element on a desktop device";
|
||||
"screen_qr_code_login_initial_state_item_2" = "Click on your avatar";
|
||||
"screen_qr_code_login_initial_state_item_3" = "Select %1$@";
|
||||
@@ -483,6 +490,10 @@
|
||||
"screen_qr_code_login_initial_state_item_4" = "Select %1$@";
|
||||
"screen_qr_code_login_initial_state_item_4_action" = "“Show QR code”";
|
||||
"screen_qr_code_login_initial_state_title" = "Open Element on another device to get the QR code";
|
||||
"screen_qr_code_login_invalid_scan_state_description" = "Use the QR code shown on the other device.";
|
||||
"screen_qr_code_login_invalid_scan_state_retry_button" = "Try Again";
|
||||
"screen_qr_code_login_invalid_scan_state_subtitle" = "Wrong QR code";
|
||||
"screen_qr_code_login_scanning_state_title" = "Scan the QR code";
|
||||
"screen_recovery_key_change_description" = "Get a new recovery key if you've lost your existing one. After changing your recovery key, your old one will no longer work.";
|
||||
"screen_recovery_key_change_generate_key" = "Generate a new recovery key";
|
||||
"screen_recovery_key_change_generate_key_description" = "Make sure you can store your recovery key somewhere safe";
|
||||
|
||||
@@ -406,7 +406,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
navigationRootCoordinator: navigationRootCoordinator,
|
||||
appSettings: appSettings,
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
orientationManager: windowManager)
|
||||
authenticationFlowCoordinator?.delegate = self
|
||||
|
||||
authenticationFlowCoordinator?.start()
|
||||
|
||||
@@ -30,6 +30,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let appSettings: AppSettings
|
||||
private let analytics: AnalyticsService
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
private let orientationManager: OrientationManagerProtocol
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
@@ -45,13 +46,15 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
navigationRootCoordinator: NavigationRootCoordinator,
|
||||
appSettings: AppSettings,
|
||||
analytics: AnalyticsService,
|
||||
userIndicatorController: UserIndicatorControllerProtocol) {
|
||||
userIndicatorController: UserIndicatorControllerProtocol,
|
||||
orientationManager: WindowManagerProtocol) {
|
||||
self.authenticationService = authenticationService
|
||||
self.bugReportService = bugReportService
|
||||
self.navigationRootCoordinator = navigationRootCoordinator
|
||||
self.appSettings = appSettings
|
||||
self.analytics = analytics
|
||||
self.userIndicatorController = userIndicatorController
|
||||
self.orientationManager = orientationManager
|
||||
|
||||
navigationStackCoordinator = NavigationStackCoordinator()
|
||||
}
|
||||
@@ -103,7 +106,8 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
|
||||
private func startQRCodeLogin() {
|
||||
let coordinator = QRCodeLoginScreenCoordinator(parameters: .init(qrCodeLoginService: QRCodeLoginService()))
|
||||
let coordinator = QRCodeLoginScreenCoordinator(parameters: .init(qrCodeLoginService: QRCodeLoginService(),
|
||||
orientationManager: orientationManager))
|
||||
coordinator.actionsPublisher.sink { [weak self] action in
|
||||
guard let self else {
|
||||
return
|
||||
|
||||
@@ -1039,6 +1039,18 @@ internal enum L10n {
|
||||
internal static func screenInvitesInvitedYou(_ p1: Any, _ p2: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_invites_invited_you", String(describing: p1), String(describing: p2))
|
||||
}
|
||||
/// Join room
|
||||
internal static var screenJoinRoomJoinAction: String { return L10n.tr("Localizable", "screen_join_room_join_action") }
|
||||
/// Knock to join
|
||||
internal static var screenJoinRoomKnockAction: String { return L10n.tr("Localizable", "screen_join_room_knock_action") }
|
||||
/// Click the button below and a room administrator will be notified. You’ll be able to join the conversation once approved.
|
||||
internal static var screenJoinRoomSubtitleKnock: String { return L10n.tr("Localizable", "screen_join_room_subtitle_knock") }
|
||||
/// You must be a member of this room to view the message history.
|
||||
internal static var screenJoinRoomSubtitleNoPreview: String { return L10n.tr("Localizable", "screen_join_room_subtitle_no_preview") }
|
||||
/// Want to join this room?
|
||||
internal static var screenJoinRoomTitleKnock: String { return L10n.tr("Localizable", "screen_join_room_title_knock") }
|
||||
/// Preview is not available
|
||||
internal static var screenJoinRoomTitleNoPreview: String { return L10n.tr("Localizable", "screen_join_room_title_no_preview") }
|
||||
/// Turn off
|
||||
internal static var screenKeyBackupDisableConfirmationActionTurnOff: String { return L10n.tr("Localizable", "screen_key_backup_disable_confirmation_action_turn_off") }
|
||||
/// You will lose your encrypted messages if you are signed out of all devices.
|
||||
@@ -1167,6 +1179,8 @@ internal enum L10n {
|
||||
internal static var screenPollsHistoryFilterPast: String { return L10n.tr("Localizable", "screen_polls_history_filter_past") }
|
||||
/// Polls
|
||||
internal static var screenPollsHistoryTitle: String { return L10n.tr("Localizable", "screen_polls_history_title") }
|
||||
/// Establishing connection
|
||||
internal static var screenQrCodeLoginConnectingSubtitle: String { return L10n.tr("Localizable", "screen_qr_code_login_connecting_subtitle") }
|
||||
/// Open Element on a desktop device
|
||||
internal static var screenQrCodeLoginInitialStateItem1: String { return L10n.tr("Localizable", "screen_qr_code_login_initial_state_item_1") }
|
||||
/// Click on your avatar
|
||||
@@ -1185,6 +1199,14 @@ internal enum L10n {
|
||||
internal static var screenQrCodeLoginInitialStateItem4Action: String { return L10n.tr("Localizable", "screen_qr_code_login_initial_state_item_4_action") }
|
||||
/// Open Element on another device to get the QR code
|
||||
internal static var screenQrCodeLoginInitialStateTitle: String { return L10n.tr("Localizable", "screen_qr_code_login_initial_state_title") }
|
||||
/// Use the QR code shown on the other device.
|
||||
internal static var screenQrCodeLoginInvalidScanStateDescription: String { return L10n.tr("Localizable", "screen_qr_code_login_invalid_scan_state_description") }
|
||||
/// Try Again
|
||||
internal static var screenQrCodeLoginInvalidScanStateRetryButton: String { return L10n.tr("Localizable", "screen_qr_code_login_invalid_scan_state_retry_button") }
|
||||
/// Wrong QR code
|
||||
internal static var screenQrCodeLoginInvalidScanStateSubtitle: String { return L10n.tr("Localizable", "screen_qr_code_login_invalid_scan_state_subtitle") }
|
||||
/// Scan the QR code
|
||||
internal static var screenQrCodeLoginScanningStateTitle: String { return L10n.tr("Localizable", "screen_qr_code_login_scanning_state_title") }
|
||||
/// Get a new recovery key if you've lost your existing one. After changing your recovery key, your old one will no longer work.
|
||||
internal static var screenRecoveryKeyChangeDescription: String { return L10n.tr("Localizable", "screen_recovery_key_change_description") }
|
||||
/// Generate a new recovery key
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Copyright 2024 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 Foundation
|
||||
|
||||
struct QRCodeLoginServiceMockConfiguration {
|
||||
var isAuthorized = true
|
||||
}
|
||||
|
||||
extension QRCodeLoginServiceMock {
|
||||
convenience init(configuration: QRCodeLoginServiceMockConfiguration) {
|
||||
self.init()
|
||||
requestAuthorizationIfNeededReturnValue = configuration.isAuthorized
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -30,9 +30,11 @@ class AuthenticationStartScreenViewModel: AuthenticationStartScreenViewModelType
|
||||
init(appSettings: AppSettings) {
|
||||
self.appSettings = appSettings
|
||||
super.init(initialViewState: AuthenticationStartScreenViewState())
|
||||
appSettings.$qrCodeLoginEnabled
|
||||
.weakAssign(to: \.state.isQRCodeLoginEnabled, on: self)
|
||||
.store(in: &cancellables)
|
||||
if !ProcessInfo.processInfo.isiOSAppOnMac {
|
||||
appSettings.$qrCodeLoginEnabled
|
||||
.weakAssign(to: \.state.isQRCodeLoginEnabled, on: self)
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
|
||||
override func process(viewAction: AuthenticationStartScreenViewAction) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import SwiftUI
|
||||
|
||||
struct QRCodeLoginScreenCoordinatorParameters {
|
||||
let qrCodeLoginService: QRCodeLoginServiceProtocol
|
||||
let orientationManager: OrientationManagerProtocol
|
||||
}
|
||||
|
||||
enum QRCodeLoginScreenCoordinatorAction {
|
||||
@@ -29,6 +30,7 @@ enum QRCodeLoginScreenCoordinatorAction {
|
||||
|
||||
final class QRCodeLoginScreenCoordinator: CoordinatorProtocol {
|
||||
private let viewModel: QRCodeLoginScreenViewModelProtocol
|
||||
private let orientationManager: OrientationManagerProtocol
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
@@ -39,6 +41,7 @@ final class QRCodeLoginScreenCoordinator: CoordinatorProtocol {
|
||||
|
||||
init(parameters: QRCodeLoginScreenCoordinatorParameters) {
|
||||
viewModel = QRCodeLoginScreenViewModel(qrCodeLoginService: parameters.qrCodeLoginService)
|
||||
orientationManager = parameters.orientationManager
|
||||
}
|
||||
|
||||
func start() {
|
||||
@@ -52,6 +55,13 @@ final class QRCodeLoginScreenCoordinator: CoordinatorProtocol {
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
orientationManager.setOrientation(.portrait)
|
||||
orientationManager.lockOrientation(.portrait)
|
||||
}
|
||||
|
||||
func stop() {
|
||||
orientationManager.lockOrientation(.all)
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
|
||||
@@ -56,12 +56,24 @@ enum QRCodeLoginScreenViewAction {
|
||||
case startScan
|
||||
}
|
||||
|
||||
enum QRCodeLoginState {
|
||||
enum QRCodeLoginState: Equatable {
|
||||
/// Initial state where the user is informed how to perform the scan
|
||||
case initial
|
||||
case scanning
|
||||
/// The camera is scanning
|
||||
case scan(QRCodeLoginScanningState)
|
||||
/// Any full screen error state
|
||||
case error(QRCodeLoginErrorState)
|
||||
|
||||
enum QRCodeLoginErrorState {
|
||||
enum QRCodeLoginErrorState: Equatable {
|
||||
case noCameraPermission
|
||||
}
|
||||
|
||||
enum QRCodeLoginScanningState: Equatable {
|
||||
/// the qr code is scanning
|
||||
case scanning
|
||||
/// the qr code has been detected and is being processed
|
||||
case connecting
|
||||
/// the qr code has been processed and is invalid
|
||||
case invalid
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,18 @@ class QRCodeLoginScreenViewModel: QRCodeLoginScreenViewModelType, QRCodeLoginScr
|
||||
}
|
||||
|
||||
private func startScanIfPossible() async {
|
||||
state.state = await qrCodeLoginService.requestAuthorizationIfNeeded() ? .scanning : .error(.noCameraPermission)
|
||||
state.state = await qrCodeLoginService.requestAuthorizationIfNeeded() ? .scan(.scanning) : .error(.noCameraPermission)
|
||||
}
|
||||
|
||||
/// Only for mocking initial states
|
||||
fileprivate init(state: QRCodeLoginState) {
|
||||
qrCodeLoginService = QRCodeLoginServiceMock(configuration: .init())
|
||||
super.init(initialViewState: .init(state: state))
|
||||
}
|
||||
}
|
||||
|
||||
extension QRCodeLoginScreenViewModel {
|
||||
static func mock(state: QRCodeLoginState) -> QRCodeLoginScreenViewModel {
|
||||
QRCodeLoginScreenViewModel(state: state)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import SwiftUI
|
||||
|
||||
struct QRCodeLoginScreen: View {
|
||||
@ObservedObject var context: QRCodeLoginScreenViewModel.Context
|
||||
@State private var qrFrame = CGRect.zero
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
@@ -36,13 +37,15 @@ struct QRCodeLoginScreen: View {
|
||||
switch context.viewState.state {
|
||||
case .initial:
|
||||
initialContent
|
||||
case .scanning, .error:
|
||||
// TODO: Handle states
|
||||
case .scan:
|
||||
qrScanContent
|
||||
case .error:
|
||||
// TODO: Handle error states
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
|
||||
var initialContent: some View {
|
||||
private var initialContent: some View {
|
||||
FullscreenDialog {
|
||||
VStack(alignment: .leading, spacing: 40) {
|
||||
VStack(spacing: 16) {
|
||||
@@ -64,6 +67,75 @@ struct QRCodeLoginScreen: View {
|
||||
.buttonStyle(.compound(.primary))
|
||||
}
|
||||
}
|
||||
|
||||
private var qrScanContent: some View {
|
||||
FullscreenDialog {
|
||||
VStack(spacing: 40) {
|
||||
VStack(spacing: 16) {
|
||||
HeroImage(icon: \.takePhotoSolid, style: .subtle)
|
||||
|
||||
Text(L10n.screenQrCodeLoginScanningStateTitle)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.font(.compound.headingMDBold)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
|
||||
qrScanner
|
||||
}
|
||||
} bottomContent: {
|
||||
qrScanFooter
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var qrScanFooter: some View {
|
||||
if case let .scan(scanState) = context.viewState.state {
|
||||
switch scanState {
|
||||
case .connecting:
|
||||
VStack(spacing: 4) {
|
||||
ProgressView()
|
||||
Text(L10n.screenQrCodeLoginConnectingSubtitle)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.font(.compound.bodySM)
|
||||
}
|
||||
case .scanning:
|
||||
// To keep the spacing consistent between states
|
||||
Button("") { }
|
||||
.buttonStyle(.compound(.primary))
|
||||
.hidden()
|
||||
case .invalid:
|
||||
VStack(spacing: 16) {
|
||||
Button(L10n.screenQrCodeLoginInvalidScanStateRetryButton) {
|
||||
// TODO: Implement try again
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
|
||||
VStack(spacing: 0) {
|
||||
Label(L10n.screenQrCodeLoginInvalidScanStateSubtitle, icon: \.error, iconSize: .medium, relativeTo: .compound.bodyMDSemibold)
|
||||
.labelStyle(.custom(spacing: 10))
|
||||
.font(.compound.bodyMDSemibold)
|
||||
.foregroundColor(.compound.textCriticalPrimary)
|
||||
|
||||
Text(L10n.screenQrCodeLoginInvalidScanStateDescription)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.font(.compound.bodySM)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var qrScanner: some View {
|
||||
QRCodeScannerView()
|
||||
.aspectRatio(1.0, contentMode: .fill)
|
||||
.frame(maxWidth: 312)
|
||||
.readFrame($qrFrame)
|
||||
.background(.compound.bgCanvasDefault)
|
||||
.overlay(
|
||||
QRScannerViewOverlay(length: qrFrame.height)
|
||||
)
|
||||
}
|
||||
|
||||
@ToolbarContentBuilder
|
||||
private var toolbar: some ToolbarContent {
|
||||
@@ -75,12 +147,53 @@ struct QRCodeLoginScreen: View {
|
||||
}
|
||||
}
|
||||
|
||||
private struct QRScannerViewOverlay: View {
|
||||
let length: CGFloat
|
||||
|
||||
private let dashRatio: CGFloat = 80.0 / 312.0
|
||||
private let emptyRatio: CGFloat = 232.0 / 312.0
|
||||
private let dashPhaseRatio: CGFloat = 40.0 / 312.0
|
||||
|
||||
private var dashLength: CGFloat {
|
||||
length * dashRatio
|
||||
}
|
||||
|
||||
private var emptyLength: CGFloat {
|
||||
length * emptyRatio
|
||||
}
|
||||
|
||||
private var dashPhase: CGFloat {
|
||||
length * dashPhaseRatio
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Rectangle()
|
||||
.stroke(.compound.textPrimary, style: StrokeStyle(lineWidth: 4.0, lineCap: .square, dash: [dashLength, emptyLength], dashPhase: dashPhase))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct QRCodeLoginScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = QRCodeLoginScreenViewModel(qrCodeLoginService: QRCodeLoginServiceMock())
|
||||
static let initialStateViewModel = QRCodeLoginScreenViewModel.mock(state: .initial)
|
||||
|
||||
static let scanningStateViewModel = QRCodeLoginScreenViewModel.mock(state: .scan(.scanning))
|
||||
|
||||
static let connectingStateViewModel = QRCodeLoginScreenViewModel.mock(state: .scan(.connecting))
|
||||
|
||||
static let invalidStateViewModel = QRCodeLoginScreenViewModel.mock(state: .scan(.invalid))
|
||||
|
||||
static var previews: some View {
|
||||
QRCodeLoginScreen(context: viewModel.context)
|
||||
QRCodeLoginScreen(context: initialStateViewModel.context)
|
||||
.previewDisplayName("Initial")
|
||||
|
||||
QRCodeLoginScreen(context: scanningStateViewModel.context)
|
||||
.previewDisplayName("Scanning")
|
||||
|
||||
QRCodeLoginScreen(context: connectingStateViewModel.context)
|
||||
.previewDisplayName("Connecting")
|
||||
|
||||
QRCodeLoginScreen(context: invalidStateViewModel.context)
|
||||
.previewDisplayName("Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// Copyright 2024 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 SwiftUI
|
||||
import UIKit
|
||||
|
||||
struct QRCodeScannerView: UIViewControllerRepresentable {
|
||||
func makeUIViewController(context: Context) -> QRScannerController {
|
||||
let controller = QRScannerController()
|
||||
|
||||
return controller
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: QRScannerController, context: Context) { }
|
||||
}
|
||||
|
||||
final class QRScannerController: UIViewController {
|
||||
private var captureSession = AVCaptureSession()
|
||||
private var videoPreviewLayer: AVCaptureVideoPreviewLayer?
|
||||
private var qrCodeFrameView: UIView?
|
||||
|
||||
var delegate: AVCaptureMetadataOutputObjectsDelegate?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Get the back-facing camera for capturing videos
|
||||
guard let captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {
|
||||
MXLog.error("Failed to get the camera device")
|
||||
return
|
||||
}
|
||||
|
||||
let videoInput: AVCaptureDeviceInput
|
||||
|
||||
do {
|
||||
// Get an instance of the AVCaptureDeviceInput class using the previous device object.
|
||||
videoInput = try AVCaptureDeviceInput(device: captureDevice)
|
||||
|
||||
} catch {
|
||||
// If any error occurs, simply print it out and don't continue any more.
|
||||
MXLog.error("ACaptureDeviceInput error: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
// Set the input device on the capture session.
|
||||
captureSession.addInput(videoInput)
|
||||
|
||||
// Initialize a AVCaptureMetadataOutput object and set it as the output device to the capture session.
|
||||
let captureMetadataOutput = AVCaptureMetadataOutput()
|
||||
captureSession.addOutput(captureMetadataOutput)
|
||||
|
||||
// Set delegate and use the default dispatch queue to execute the call back
|
||||
captureMetadataOutput.setMetadataObjectsDelegate(delegate, queue: DispatchQueue.main)
|
||||
captureMetadataOutput.metadataObjectTypes = [.qr]
|
||||
|
||||
// Initialize the video preview layer and add it as a sublayer to the viewPreview view's layer.
|
||||
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
|
||||
videoPreviewLayer = previewLayer
|
||||
videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
||||
videoPreviewLayer?.frame = view.layer.bounds
|
||||
view.layer.addSublayer(previewLayer)
|
||||
|
||||
// Start video capture.
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
self.captureSession.startRunning()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillLayoutSubviews() {
|
||||
super.viewWillLayoutSubviews()
|
||||
videoPreviewLayer?.frame = view.layer.bounds
|
||||
}
|
||||
}
|
||||
@@ -128,7 +128,8 @@ class MockScreen: Identifiable {
|
||||
navigationRootCoordinator: navigationRootCoordinator,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
orientationManager: windowManager)
|
||||
flowCoordinator.start()
|
||||
retainedState.append(flowCoordinator)
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user