iOS/Sources/App/BarcodeScanner/Camera/BarcodeScannerView.swift

136 lines
4.4 KiB
Swift

import Shared
import SwiftUI
struct BarcodeScannerView: View {
@Environment(\.dismiss) private var dismiss
@StateObject private var viewModel: BarcodeScannerViewModel
// Use single data model so both camera previews use same camera stream
@State private var cameraDataModel = BarcodeScannerDataModel()
private let cameraSquareSize: CGFloat = 320
private let flashlightIcon = MaterialDesignIcons.flashlightIcon.image(
ofSize: .init(width: 24, height: 24),
color: .white
)
private let title: String
private let description: String
private let alternativeOptionLabel: String?
init(
title: String,
description: String,
alternativeOptionLabel: String? = nil,
incomingMessageId: Int
) {
self.title = title
self.description = description
self.alternativeOptionLabel = alternativeOptionLabel
self._viewModel = .init(wrappedValue: .init(incomingMessageId: incomingMessageId))
}
var body: some View {
ZStack(alignment: .top) {
ZStack {
cameraBackground
cameraSquare
}
.ignoresSafeArea()
.frame(maxWidth: .infinity)
.frame(maxHeight: .infinity)
topInformation
}
.onAppear {
cameraDataModel.camera.barcodeFound = { code, format in
viewModel.scannedCode(code, format: format)
}
}
.onDisappear {
cameraDataModel.turnOffFlashlight()
}
}
private var topInformation: some View {
VStack(spacing: 8) {
Button(action: {
viewModel.aborted(.canceled)
dismiss()
}, label: {
Image(systemName: "xmark")
.font(.title2)
.foregroundColor(.white.opacity(0.8))
.frame(maxWidth: .infinity, alignment: .leading)
})
.accessibilityHint(.init(L10n.closeLabel))
Group {
Text(title)
.padding(.top)
.font(.title2)
Text(description)
.font(.subheadline)
}
.foregroundColor(.white)
if let alternativeOptionLabel {
Button {
viewModel.aborted(.alternativeOptions)
dismiss()
} label: {
Text(alternativeOptionLabel)
.font(.subheadline)
.foregroundColor(.accentColor)
}
.padding(.top)
}
}
.padding()
}
private var cameraBackground: some View {
BarcodeScannerCameraView(model: cameraDataModel)
.ignoresSafeArea()
.frame(maxWidth: .infinity)
.frame(maxHeight: .infinity)
.overlay {
Color.black.opacity(0.8)
}
}
private var cameraSquare: some View {
BarcodeScannerCameraView(model: cameraDataModel, shouldStartCamera: false)
.ignoresSafeArea()
.frame(maxWidth: .infinity)
.frame(maxHeight: .infinity)
.mask {
RoundedRectangle(cornerSize: CGSize(width: 20, height: 20))
.frame(width: cameraSquareSize, height: cameraSquareSize)
}
.overlay {
ZStack(alignment: .bottomTrailing) {
RoundedRectangle(cornerSize: CGSize(width: 20, height: 20))
.stroke(Color.blue, lineWidth: 1)
.frame(width: cameraSquareSize, height: cameraSquareSize)
Button(action: {
cameraDataModel.toggleFlashlight()
}, label: {
Image(uiImage: flashlightIcon)
.padding()
.background(Color(uiColor: .init(hex: "#384956")))
.mask(Circle())
.offset(x: -22, y: -22)
})
}
}
}
}
#Preview {
BarcodeScannerView(title: "Scan QR-code", description: "Find the code on your device", incomingMessageId: 1)
}
final class BarcodeScannerHostingController: UIHostingController<BarcodeScannerView> {
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
[.portrait]
}
}