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 }