CameraView.swift (8504B)
1 // 2 // CameraView.swift 3 // damus 4 // 5 // Created by Suhail Saqan on 8/5/23. 6 // 7 8 import SwiftUI 9 import Combine 10 import AVFoundation 11 12 struct CameraView: View { 13 let damus_state: DamusState 14 let action: (([MediaItem]) -> Void) 15 16 @Environment(\.presentationMode) var presentationMode 17 18 @StateObject var model: CameraModel 19 20 @State var currentZoomFactor: CGFloat = 1.0 21 22 public init(damus_state: DamusState, action: @escaping (([MediaItem]) -> Void)) { 23 self.damus_state = damus_state 24 self.action = action 25 _model = StateObject(wrappedValue: CameraModel()) 26 } 27 28 var captureButton: some View { 29 Button { 30 if model.isRecording { 31 withAnimation { 32 model.stopRecording() 33 } 34 } else { 35 withAnimation { 36 model.capturePhoto() 37 } 38 } 39 UIImpactFeedbackGenerator(style: .medium).impactOccurred() 40 } label: { 41 ZStack { 42 Circle() 43 .fill( model.isRecording ? .red : DamusColors.black) 44 .frame(width: model.isRecording ? 85 : 65, height: model.isRecording ? 85 : 65, alignment: .center) 45 46 Circle() 47 .stroke( model.isRecording ? .red : DamusColors.white, lineWidth: 4) 48 .frame(width: model.isRecording ? 95 : 75, height: model.isRecording ? 95 : 75, alignment: .center) 49 } 50 .frame(alignment: .center) 51 } 52 .simultaneousGesture( 53 LongPressGesture(minimumDuration: 0.5).onEnded({ value in 54 if (!model.isCameraButtonDisabled) { 55 withAnimation { 56 model.startRecording() 57 model.captureMode = .video 58 } 59 } 60 }) 61 ) 62 .buttonStyle(.plain) 63 } 64 65 var capturedPhotoThumbnail: some View { 66 ZStack { 67 if model.thumbnail != nil { 68 Image(uiImage: model.thumbnail.thumbnailImage!) 69 .resizable() 70 .aspectRatio(contentMode: .fill) 71 .frame(width: 60, height: 60) 72 .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) 73 } 74 if model.isPhotoProcessing { 75 ProgressView() 76 .progressViewStyle(CircularProgressViewStyle(tint: DamusColors.white)) 77 } 78 } 79 } 80 81 var closeButton: some View { 82 Button { 83 presentationMode.wrappedValue.dismiss() 84 model.stop() 85 } label: { 86 HStack { 87 Image(systemName: "xmark") 88 .font(.system(size: 24)) 89 } 90 .frame(minWidth: 40, minHeight: 40) 91 } 92 .accentColor(DamusColors.white) 93 } 94 95 var flipCameraButton: some View { 96 Button(action: { 97 model.flipCamera() 98 }, label: { 99 HStack { 100 Image(systemName: "camera.rotate.fill") 101 .font(.system(size: 20)) 102 } 103 .frame(minWidth: 40, minHeight: 40) 104 }) 105 .accentColor(DamusColors.white) 106 } 107 108 var toggleFlashButton: some View { 109 Button(action: { 110 model.switchFlash() 111 }, label: { 112 HStack { 113 Image(systemName: model.isFlashOn ? "bolt.fill" : "bolt.slash.fill") 114 .font(.system(size: 20)) 115 } 116 .frame(minWidth: 40, minHeight: 40) 117 }) 118 .accentColor(model.isFlashOn ? .yellow : DamusColors.white) 119 } 120 121 var body: some View { 122 NavigationView { 123 GeometryReader { reader in 124 ZStack { 125 DamusColors.black.edgesIgnoringSafeArea(.all) 126 127 CameraPreview(session: model.session) 128 .padding(.bottom, 175) 129 .edgesIgnoringSafeArea(.all) 130 .gesture( 131 DragGesture().onChanged({ (val) in 132 if abs(val.translation.height) > abs(val.translation.width) { 133 let percentage: CGFloat = -(val.translation.height / reader.size.height) 134 let calc = currentZoomFactor + percentage 135 let zoomFactor: CGFloat = min(max(calc, 1), 5) 136 137 currentZoomFactor = zoomFactor 138 model.zoom(with: zoomFactor) 139 } 140 }) 141 ) 142 .onAppear { 143 model.configure() 144 } 145 .alert(isPresented: $model.showAlertError, content: { 146 Alert(title: Text(model.alertError.title), message: Text(model.alertError.message), dismissButton: .default(Text(model.alertError.primaryButtonTitle), action: { 147 model.alertError.primaryAction?() 148 })) 149 }) 150 .overlay( 151 Group { 152 if model.willCapturePhoto { 153 Color.black 154 } 155 } 156 ) 157 158 VStack { 159 if !model.isRecording { 160 HStack { 161 closeButton 162 163 Spacer() 164 165 HStack { 166 flipCameraButton 167 toggleFlashButton 168 } 169 } 170 .padding(.horizontal, 20) 171 } 172 173 Spacer() 174 175 HStack(alignment: .center) { 176 if !model.mediaItems.isEmpty { 177 NavigationLink(destination: CameraMediaView(video_controller: damus_state.video, urls: model.mediaItems.map { mediaItem in 178 switch mediaItem.type { 179 case .image: 180 return .image(mediaItem.url) 181 case .video: 182 return .video(mediaItem.url) 183 } 184 }, settings: damus_state.settings) 185 .navigationBarBackButtonHidden(true) 186 ) { 187 capturedPhotoThumbnail 188 } 189 .frame(width: 100, alignment: .leading) 190 } 191 192 Spacer() 193 194 captureButton 195 196 Spacer() 197 198 if !model.mediaItems.isEmpty { 199 Button(action: { 200 action(model.mediaItems) 201 presentationMode.wrappedValue.dismiss() 202 model.stop() 203 }) { 204 Text("Upload") 205 .frame(width: 100, height: 40, alignment: .center) 206 .foregroundColor(DamusColors.white) 207 .overlay { 208 RoundedRectangle(cornerRadius: 24) 209 .stroke(DamusColors.white, lineWidth: 2) 210 } 211 } 212 } 213 } 214 .frame(height: 100) 215 .padding([.horizontal, .vertical], 20) 216 } 217 } 218 } 219 } 220 } 221 }