mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-17 15:09:31 +02:00
MESSENGER-4093 scan permalinks as qr
This commit is contained in:
committed by
Frank Rotermund
parent
11ed494648
commit
00222176c7
@@ -108,7 +108,7 @@ class BWIBuildSettings: NSObject {
|
||||
var bwiAllowRoomPermalink = false
|
||||
|
||||
var bwiAllowUserPermalink = false
|
||||
|
||||
|
||||
var bwiCheckAppVersion = true
|
||||
|
||||
var bwiNotificationTimes = true
|
||||
@@ -544,10 +544,16 @@ class BWIBuildSettings: NSObject {
|
||||
// internal html page for netiquette in en and de
|
||||
var netiquetteEnHTML = "netiquette_en"
|
||||
var netiquetteDeHTML = "netiquette_de"
|
||||
|
||||
|
||||
// MARK: - Scan server qr code
|
||||
var scanServerQRCode = true
|
||||
var allowScanServerQRCode = true
|
||||
|
||||
// MARK: - Login with qr code
|
||||
var allowLoginWithQR = false // should be set by the server but we disable it with false also in the app
|
||||
|
||||
// MARK: - Scan permalink qr code
|
||||
var clientPermalinkBaseUrl = ""
|
||||
var allowScanPermalinkQRCode = false
|
||||
|
||||
// MARK: - Maintenance
|
||||
var enableMaintenanceInfoOnWelcomeScreen = false
|
||||
|
||||
15
Riot/Assets/Images.xcassets/BWI/qrcode_viewfinder.imageset/Contents.json
vendored
Normal file
15
Riot/Assets/Images.xcassets/BWI/qrcode_viewfinder.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon_qrcode.viewfinder.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
10
Riot/Assets/Images.xcassets/BWI/qrcode_viewfinder.imageset/icon_qrcode.viewfinder.svg
vendored
Normal file
10
Riot/Assets/Images.xcassets/BWI/qrcode_viewfinder.imageset/icon_qrcode.viewfinder.svg
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_30_95)">
|
||||
<path d="M0.78125 6.23047C1.29883 6.23047 1.57227 5.9375 1.57227 5.42969V3.125C1.57227 2.10938 2.10938 1.5918 3.08594 1.5918H5.44922C5.9668 1.5918 6.25 1.30859 6.25 0.800781C6.25 0.292969 5.9668 0.0195312 5.44922 0.0195312H3.06641C1.02539 0.0195312 0 1.02539 0 3.03711V5.42969C0 5.9375 0.283203 6.23047 0.78125 6.23047ZM18.3301 6.23047C18.8477 6.23047 19.1211 5.9375 19.1211 5.42969V3.03711C19.1211 1.02539 18.0957 0.0195312 16.0547 0.0195312H13.6621C13.1543 0.0195312 12.8711 0.292969 12.8711 0.800781C12.8711 1.30859 13.1543 1.5918 13.6621 1.5918H16.0254C16.9922 1.5918 17.5488 2.10938 17.5488 3.125V5.42969C17.5488 5.9375 17.832 6.23047 18.3301 6.23047ZM3.06641 19.1309H5.44922C5.9668 19.1309 6.25 18.8477 6.25 18.3496C6.25 17.8418 5.9668 17.5586 5.44922 17.5586H3.08594C2.10938 17.5586 1.57227 17.041 1.57227 16.0254V13.7207C1.57227 13.2031 1.28906 12.9199 0.78125 12.9199C0.273438 12.9199 0 13.2031 0 13.7207V16.1035C0 18.125 1.02539 19.1309 3.06641 19.1309ZM13.6621 19.1309H16.0547C18.0957 19.1309 19.1211 18.1152 19.1211 16.1035V13.7207C19.1211 13.2031 18.8379 12.9199 18.3301 12.9199C17.8223 12.9199 17.5488 13.2031 17.5488 13.7207V16.0254C17.5488 17.041 16.9922 17.5586 16.0254 17.5586H13.6621C13.1543 17.5586 12.8711 17.8418 12.8711 18.3496C12.8711 18.8477 13.1543 19.1309 13.6621 19.1309ZM5.30273 14.2578H8.70117C8.94531 14.2578 9.13086 14.0723 9.13086 13.8281V10.4297C9.13086 10.1855 8.94531 10 8.70117 10H5.30273C5.05859 10 4.87305 10.1855 4.87305 10.4297V13.8281C4.87305 14.0723 5.05859 14.2578 5.30273 14.2578ZM5.72266 13.4082V10.8496H8.28125V13.4082H5.72266ZM6.46484 12.6562H7.53906V11.5918H6.46484V12.6562ZM5.30273 9.15039H8.70117C8.94531 9.15039 9.13086 8.96484 9.13086 8.73047V5.32227C9.13086 5.08789 8.94531 4.89258 8.70117 4.89258H5.30273C5.05859 4.89258 4.87305 5.08789 4.87305 5.32227V8.73047C4.87305 8.96484 5.05859 9.15039 5.30273 9.15039ZM5.72266 8.30078V5.74219H8.28125V8.30078H5.72266ZM6.46484 7.55859H7.53906V6.48438H6.46484V7.55859ZM10.4004 9.15039H13.7988C14.043 9.15039 14.2285 8.96484 14.2285 8.73047V5.32227C14.2285 5.08789 14.043 4.89258 13.7988 4.89258H10.4004C10.1562 4.89258 9.9707 5.08789 9.9707 5.32227V8.73047C9.9707 8.96484 10.1562 9.15039 10.4004 9.15039ZM10.8203 8.30078V5.74219H13.3789V8.30078H10.8203ZM11.5723 7.55859H12.6367V6.48438H11.5723V7.55859ZM10.0977 14.1309H11.1621V13.0664H10.0977V14.1309ZM13.0371 14.1309H14.1016V13.0664H13.0371V14.1309ZM11.5625 12.6562H12.6367V11.5918H11.5625V12.6562ZM10.0977 11.1816H11.1621V10.1172H10.0977V11.1816ZM13.0371 11.1816H14.1016V10.1172H13.0371V11.1816Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_30_95">
|
||||
<rect width="19.1211" height="19.1309" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -571,6 +571,11 @@
|
||||
// MARK: - New Layout
|
||||
"all_chats_edit_layout_show_personal_notes" = "Notizenraum anzeigen";
|
||||
|
||||
// MARK: - Permalink Scan
|
||||
"room_recents_scan_qr_code" = "QR Code scannen";
|
||||
"room_recents_scan_failed_title" = "Scan fehlgeschlagen";
|
||||
"room_recents_scan_failed_message" = "Dieser QR Code entspricht keinem gültigen Permalink.";
|
||||
|
||||
// MARK: - Context Menu All Chats
|
||||
"room_recents_create_empty_room" = "Neuer Raum";
|
||||
"room_recents_start_chat_with" = "Neue Direktnachricht";
|
||||
|
||||
@@ -450,6 +450,11 @@
|
||||
// MARK: - New Layout
|
||||
"all_chats_edit_layout_show_personal_notes" = "Show personal notes";
|
||||
|
||||
// MARK: - Permalink Scan
|
||||
"room_recents_scan_qr_code" = "Scan QR Code";
|
||||
"room_recents_scan_failed_title" = "Scan failed";
|
||||
"room_recents_scan_failed_message" = "This qr code does not conform to a permlink.";
|
||||
|
||||
// MARK: Context Menu All Chats
|
||||
"room_recents_create_empty_room" = "New room";
|
||||
"room_recents_start_chat_with" = "New chat";
|
||||
|
||||
@@ -48,6 +48,7 @@ internal class Asset: NSObject {
|
||||
internal static let birthdayCake = ImageAsset(name: "birthday_cake")
|
||||
internal static let fileAttachmentIcon = ImageAsset(name: "file_attachment_icon")
|
||||
internal static let fileScanInfected = ImageAsset(name: "file_scan_infected")
|
||||
internal static let qrcodeViewfinder = ImageAsset(name: "qrcode_viewfinder")
|
||||
internal static let welcomeExperience1 = ImageAsset(name: "welcome_experience_1")
|
||||
internal static let welcomeExperience2 = ImageAsset(name: "welcome_experience_2")
|
||||
internal static let welcomeExperience3 = ImageAsset(name: "welcome_experience_3")
|
||||
|
||||
@@ -162,6 +162,11 @@ FOUNDATION_EXPORT NSString *const RecentsViewControllerDataReadyNotification;
|
||||
*/
|
||||
- (void)startChat;
|
||||
|
||||
/**
|
||||
Open the QR code scanner for scanning permalinks.
|
||||
*/
|
||||
- (void)scanPermalink;
|
||||
|
||||
/**
|
||||
Open screen to create a new room.
|
||||
*/
|
||||
|
||||
@@ -1917,6 +1917,28 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
|
||||
[self performSegueWithIdentifier:@"presentStartChat" sender:self];
|
||||
}
|
||||
|
||||
- (void)scanPermalink {
|
||||
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
||||
if(authStatus == AVAuthorizationStatusDenied) {
|
||||
UIAlertController* alert = [UIAlertController alertControllerWithTitle:BWIL10n.authenticationServerSelectionQrMissingAuthorizationTitle
|
||||
message:BWIL10n.authenticationServerSelectionQrMissingAuthorizationMessage
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
UIAlertAction* primaryAction = [UIAlertAction actionWithTitle:VectorL10n.settingsTitle style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * action) {
|
||||
[[UIApplication sharedApplication] openURL: [NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
|
||||
}];
|
||||
UIAlertAction* secondaryAction = [UIAlertAction actionWithTitle:VectorL10n.cancel style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction * action) {}];
|
||||
[alert addAction:primaryAction];
|
||||
[alert addAction:secondaryAction];
|
||||
[self presentViewController:alert animated:YES completion:nil];
|
||||
} else {
|
||||
UIViewController* viewController = [PermalinkQRCodeScannerController createFromSwiftUIView];
|
||||
[self presentViewController:viewController animated: YES completion: nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)createNewRoom
|
||||
{
|
||||
// Sanity check
|
||||
|
||||
@@ -22,6 +22,7 @@ enum AllChatsEditActionProviderOption {
|
||||
case createRoom
|
||||
case startChat
|
||||
case createSpace
|
||||
case scanPermalink
|
||||
}
|
||||
|
||||
protocol AllChatsEditActionProviderDelegate: AnyObject {
|
||||
@@ -50,29 +51,57 @@ class AllChatsEditActionProvider {
|
||||
// MARK: - RoomActionProviderProtocol
|
||||
|
||||
var menu: UIMenu {
|
||||
guard parentSpace != nil else {
|
||||
var createActions = [
|
||||
self.createRoomAction,
|
||||
self.startChatAction
|
||||
]
|
||||
if rootSpaceCount > 0 && BWIBuildSettings.shared.enableSpaces {
|
||||
createActions.insert(self.createSpaceAction, at: 0)
|
||||
if BWIBuildSettings.shared.allowScanPermalinkQRCode {
|
||||
guard parentSpace != nil else {
|
||||
var createActions = [
|
||||
self.exploreRoomsAction,
|
||||
self.createRoomAction,
|
||||
self.startChatAction
|
||||
]
|
||||
if rootSpaceCount > 0 && BWIBuildSettings.shared.enableSpaces {
|
||||
createActions.insert(self.createSpaceAction, at: 0)
|
||||
}
|
||||
return UIMenu(title: "", children: [
|
||||
self.scanPermalinkAction,
|
||||
UIMenu(title: "", options: .displayInline, children: createActions)
|
||||
])
|
||||
}
|
||||
|
||||
return UIMenu(title: "", children: [
|
||||
self.exploreRoomsAction,
|
||||
UIMenu(title: "", options: .displayInline, children: createActions)
|
||||
UIMenu(title: "", options: .displayInline, children: [
|
||||
self.scanPermalinkAction
|
||||
]),
|
||||
UIMenu(title: "", options: .displayInline, children: [
|
||||
self.exploreRoomsAction,
|
||||
self.createSpaceAction,
|
||||
self.createRoomAction
|
||||
])
|
||||
])
|
||||
} else {
|
||||
guard parentSpace != nil else {
|
||||
var createActions = [
|
||||
self.createRoomAction,
|
||||
self.startChatAction
|
||||
]
|
||||
if rootSpaceCount > 0 && BWIBuildSettings.shared.enableSpaces {
|
||||
createActions.insert(self.createSpaceAction, at: 0)
|
||||
}
|
||||
return UIMenu(title: "", children: [
|
||||
self.exploreRoomsAction,
|
||||
UIMenu(title: "", options: .displayInline, children: createActions)
|
||||
])
|
||||
}
|
||||
|
||||
return UIMenu(title: "", children: [
|
||||
UIMenu(title: "", options: .displayInline, children: [
|
||||
self.exploreRoomsAction
|
||||
]),
|
||||
UIMenu(title: "", options: .displayInline, children: [
|
||||
self.createSpaceAction,
|
||||
self.createRoomAction
|
||||
])
|
||||
])
|
||||
}
|
||||
|
||||
return UIMenu(title: "", children: [
|
||||
UIMenu(title: "", options: .displayInline, children: [
|
||||
self.exploreRoomsAction
|
||||
]),
|
||||
UIMenu(title: "", options: .displayInline, children: [
|
||||
self.createSpaceAction,
|
||||
self.createRoomAction
|
||||
])
|
||||
])
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
@@ -167,4 +196,14 @@ class AllChatsEditActionProvider {
|
||||
self.delegate?.allChatsEditActionProvider(self, didSelect: .createSpace)
|
||||
}
|
||||
}
|
||||
|
||||
private var scanPermalinkAction: UIAction {
|
||||
UIAction(title: BWIL10n.roomRecentsScanQrCode,
|
||||
image: Asset.Images.qrcodeViewfinder.image) { [weak self] action in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.delegate?.allChatsEditActionProvider(self, didSelect: .scanPermalink)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -770,6 +770,8 @@ extension AllChatsViewController: AllChatsEditActionProviderDelegate {
|
||||
startChat()
|
||||
case .createSpace:
|
||||
showCreateSpace(parentSpaceId: dataSource?.currentSpace?.spaceId)
|
||||
case .scanPermalink:
|
||||
scanPermalink()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ struct AuthenticationServerSelectionScreen: View {
|
||||
.padding(.top, OnboardingMetrics.topPaddingToNavigationBar)
|
||||
.padding(.bottom, 16)
|
||||
|
||||
if BWIBuildSettings.shared.scanServerQRCode {
|
||||
if BWIBuildSettings.shared.allowScanServerQRCode {
|
||||
scanButton
|
||||
.alert(isPresented: $showAlertForMissingCameraAuthorization) {
|
||||
Alert(
|
||||
|
||||
@@ -158,10 +158,8 @@ extension UserDefaults
|
||||
return "https://" + url
|
||||
} else if let url = ServerURLHelper.shared.httpsPermalink() {
|
||||
return url
|
||||
} else if let url = BuildSettings.clientPermalinkBaseUrl {
|
||||
return url
|
||||
} else {
|
||||
return nil
|
||||
return BWIBuildSettings.shared.clientPermalinkBaseUrl
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
struct AuthenticationServerSelectionQRCodeScanner: View {
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
@@ -44,102 +42,3 @@ struct AuthenticationServerSelectionQRCodeScanner: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ScannerView: UIViewControllerRepresentable {
|
||||
@Binding var qrCode: String
|
||||
@Binding var scanCompleted: Bool
|
||||
|
||||
class Coordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate {
|
||||
var parent: ScannerView
|
||||
|
||||
init(_ parent: ScannerView) {
|
||||
self.parent = parent
|
||||
}
|
||||
|
||||
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
|
||||
|
||||
if let metadataObject = metadataObjects.first {
|
||||
guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return }
|
||||
guard let stringValue = readableObject.stringValue else { return }
|
||||
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
|
||||
DispatchQueue.main.async {
|
||||
self.parent.qrCode = stringValue
|
||||
self.parent.scanCompleted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(self)
|
||||
}
|
||||
|
||||
func makeUIViewController(context: Context) -> some ScannerViewController {
|
||||
let controller = ScannerViewController()
|
||||
controller.delegate = context.coordinator
|
||||
return controller
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ScannerViewController: UIViewController {
|
||||
var captureSession: AVCaptureSession?
|
||||
var previewLayer: AVCaptureVideoPreviewLayer!
|
||||
let metadataOutput = AVCaptureMetadataOutput()
|
||||
weak var delegate: AVCaptureMetadataOutputObjectsDelegate?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
view.backgroundColor = UIColor.black
|
||||
captureSession = AVCaptureSession()
|
||||
|
||||
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video), let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice) else {
|
||||
captureSession = nil
|
||||
return
|
||||
}
|
||||
|
||||
if let captureSession = captureSession {
|
||||
guard captureSession.canAddInput(videoInput) && captureSession.canAddOutput(metadataOutput) else {
|
||||
self.captureSession = nil
|
||||
return
|
||||
}
|
||||
|
||||
captureSession.addInput(videoInput)
|
||||
captureSession.addOutput(metadataOutput)
|
||||
|
||||
metadataOutput.setMetadataObjectsDelegate(delegate, queue: .main)
|
||||
metadataOutput.metadataObjectTypes = [.qr]
|
||||
|
||||
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
|
||||
previewLayer.frame = view.layer.bounds
|
||||
previewLayer.videoGravity = .resizeAspectFill
|
||||
view.layer.addSublayer(previewLayer)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
let dispatchQueue = DispatchQueue(label: "AVCapturesession.startRunning", qos: .background)
|
||||
dispatchQueue.async{
|
||||
if (self.captureSession?.isRunning == false) {
|
||||
self.captureSession?.startRunning()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
let dispatchQueue = DispatchQueue(label: "AVCapturesession.stopRunning", qos: .background)
|
||||
dispatchQueue.async{
|
||||
if (self.captureSession?.isRunning == true) {
|
||||
self.captureSession?.stopRunning()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
71
bwi/QRCode/PermalinkQRCodeScanner.swift
Normal file
71
bwi/QRCode/PermalinkQRCodeScanner.swift
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
/*
|
||||
* Copyright (c) 2022 BWI GmbH
|
||||
*
|
||||
* 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 SwiftUI
|
||||
import UIKit
|
||||
|
||||
|
||||
class PermalinkQRCodeScannerController: NSObject {
|
||||
|
||||
@objc static func createFromSwiftUIView() -> UIViewController {
|
||||
return UIHostingController(rootView: PermalinkQRCodeScanner())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct PermalinkQRCodeScanner: View {
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
@State var qrCode: String = ""
|
||||
@State var scanCompleted = false
|
||||
@State var showInvalidCodeAlert = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ScannerView(qrCode: $qrCode, scanCompleted: $scanCompleted)
|
||||
.navigationTitle(BWIL10n.roomRecentsScanQrCode)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button(VectorL10n.close) {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $showInvalidCodeAlert) {
|
||||
Alert(
|
||||
title: Text(BWIL10n.roomRecentsScanFailedTitle),
|
||||
message: Text(BWIL10n.roomRecentsScanFailedMessage),
|
||||
dismissButton: .default(Text(VectorL10n.ok)))
|
||||
}
|
||||
}
|
||||
.onChange(of: scanCompleted) { newValue in
|
||||
if newValue {
|
||||
if !BWIBuildSettings.shared.clientPermalinkBaseUrl.isEmpty && qrCode.hasPrefix(BWIBuildSettings.shared.clientPermalinkBaseUrl) {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
||||
if let url = URL(string: qrCode) {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showInvalidCodeAlert = true
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
120
bwi/QRCode/ScannerView.swift
Normal file
120
bwi/QRCode/ScannerView.swift
Normal file
@@ -0,0 +1,120 @@
|
||||
//
|
||||
/*
|
||||
* Copyright (c) 2022 BWI GmbH
|
||||
*
|
||||
* 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 SwiftUI
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
|
||||
struct ScannerView: UIViewControllerRepresentable {
|
||||
@Binding var qrCode: String
|
||||
@Binding var scanCompleted: Bool
|
||||
|
||||
class Coordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate {
|
||||
var parent: ScannerView
|
||||
|
||||
init(_ parent: ScannerView) {
|
||||
self.parent = parent
|
||||
}
|
||||
|
||||
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
|
||||
|
||||
if let metadataObject = metadataObjects.first {
|
||||
guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return }
|
||||
guard let stringValue = readableObject.stringValue else { return }
|
||||
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
|
||||
DispatchQueue.main.async {
|
||||
self.parent.qrCode = stringValue
|
||||
self.parent.scanCompleted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(self)
|
||||
}
|
||||
|
||||
func makeUIViewController(context: Context) -> some ScannerViewController {
|
||||
let controller = ScannerViewController()
|
||||
controller.delegate = context.coordinator
|
||||
return controller
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ScannerViewController: UIViewController {
|
||||
var captureSession: AVCaptureSession?
|
||||
var previewLayer: AVCaptureVideoPreviewLayer!
|
||||
let metadataOutput = AVCaptureMetadataOutput()
|
||||
weak var delegate: AVCaptureMetadataOutputObjectsDelegate?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
view.backgroundColor = UIColor.black
|
||||
captureSession = AVCaptureSession()
|
||||
|
||||
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video), let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice) else {
|
||||
captureSession = nil
|
||||
return
|
||||
}
|
||||
|
||||
if let captureSession = captureSession {
|
||||
guard captureSession.canAddInput(videoInput) && captureSession.canAddOutput(metadataOutput) else {
|
||||
self.captureSession = nil
|
||||
return
|
||||
}
|
||||
|
||||
captureSession.addInput(videoInput)
|
||||
captureSession.addOutput(metadataOutput)
|
||||
|
||||
metadataOutput.setMetadataObjectsDelegate(delegate, queue: .main)
|
||||
metadataOutput.metadataObjectTypes = [.qr]
|
||||
|
||||
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
|
||||
previewLayer.frame = view.layer.bounds
|
||||
previewLayer.videoGravity = .resizeAspectFill
|
||||
view.layer.addSublayer(previewLayer)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
let dispatchQueue = DispatchQueue(label: "AVCapturesession.startRunning", qos: .background)
|
||||
dispatchQueue.async{
|
||||
if (self.captureSession?.isRunning == false) {
|
||||
self.captureSession?.startRunning()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
let dispatchQueue = DispatchQueue(label: "AVCapturesession.stopRunning", qos: .background)
|
||||
dispatchQueue.async{
|
||||
if (self.captureSession?.isRunning == true) {
|
||||
self.captureSession?.stopRunning()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user