From 9ade0f20562e61aa1b8893ddcee06f2376444156 Mon Sep 17 00:00:00 2001 From: Suhail Saqan Date: Tue, 19 Sep 2023 13:35:52 -0700 Subject: [PATCH] camera: add CameraPreview for displaying the view the camera is reading --- damus.xcodeproj/project.pbxproj | 12 ++++ damus/Views/Camera/CameraPreview.swift | 95 ++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 damus/Views/Camera/CameraPreview.swift diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index b0c3c57ac8..fca48c16f1 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -383,6 +383,7 @@ 501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */; }; 504323A72A34915F006AE6DC /* RelayModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504323A62A34915F006AE6DC /* RelayModel.swift */; }; 504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504323A82A3495B6006AE6DC /* RelayModelCache.swift */; }; + BA3759972ABCCF360018D73B /* CameraPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA3759962ABCCF360018D73B /* CameraPreview.swift */; }; 5053ACA72A56DF3B00851AE3 /* DeveloperSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5053ACA62A56DF3B00851AE3 /* DeveloperSettingsView.swift */; }; 50A16FFB2AA6C06600DFEC1F /* AVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A16FFA2AA6C06600DFEC1F /* AVPlayerView.swift */; }; 50A16FFD2AA7525700DFEC1F /* DamusVideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A16FFC2AA7525700DFEC1F /* DamusVideoPlayerViewModel.swift */; }; @@ -943,6 +944,7 @@ 4CA352A92A76BF3A003BB08B /* LocalNotificationNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationNotify.swift; sourceTree = ""; }; 4CA352AB2A76C07F003BB08B /* NewUnmutesNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewUnmutesNotify.swift; sourceTree = ""; }; 4CA352AD2A76C1AC003BB08B /* FollowedNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedNotify.swift; sourceTree = ""; }; + BA3759962ABCCF360018D73B /* CameraPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraPreview.swift; sourceTree = ""; }; 4CA3FA0F29F593D000FDB3C3 /* ZapTypePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapTypePicker.swift; sourceTree = ""; }; 4CA5588229F33F5B00DC6A45 /* StringCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringCodable.swift; sourceTree = ""; }; 4CA9275C2A28FF630098A105 /* LongformView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongformView.swift; sourceTree = ""; }; @@ -1629,6 +1631,7 @@ 4C75EFA227FA576C0006080F /* Views */ = { isa = PBXGroup; children = ( + BA3759952ABCCF360018D73B /* Camera */, F71694E82A66221E001F4053 /* Onboarding */, 4C190F232A547D1700027FD5 /* NostrScript */, 4C7D09692A0AEA0400943473 /* CodeScanner */, @@ -2277,6 +2280,14 @@ path = Camera; sourceTree = ""; }; + BA3759952ABCCF360018D73B /* Camera */ = { + isa = PBXGroup; + children = ( + BA3759962ABCCF360018D73B /* CameraPreview.swift */, + ); + path = Camera; + sourceTree = ""; + }; F71694E82A66221E001F4053 /* Onboarding */ = { isa = PBXGroup; children = ( @@ -2620,6 +2631,7 @@ 4C32B9582A9AD44700DC3548 /* VeriferOptions.swift in Sources */, 4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */, 4C30AC7629A5770900E2BD5A /* NotificationItemView.swift in Sources */, + BA3759972ABCCF360018D73B /* CameraPreview.swift in Sources */, 4C86F7C42A76C44C00EC0817 /* ZappingNotify.swift in Sources */, 4C363A8428233689006E126D /* Parser.swift in Sources */, 3AAA95CA298DF87B00F3D526 /* TranslationService.swift in Sources */, diff --git a/damus/Views/Camera/CameraPreview.swift b/damus/Views/Camera/CameraPreview.swift new file mode 100644 index 0000000000..ab1ff63ec3 --- /dev/null +++ b/damus/Views/Camera/CameraPreview.swift @@ -0,0 +1,95 @@ +// +// CameraPreview.swift +// damus +// +// Created by Suhail Saqan on 8/5/23. +// + +import UIKit +import AVFoundation +import SwiftUI + +public struct CameraPreview: UIViewRepresentable { + public class VideoPreviewView: UIView { + public override class var layerClass: AnyClass { + AVCaptureVideoPreviewLayer.self + } + + var videoPreviewLayer: AVCaptureVideoPreviewLayer { + return layer as! AVCaptureVideoPreviewLayer + } + + let focusView: UIView = { + let focusView = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30)) + focusView.layer.borderColor = UIColor.white.cgColor + focusView.layer.borderWidth = 1.5 + focusView.layer.cornerRadius = 15 + focusView.layer.opacity = 0 + focusView.backgroundColor = .clear + return focusView + }() + + @objc func focusAndExposeTap(gestureRecognizer: UITapGestureRecognizer) { + let layerPoint = gestureRecognizer.location(in: gestureRecognizer.view) + + guard layerPoint.x >= 0 && layerPoint.x <= bounds.width && + layerPoint.y >= 0 && layerPoint.y <= bounds.height else { + return + } + + let devicePoint = videoPreviewLayer.captureDevicePointConverted(fromLayerPoint: layerPoint) + + self.focusView.layer.frame = CGRect(origin: layerPoint, size: CGSize(width: 30, height: 30)) + + NotificationCenter.default.post(.init(name: .init("UserDidRequestNewFocusPoint"), object: nil, userInfo: ["devicePoint": devicePoint] as [AnyHashable: Any])) + + UIView.animate(withDuration: 0.3, animations: { + self.focusView.layer.opacity = 1 + }) { (completed) in + if completed { + UIView.animate(withDuration: 0.3) { + self.focusView.layer.opacity = 0 + } + } + } + } + + public override func layoutSubviews() { + super.layoutSubviews() + + videoPreviewLayer.videoGravity = .resizeAspectFill + + self.layer.addSublayer(focusView.layer) + + let gRecognizer = UITapGestureRecognizer(target: self, action: #selector(VideoPreviewView.focusAndExposeTap(gestureRecognizer:))) + self.addGestureRecognizer(gRecognizer) + } + } + + public let session: AVCaptureSession + + public init(session: AVCaptureSession) { + self.session = session + } + + public func makeUIView(context: Context) -> VideoPreviewView { + let viewFinder = VideoPreviewView() + viewFinder.backgroundColor = .black + viewFinder.videoPreviewLayer.cornerRadius = 20 + viewFinder.videoPreviewLayer.session = session + viewFinder.videoPreviewLayer.connection?.videoOrientation = .portrait + + return viewFinder + } + + public func updateUIView(_ uiView: VideoPreviewView, context: Context) { + + } +} + +struct CameraPreview_Previews: PreviewProvider { + static var previews: some View { + CameraPreview(session: AVCaptureSession()) + .frame(height: 300) + } +}