damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

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 }