diff --git a/ntfy.xcodeproj/project.pbxproj b/ntfy.xcodeproj/project.pbxproj index dd2dc24..6737885 100644 --- a/ntfy.xcodeproj/project.pbxproj +++ b/ntfy.xcodeproj/project.pbxproj @@ -52,7 +52,6 @@ 94E9196C28353E0100F30170 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F216283531A200CDE4DD /* Log.swift */; }; E27008102AF0F64B006E33BA /* SubscriptionsObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E270080F2AF0F64B006E33BA /* SubscriptionsObservable.swift */; }; E27008122AF1030A006E33BA /* NotificationsObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27008112AF1030A006E33BA /* NotificationsObservable.swift */; }; - E278CB332AECECCA004B9143 /* QRScannerUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E278CB322AECECCA004B9143 /* QRScannerUIView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -116,7 +115,6 @@ 94CD1969283E666900973B93 /* EmojiManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiManager.swift; sourceTree = ""; }; E270080F2AF0F64B006E33BA /* SubscriptionsObservable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionsObservable.swift; sourceTree = ""; }; E27008112AF1030A006E33BA /* NotificationsObservable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsObservable.swift; sourceTree = ""; }; - E278CB322AECECCA004B9143 /* QRScannerUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRScannerUIView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -206,7 +204,6 @@ 94B736D4284AF9B2003D69FB /* SettingsView.swift */, 94B736D6284AF9BE003D69FB /* MainView.swift */, 9474F20728331F3900CDE4DD /* NotificationListView.swift */, - E278CB322AECECCA004B9143 /* QRScannerUIView.swift */, ); path = Views; sourceTree = ""; @@ -218,10 +215,10 @@ 9474F1FE28316ACE00CDE4DD /* Subscription.swift */, 9474F1F82830835400CDE4DD /* Store.swift */, 9474F20B283321C300CDE4DD /* Notification.swift */, - 94A3F7C7283734D900C48E79 /* SubscriptionManager.swift */, - 9407EDD9284ADE1F00C1C334 /* User.swift */, - E270080F2AF0F64B006E33BA /* SubscriptionsObservable.swift */, E27008112AF1030A006E33BA /* NotificationsObservable.swift */, + 94A3F7C7283734D900C48E79 /* SubscriptionManager.swift */, + E270080F2AF0F64B006E33BA /* SubscriptionsObservable.swift */, + 9407EDD9284ADE1F00C1C334 /* User.swift */, ); path = Persistence; sourceTree = ""; @@ -386,7 +383,6 @@ E27008122AF1030A006E33BA /* NotificationsObservable.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 */, @@ -561,7 +557,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ntfy/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ntfy; - INFOPLIST_KEY_NSCameraUsageDescription = "We need access to the camera to scan QR codes."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; @@ -596,7 +591,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ntfy/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ntfy; - INFOPLIST_KEY_NSCameraUsageDescription = "We need access to the camera to scan QR codes."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; diff --git a/ntfy/Info.plist b/ntfy/Info.plist index 0ad242b..428685b 100644 --- a/ntfy/Info.plist +++ b/ntfy/Info.plist @@ -4,6 +4,8 @@ AppBaseURL $(APP_BASE_URL) + FirebaseAppDelegateProxyEnabled + NSAppTransportSecurity NSAllowsArbitraryLoads @@ -18,9 +20,5 @@ remote-notification - FirebaseAppDelegateProxyEnabled - - NSCameraUsageDescription - We need access to the camera for QR code scanning. diff --git a/ntfy/Views/QRScannerUIView.swift b/ntfy/Views/QRScannerUIView.swift deleted file mode 100644 index 9ecf8d2..0000000 --- a/ntfy/Views/QRScannerUIView.swift +++ /dev/null @@ -1,84 +0,0 @@ -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 - private var lastScanDate: Date? - private let debounceInterval: TimeInterval = 3.0 - - init(onCodeDetected: @escaping (String) -> Void) { - self.onCodeDetected = onCodeDetected - } - - func qrCodeScanned(_ code: String) { - let now = Date() - - // If it's the first scan or the interval since the last scan is more than the debounce interval - if let lastScan = lastScanDate, now.timeIntervalSince(lastScan) < debounceInterval { - return - } - - onCodeDetected(code) - lastScanDate = now - } - - func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { - if let metadataObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject, let stringValue = metadataObject.stringValue { - qrCodeScanned(stringValue) - } - } - } -} - - -class QRScannerUIViewContainer: UIView { - var previewLayer: AVCaptureVideoPreviewLayer? - var captureSession: AVCaptureSession? - - override func layoutSubviews() { - super.layoutSubviews() - previewLayer?.frame = self.bounds - } -} diff --git a/ntfy/Views/SubscriptionAddView.swift b/ntfy/Views/SubscriptionAddView.swift index 8b6b4cb..2fc8645 100644 --- a/ntfy/Views/SubscriptionAddView.swift +++ b/ntfy/Views/SubscriptionAddView.swift @@ -1,5 +1,4 @@ import SwiftUI -import AVFoundation struct SubscriptionAddView: View { private let tag = "SubscriptionAddView" @@ -18,7 +17,6 @@ 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) @@ -26,29 +24,19 @@ 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 background (it's hidden), abd we toggle it manually. + // is rendered in the backgroun (it's hidden), abd we toggle it manually. // If anyone has a better way to do a two-page layout let me know. - .background(Group { - NavigationLink( - destination: loginView, - isActive: $showLogin - ) { - EmptyView() - } - }) + addView + .background(Group { + NavigationLink( + destination: loginView, + isActive: $showLogin + ) { + EmptyView() + } + }) } } @@ -141,55 +129,6 @@ 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