damus

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

EditPictureControl.swift (9156B)


      1 //
      2 //  EditPictureControl.swift
      3 //  damus
      4 //
      5 //  Created by Joel Klabo on 3/30/23.
      6 //
      7 
      8 import SwiftUI
      9 import Kingfisher
     10 
     11 class ImageUploadingObserver: ObservableObject {
     12     @Published var isLoading: Bool = false
     13 }
     14 
     15 struct EditPictureControl: View {
     16     let uploader: MediaUploader
     17     let pubkey: Pubkey
     18     var size: CGFloat? = 25
     19     var setup: Bool? = false
     20     @Binding var image_url: URL?
     21     @State var image_url_temp: URL?
     22     @ObservedObject var uploadObserver: ImageUploadingObserver
     23     let callback: (URL?) -> Void
     24     
     25     @StateObject var image_upload: ImageUploadModel = ImageUploadModel()
     26     
     27     @State private var show_camera = false
     28     @State private var show_library = false
     29     @State private var show_url_sheet = false
     30     @State var image_upload_confirm: Bool = false
     31 
     32     @State var preUploadedMedia: PreUploadedMedia? = nil
     33     
     34     @Environment(\.dismiss) var dismiss
     35 
     36     var body: some View {
     37         Menu {
     38             Button(action: {
     39                 self.show_url_sheet = true
     40             }) {
     41                 Text("Image URL", comment: "Option to enter a url")
     42             }
     43             
     44             Button(action: {
     45                 self.show_library = true
     46             }) {
     47                 Text("Choose from Library", comment: "Option to select photo from library")
     48             }
     49             
     50             Button(action: {
     51                 self.show_camera = true
     52             }) {
     53                 Text("Take Photo", comment: "Option to take a photo with the camera")
     54             }
     55         } label: {
     56             if uploadObserver.isLoading {
     57                 ProgressView()
     58                     .progressViewStyle(CircularProgressViewStyle(tint: DamusColors.purple))
     59                     .frame(width: size, height: size)
     60                     .padding(10)
     61                     .background(DamusColors.white.opacity(0.7))
     62                     .clipShape(Circle())
     63                     .shadow(color: DamusColors.purple, radius: 15, x: 0, y: 0)
     64             } else if let url = image_url, setup ?? false {
     65                 KFAnimatedImage(url)
     66                     .imageContext(.pfp, disable_animation: false)
     67                     .onFailure(fallbackUrl: URL(string: robohash(pubkey)), cacheKey: url.absoluteString)
     68                     .cancelOnDisappear(true)
     69                     .configure { view in
     70                         view.framePreloadCount = 3
     71                     }
     72                     .scaledToFill()
     73                     .frame(width: (size ?? 25) + 10, height: (size ?? 25) + 10)
     74                     .kfClickable()
     75                     .foregroundColor(DamusColors.white)
     76                     .clipShape(Circle())
     77                     .overlay(Circle().stroke(.white, lineWidth: 4))
     78             } else {
     79                 if setup ?? false {
     80                     Image(systemName: "person")
     81                         .resizable()
     82                         .scaledToFit()
     83                         .frame(width: size, height: size)
     84                         .foregroundColor(DamusColors.white)
     85                         .padding(20)
     86                         .clipShape(Circle())
     87                         .background {
     88                             Circle()
     89                                 .fill(PinkGradient, strokeBorder: .white, lineWidth: 4)
     90                         }
     91                         
     92                 } else {
     93                     Image("camera")
     94                         .resizable()
     95                         .scaledToFit()
     96                         .frame(width: size, height: size)
     97                         .foregroundColor(DamusColors.purple)
     98                         .padding(10)
     99                         .background(DamusColors.white.opacity(0.7))
    100                         .clipShape(Circle())
    101                         .background {
    102                             Circle()
    103                                 .fill(DamusColors.purple, strokeBorder: .white, lineWidth: 2)
    104                         }
    105                 }
    106                     
    107             }
    108         }
    109         .sheet(isPresented: $show_camera) {
    110             CameraController(uploader: uploader) {
    111                 self.show_camera = false
    112                 self.show_library = true
    113             }
    114         }
    115         .sheet(isPresented: $show_library) {
    116             MediaPicker(image_upload_confirm: $image_upload_confirm, imagesOnly: true) { media in
    117                 self.preUploadedMedia = media
    118             }
    119             .alert(NSLocalizedString("Are you sure you want to upload this image?", comment: "Alert message asking if the user wants to upload an image."), isPresented: $image_upload_confirm) {
    120                 Button(NSLocalizedString("Upload", comment: "Button to proceed with uploading."), role: .none) {
    121                     if let mediaToUpload = generateMediaUpload(preUploadedMedia) {
    122                         self.handle_upload(media: mediaToUpload)
    123                         self.show_library = false
    124                     }
    125                 }
    126                 Button(NSLocalizedString("Cancel", comment: "Button to cancel the upload."), role: .cancel) {}
    127             }
    128         }
    129         .sheet(isPresented: $show_url_sheet) {
    130             ZStack {
    131                 DamusColors.adaptableWhite.edgesIgnoringSafeArea(.all)
    132                 VStack {
    133                     Text("Image URL")
    134                         .bold()
    135                     
    136                     Divider()
    137                         .padding(.horizontal)
    138                     
    139                     HStack {
    140                         Image(systemName: "doc.on.clipboard")
    141                             .foregroundColor(.gray)
    142                             .onTapGesture {
    143                                 if let pastedURL = UIPasteboard.general.string {
    144                                     image_url_temp = URL(string: pastedURL)
    145                                 }
    146                             }
    147                         TextField(image_url_temp?.absoluteString ?? "", text: Binding(
    148                             get: { image_url_temp?.absoluteString ?? "" },
    149                             set: { image_url_temp = URL(string: $0) }
    150                         ))
    151                     }
    152                     .padding(12)
    153                     .background {
    154                         RoundedRectangle(cornerRadius: 12)
    155                             .stroke(.gray.opacity(0.5), lineWidth: 1)
    156                             .background {
    157                                 RoundedRectangle(cornerRadius: 12)
    158                                     .foregroundColor(.damusAdaptableWhite)
    159                             }
    160                     }
    161                     .padding(10)
    162                     
    163                     Button(action: {
    164                         show_url_sheet.toggle()
    165                     }, label: {
    166                         Text("Cancel", comment: "Cancel button text for dismissing updating image url.")
    167                             .frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
    168                             .padding(10)
    169                     })
    170                     .buttonStyle(NeutralButtonStyle())
    171                     .padding(10)
    172                     
    173                     Button(action: {
    174                         image_url = image_url_temp
    175                         callback(image_url)
    176                         show_url_sheet.toggle()
    177                     }, label: {
    178                         Text("Update", comment: "Update button text for updating image url.")
    179                             .frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
    180                     })
    181                     .buttonStyle(GradientButtonStyle(padding: 10))
    182                     .padding(.horizontal, 10)
    183                     .disabled(image_url_temp == image_url)
    184                     .opacity(image_url_temp == image_url ? 0.5 : 1)
    185                 }
    186             }
    187             .onAppear {
    188                 image_url_temp = image_url
    189             }
    190             .presentationDetents([.height(300)])
    191             .presentationDragIndicator(.visible)
    192         }
    193     }
    194     
    195     private func handle_upload(media: MediaUpload) {
    196         uploadObserver.isLoading = true
    197         Task {
    198             let res = await image_upload.start(media: media, uploader: uploader)
    199             
    200             switch res {
    201             case .success(let urlString):
    202                 let url = URL(string: urlString)
    203                 image_url = url
    204                 callback(url)
    205             case .failed(let error):
    206                 if let error {
    207                     print("Error uploading profile image \(error.localizedDescription)")
    208                 } else {
    209                     print("Error uploading image :(")
    210                 }
    211                 callback(nil)
    212             }
    213             uploadObserver.isLoading = false
    214         }
    215     }
    216 }
    217 
    218 struct EditPictureControl_Previews: PreviewProvider {
    219     static var previews: some View {
    220         let url = Binding<URL?>.constant(URL(string: "https://damus.io")!)
    221         let observer = ImageUploadingObserver()
    222         ZStack {
    223             Color.gray
    224             EditPictureControl(uploader: .nostrBuild, pubkey: test_pubkey, size: 100, setup: false, image_url: url, uploadObserver: observer) { _ in
    225                 //
    226             }
    227         }
    228     }
    229 }