Show image upload progress
This commit is contained in:
28
damus/Models/ImageUploadModel.swift
Normal file
28
damus/Models/ImageUploadModel.swift
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// ImageUploadModel.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-03-16.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
|
||||
class ImageUploadModel: NSObject, URLSessionTaskDelegate, ObservableObject {
|
||||
@Published var progress: Double? = nil
|
||||
|
||||
func start(img: UIImage, uploader: ImageUploader) async -> ImageUploadResult {
|
||||
return await create_image_upload_request(imageToUpload: img, imageUploader: uploader, progress: self)
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
|
||||
DispatchQueue.main.async {
|
||||
self.progress = Double(totalBytesSent) / Double(totalBytesExpectedToSend)
|
||||
|
||||
if self.progress! >= 1.0 {
|
||||
self.progress = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,50 +6,16 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
import CoreGraphics
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
extension PostView {
|
||||
func myImageUploadRequest(imageToUpload: UIImage, imageUploader: ImageUploader) {
|
||||
let myUrl = NSURL(string: imageUploader.postAPI);
|
||||
let request = NSMutableURLRequest(url:myUrl! as URL);
|
||||
request.httpMethod = "POST";
|
||||
let boundary = generateBoundaryString()
|
||||
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
|
||||
let imageData = imageToUpload.jpegData(compressionQuality: 1)
|
||||
if imageData == nil {
|
||||
return
|
||||
}
|
||||
request.httpBody = createBodyWithParameters(imageDataKey: imageData! as NSData, boundary: boundary, imageUploader: imageUploader) as Data
|
||||
enum ImageUploadResult {
|
||||
case success(String)
|
||||
case failed(Error?)
|
||||
}
|
||||
|
||||
let task = URLSession.shared.dataTask(with: request as URLRequest) {
|
||||
data, response, error in
|
||||
if let error {
|
||||
print("error=\(error)")
|
||||
return
|
||||
}
|
||||
|
||||
guard let data else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let responseString = String(data: data, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue)) else {
|
||||
return
|
||||
}
|
||||
print("response data = \(responseString)")
|
||||
|
||||
guard let url = imageUploader.getImageURL(from: responseString) else {
|
||||
return
|
||||
}
|
||||
|
||||
let uploadedImageURL = NSMutableAttributedString(string: url, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0), NSAttributedString.Key.foregroundColor: UIColor.label])
|
||||
let combinedAttributedString = NSMutableAttributedString()
|
||||
combinedAttributedString.append(post)
|
||||
combinedAttributedString.append(uploadedImageURL)
|
||||
post = combinedAttributedString
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
|
||||
func createBodyWithParameters(imageDataKey: NSData, boundary: String, imageUploader: ImageUploader) -> NSData {
|
||||
fileprivate func create_upload_body(imageDataKey: Data, boundary: String, imageUploader: ImageUploader) -> Data {
|
||||
let body = NSMutableData();
|
||||
let contentType = "image/jpg"
|
||||
body.appendString(string: "Content-Type: multipart/form-data; boundary=\(boundary)\r\n\r\n")
|
||||
@@ -59,14 +25,51 @@ extension PostView {
|
||||
body.append(imageDataKey as Data)
|
||||
body.appendString(string: "\r\n")
|
||||
body.appendString(string: "--\(boundary)--\r\n")
|
||||
return body
|
||||
return body as Data
|
||||
}
|
||||
|
||||
func generateBoundaryString() -> String {
|
||||
return "Boundary-\(NSUUID().uuidString)"
|
||||
|
||||
func create_image_upload_request(imageToUpload: UIImage, imageUploader: ImageUploader, progress: URLSessionTaskDelegate) async -> ImageUploadResult {
|
||||
|
||||
guard let url = URL(string: imageUploader.postAPI) else {
|
||||
return .failed(nil)
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST";
|
||||
let boundary = "Boundary-\(UUID().description)"
|
||||
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
// otherwise convert to jpg
|
||||
guard let jpegData = imageToUpload.jpegData(compressionQuality: 0.8) else {
|
||||
// somehow failed, just return original
|
||||
return .failed(nil)
|
||||
}
|
||||
|
||||
request.httpBody = create_upload_body(imageDataKey: jpegData, boundary: boundary, imageUploader: imageUploader)
|
||||
|
||||
do {
|
||||
let (data, _) = try await URLSession.shared.data(for: request, delegate: progress)
|
||||
|
||||
guard let responseString = String(data: data, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue)) else {
|
||||
print("Upload failed getting response string")
|
||||
return .failed(nil)
|
||||
}
|
||||
|
||||
guard let url = imageUploader.getImageURL(from: responseString) else {
|
||||
print("Upload failed getting image url")
|
||||
return .failed(nil)
|
||||
}
|
||||
|
||||
return .success(url)
|
||||
|
||||
} catch {
|
||||
return .failed(error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension PostView {
|
||||
struct ImagePicker: UIViewControllerRepresentable {
|
||||
|
||||
@Environment(\.presentationMode)
|
||||
|
||||
@@ -19,6 +19,9 @@ struct PostView: View {
|
||||
@FocusState var focus: Bool
|
||||
@State var showPrivateKeyWarning: Bool = false
|
||||
@State var attach_media: Bool = false
|
||||
@State var error: String? = nil
|
||||
|
||||
@StateObject var image_upload: ImageUploadModel = ImageUploadModel()
|
||||
|
||||
let replying_to: NostrEvent?
|
||||
let references: [ReferencedId]
|
||||
@@ -80,6 +83,7 @@ struct PostView: View {
|
||||
var AttachmentBar: some View {
|
||||
HStack(alignment: .center) {
|
||||
ImageButton
|
||||
.disabled(image_upload.progress != nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,22 +126,62 @@ struct PostView: View {
|
||||
}
|
||||
|
||||
var TopBar: some View {
|
||||
HStack {
|
||||
Button(NSLocalizedString("Cancel", comment: "Button to cancel out of posting a note.")) {
|
||||
self.cancel()
|
||||
VStack {
|
||||
HStack(spacing: 5.0) {
|
||||
Button(NSLocalizedString("Cancel", comment: "Button to cancel out of posting a note.")) {
|
||||
self.cancel()
|
||||
}
|
||||
.foregroundColor(.primary)
|
||||
|
||||
if let error {
|
||||
Text(error)
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if !is_post_empty {
|
||||
PostButton
|
||||
}
|
||||
}
|
||||
.foregroundColor(.primary)
|
||||
|
||||
Spacer()
|
||||
|
||||
if !is_post_empty {
|
||||
PostButton
|
||||
|
||||
if let progress = image_upload.progress {
|
||||
ProgressView(value: progress, total: 1.0)
|
||||
.progressViewStyle(.linear)
|
||||
}
|
||||
}
|
||||
.frame(height: 30)
|
||||
.padding([.top, .bottom], 4)
|
||||
}
|
||||
|
||||
func handle_upload(image: UIImage) {
|
||||
let uploader = get_image_uploader(damus_state.pubkey)
|
||||
|
||||
Task.init {
|
||||
let res = await image_upload.start(img: image, uploader: uploader)
|
||||
|
||||
switch res {
|
||||
case .success(let url):
|
||||
let uploadedImageURL = NSMutableAttributedString(string: url, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0), NSAttributedString.Key.foregroundColor: UIColor.label])
|
||||
let combinedAttributedString = NSMutableAttributedString()
|
||||
combinedAttributedString.append(post)
|
||||
if !post.string.hasSuffix(" ") {
|
||||
combinedAttributedString.append(NSAttributedString(string: " "))
|
||||
}
|
||||
combinedAttributedString.append(uploadedImageURL)
|
||||
post = combinedAttributedString
|
||||
|
||||
case .failed(let error):
|
||||
if let error {
|
||||
self.error = error.localizedDescription
|
||||
} else {
|
||||
self.error = "Error uploading image :("
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
TopBar
|
||||
@@ -162,9 +206,8 @@ struct PostView: View {
|
||||
AttachmentBar
|
||||
}
|
||||
.sheet(isPresented: $attach_media) {
|
||||
ImagePicker(sourceType: .photoLibrary) { image in
|
||||
let imageUploader = get_image_uploader(damus_state.pubkey)
|
||||
myImageUploadRequest(imageToUpload: image, imageUploader: imageUploader)
|
||||
ImagePicker(sourceType: .photoLibrary) { img in
|
||||
handle_upload(image: img)
|
||||
}
|
||||
}
|
||||
.onAppear() {
|
||||
|
||||
Reference in New Issue
Block a user