things kinda work. adding debounce

This commit is contained in:
Tom Caputi 2023-10-28 04:23:23 -04:00
parent 372e946419
commit 740e1a7091
4 changed files with 149 additions and 12 deletions

View file

@ -50,6 +50,7 @@
94CD196A283E666900973B93 /* EmojiManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94CD1969283E666900973B93 /* EmojiManager.swift */; };
94CD196B283E666900973B93 /* EmojiManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94CD1969283E666900973B93 /* EmojiManager.swift */; };
94E9196C28353E0100F30170 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F216283531A200CDE4DD /* Log.swift */; };
E278CB332AECECCA004B9143 /* QRScannerUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E278CB322AECECCA004B9143 /* QRScannerUIView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -111,6 +112,7 @@
94B736D6284AF9BE003D69FB /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
94CD1965283E662900973B93 /* emojis.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = emojis.json; sourceTree = "<group>"; };
94CD1969283E666900973B93 /* EmojiManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiManager.swift; sourceTree = "<group>"; };
E278CB322AECECCA004B9143 /* QRScannerUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRScannerUIView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -237,6 +239,7 @@
94867142283EC9950093C7A4 /* Actions.swift */,
948671462841B0B20093C7A4 /* NotificationContent.swift */,
948671492841D0CE0093C7A4 /* ActionExecutor.swift */,
E278CB322AECECCA004B9143 /* QRScannerUIView.swift */,
);
path = Utils;
sourceTree = "<group>";
@ -375,6 +378,7 @@
9474F1F22830825600CDE4DD /* SubscriptionListView.swift in Sources */,
9486714A2841D0CE0093C7A4 /* ActionExecutor.swift in Sources */,
9474F1FD2831311A00CDE4DD /* SubscriptionAddView.swift in Sources */,
E278CB332AECECCA004B9143 /* QRScannerUIView.swift in Sources */,
9474F1FF28316ACE00CDE4DD /* Subscription.swift in Sources */,
94CD196A283E666900973B93 /* EmojiManager.swift in Sources */,
9474F1C1282F2AA700CDE4DD /* AppMain.swift in Sources */,

View file

@ -18,7 +18,9 @@
<array>
<string>remote-notification</string>
</array>
<key>FirebaseAppDelegateProxyEnabled</key>
<false/>
<key>FirebaseAppDelegateProxyEnabled</key>
<false/>
<key>NSCameraUsageDescription</key>
<string>We need access to the camera for QR code scanning.</string>
</dict>
</plist>

View file

@ -0,0 +1,70 @@
import AVFoundation
import SwiftUI
struct QRScannerUIView: UIViewRepresentable {
var onCodeDetected: (String) -> Void
func makeUIView(context: Context) -> some UIView {
let view = QRScannerUIViewContainer()
let captureSession = AVCaptureSession()
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video),
let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice),
captureSession.canAddInput(videoInput) else {
return view
}
captureSession.addInput(videoInput)
let metadataOutput = AVCaptureMetadataOutput()
captureSession.addOutput(metadataOutput)
metadataOutput.setMetadataObjectsDelegate(context.coordinator, queue: DispatchQueue.main)
metadataOutput.metadataObjectTypes = [.qr]
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.layer.bounds
previewLayer.videoGravity = .resizeAspectFill
view.layer.insertSublayer(previewLayer, at: 0)
// Move the startRunning call to a background thread
DispatchQueue.global(qos: .userInitiated).async {
captureSession.startRunning()
}
view.previewLayer = previewLayer
view.captureSession = captureSession
return view
}
func updateUIView(_ uiView: UIViewType, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(onCodeDetected: onCodeDetected)
}
class Coordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate {
var onCodeDetected: (String) -> Void
init(onCodeDetected: @escaping (String) -> Void) {
self.onCodeDetected = onCodeDetected
}
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
if let metadataObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject, let stringValue = metadataObject.stringValue {
onCodeDetected(stringValue)
}
}
}
}
class QRScannerUIViewContainer: UIView {
var previewLayer: AVCaptureVideoPreviewLayer?
var captureSession: AVCaptureSession?
override func layoutSubviews() {
super.layoutSubviews()
previewLayer?.frame = self.bounds
}
}

View file

@ -1,4 +1,5 @@
import SwiftUI
import AVFoundation
struct SubscriptionAddView: View {
private let tag = "SubscriptionAddView"
@ -17,6 +18,7 @@ struct SubscriptionAddView: View {
@State private var loading = false
@State private var addError: String?
@State private var loginError: String?
@State private var hasCameraPermission: Bool = false
private var subscriptionManager: SubscriptionManager {
return SubscriptionManager(store: store)
@ -24,19 +26,29 @@ struct SubscriptionAddView: View {
var body: some View {
NavigationView {
VStack {
addView
// TODO: hide this if permission not granted
QRScannerUIView { code in
onQRCodeScanned(text: code)
}
.frame(height: 250) // You can adjust the height as needed.
.padding()
.onAppear(perform: checkCameraPermission)
}
// This is a little weird, but it works. The nagivation link for the login view
// is rendered in the backgroun (it's hidden), abd we toggle it manually.
// is rendered in the background (it's hidden), abd we toggle it manually.
// If anyone has a better way to do a two-page layout let me know.
addView
.background(Group {
NavigationLink(
destination: loginView,
isActive: $showLogin
) {
EmptyView()
}
})
.background(Group {
NavigationLink(
destination: loginView,
isActive: $showLogin
) {
EmptyView()
}
})
}
}
@ -129,6 +141,55 @@ struct SubscriptionAddView: View {
return topic.trimmingCharacters(in: .whitespaces)
}
private func checkCameraPermission() {
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
self.hasCameraPermission = true
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { granted in
DispatchQueue.main.async {
self.hasCameraPermission = granted
if !granted {
self.hasCameraPermission = false
}
}
}
case .denied, .restricted:
self.hasCameraPermission = false
@unknown default:
break
}
}
func onQRCodeScanned(text: String){
// Check if the text is a valid URL with HTTP or HTTPS scheme
guard let url = URL(string: text), let scheme = url.scheme, ["http", "https"].contains(scheme) else {
return
}
// Extract the base URL without the path
var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
components?.path = ""
guard let foundBaseUrl = components?.url else {
return
}
// Extract the route from the original URL
baseUrl = foundBaseUrl.absoluteString
useAnother = baseUrl != store.getDefaultBaseUrl()
topic = url.path
if (topic.hasPrefix("/")) {
topic.removeFirst()
}
print("------> \(baseUrl) : \(topic) : \(useAnother)")
subscribeOrShowLoginAction()
}
private func isAddViewValid() -> Bool {
if sanitizedTopic.isEmpty {
return false