mirror of
https://github.com/bitwarden/ios.git
synced 2025-12-17 13:52:54 -06:00
164 lines
4.9 KiB
Swift
164 lines
4.9 KiB
Swift
import AVFoundation
|
|
import BitwardenKit
|
|
import BitwardenResources
|
|
import SwiftUI
|
|
|
|
// MARK: - ScanCodeView
|
|
|
|
/// A view that shows the camera to scan QR codes.
|
|
struct ScanCodeView: View {
|
|
// MARK: Properties
|
|
|
|
/// The AVCaptureSession used to scan qr codes
|
|
let cameraSession: AVCaptureSession
|
|
|
|
/// The maximum dynamic type size for the view
|
|
/// Default is `.xxLarge`
|
|
var maxDynamicTypeSize: DynamicTypeSize = .xxLarge
|
|
|
|
/// The `Store` for this view.
|
|
@ObservedObject var store: Store<ScanCodeState, ScanCodeAction, ScanCodeEffect>
|
|
|
|
// MARK: Views
|
|
|
|
var body: some View {
|
|
content
|
|
.background(Asset.Colors.backgroundSecondary.swiftUIColor.ignoresSafeArea())
|
|
.navigationTitle(Localizations.scanQrTitle)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
cancelToolbarItem {
|
|
store.send(.dismissPressed)
|
|
}
|
|
}
|
|
.task {
|
|
await store.perform(.appeared)
|
|
}
|
|
.onDisappear {
|
|
Task {
|
|
await store.perform(.disappeared)
|
|
}
|
|
}
|
|
}
|
|
|
|
var content: some View {
|
|
ZStack {
|
|
CameraPreviewView(session: cameraSession)
|
|
overlayContent
|
|
}
|
|
.edgesIgnoringSafeArea(.horizontal)
|
|
}
|
|
|
|
var informationContent: some View {
|
|
VStack(alignment: .center, spacing: 0) {
|
|
Text(Localizations.pointYourCameraAtTheQRCode)
|
|
.styleGuide(.body)
|
|
.multilineTextAlignment(.center)
|
|
.dynamicTypeSize(...maxDynamicTypeSize)
|
|
.foregroundColor(.white)
|
|
if store.state.showManualEntry {
|
|
Spacer()
|
|
Button(
|
|
action: { store.send(.manualEntryPressed) },
|
|
label: {
|
|
Group {
|
|
Text(Localizations.cannotScanQRCode + " ")
|
|
.foregroundColor(.white)
|
|
+ Text(Localizations.enterKeyManually)
|
|
.foregroundColor(Asset.Colors.primaryBitwardenDark.swiftUIColor)
|
|
}
|
|
.styleGuide(.body)
|
|
.multilineTextAlignment(.center)
|
|
.dynamicTypeSize(...maxDynamicTypeSize)
|
|
},
|
|
)
|
|
.buttonStyle(InlineButtonStyle())
|
|
}
|
|
}
|
|
}
|
|
|
|
@ViewBuilder var overlayContent: some View {
|
|
GeometryReader { proxy in
|
|
if proxy.size.width <= proxy.size.height {
|
|
verticalOverlay
|
|
} else {
|
|
horizontalOverlay
|
|
}
|
|
}
|
|
}
|
|
|
|
private var horizontalOverlay: some View {
|
|
GeometryReader { geoProxy in
|
|
let size = geoProxy.size
|
|
let orientation = UIDevice.current.orientation
|
|
let infoBlock = infoBlock(width: size.width / 3, height: size.height)
|
|
HStack(spacing: 0.0) {
|
|
if case .landscapeRight = orientation {
|
|
infoBlock
|
|
}
|
|
Spacer()
|
|
qrCornerGuides(length: size.height)
|
|
Spacer()
|
|
if orientation != .landscapeRight {
|
|
infoBlock
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private var verticalOverlay: some View {
|
|
GeometryReader { geoProxy in
|
|
let size = geoProxy.size
|
|
VStack(spacing: 0.0) {
|
|
Spacer()
|
|
qrCornerGuides(length: size.width)
|
|
Spacer()
|
|
infoBlock(width: size.width, height: size.height / 3)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func infoBlock(width: CGFloat, height: CGFloat) -> some View {
|
|
Rectangle()
|
|
.frame(
|
|
width: width,
|
|
height: height,
|
|
)
|
|
.foregroundColor(.black)
|
|
.opacity(0.5)
|
|
.overlay {
|
|
informationContent
|
|
.padding(36)
|
|
}
|
|
}
|
|
|
|
private func qrCornerGuides(length: CGFloat) -> some View {
|
|
CornerBorderShape(cornerLength: length * 0.1, lineWidth: 3)
|
|
.stroke(lineWidth: 3)
|
|
.foregroundColor(Asset.Colors.primaryBitwardenLight.swiftUIColor)
|
|
.frame(
|
|
width: length * 0.65,
|
|
height: length * 0.65,
|
|
)
|
|
}
|
|
}
|
|
|
|
#if DEBUG
|
|
struct ScanCodeView_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
NavigationView {
|
|
ScanCodeView(
|
|
cameraSession: AVCaptureSession(),
|
|
store: Store(
|
|
processor: StateProcessor(
|
|
state: ScanCodeState(showManualEntry: true),
|
|
),
|
|
),
|
|
)
|
|
}
|
|
.navigationViewStyle(.stack)
|
|
.previewDisplayName("Scan Code View")
|
|
}
|
|
}
|
|
#endif
|