commit 7b1f4b770160b5b8aa0a0834adfd37d65543fdee
parent 7b6d3ef9dfb471e9030aa9e7792b4636127f8997
Author: William Casarin <jb55@jb55.com>
Date: Thu, 16 Mar 2023 09:13:03 -0600
Show image upload progress
Diffstat:
4 files changed, 136 insertions(+), 58 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -175,6 +175,7 @@
4CCEB7A929B29DD50078AA28 /* SearchResultsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7A829B29DD50078AA28 /* SearchResultsModel.swift */; };
4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */; };
4CCEB7B029B5415A0078AA28 /* SearchingProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */; };
+ 4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */; };
4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; };
4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */; };
4CE0E2B229A3DF6900DB4CA2 /* LoadMoreButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */; };
@@ -238,8 +239,8 @@
7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */; };
7CFF6317299FEFE5005D382A /* SelectableText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CFF6316299FEFE5005D382A /* SelectableText.swift */; };
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9609F057296E220800069BF3 /* BannerImageView.swift */; };
- 9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */; };
9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C83F89229A937B900136C08 /* TextViewWrapper.swift */; };
+ 9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */; };
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; };
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */; };
@@ -542,6 +543,7 @@
4CCEB7A829B29DD50078AA28 /* SearchResultsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsModel.swift; sourceTree = "<group>"; };
4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchingEventView.swift; sourceTree = "<group>"; };
4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchingProfileView.swift; sourceTree = "<group>"; };
+ 4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUploadModel.swift; sourceTree = "<group>"; };
4CD7641A28A1641400B6928F /* EndBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndBlock.swift; sourceTree = "<group>"; };
4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventHolder.swift; sourceTree = "<group>"; };
4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreButton.swift; sourceTree = "<group>"; };
@@ -607,8 +609,8 @@
7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KFOptionSetter+.swift"; sourceTree = "<group>"; };
7CFF6316299FEFE5005D382A /* SelectableText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableText.swift; sourceTree = "<group>"; };
9609F057296E220800069BF3 /* BannerImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerImageView.swift; sourceTree = "<group>"; };
- 9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachMediaUtility.swift; sourceTree = "<group>"; };
9C83F89229A937B900136C08 /* TextViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewWrapper.swift; sourceTree = "<group>"; };
+ 9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachMediaUtility.swift; sourceTree = "<group>"; };
BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; };
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownTests.swift; sourceTree = "<group>"; };
@@ -766,6 +768,7 @@
4CE8795A2996C47A00F758CC /* ZapsModel.swift */,
3AA59D1C2999B0400061C48E /* DraftsModel.swift */,
4C54AA0629A540BA003E4487 /* NotificationsModel.swift */,
+ 4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */,
);
path = Models;
sourceTree = "<group>";
@@ -1434,6 +1437,7 @@
4CB8838B296F6E1E00DC99E7 /* NIP05Badge.swift in Sources */,
4C3EA66828FF5F9900C48A62 /* hex.c in Sources */,
E9E4ED0B295867B900DD7078 /* ThreadView.swift in Sources */,
+ 4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */,
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
4CF0ABE7298444FD00D66079 /* MutedEventView.swift in Sources */,
9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */,
diff --git a/damus/Models/ImageUploadModel.swift b/damus/Models/ImageUploadModel.swift
@@ -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
+ }
+ }
+ }
+}
diff --git a/damus/Views/AttachMediaUtility.swift b/damus/Views/AttachMediaUtility.swift
@@ -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
-
- 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()
- }
+enum ImageUploadResult {
+ case success(String)
+ case failed(Error?)
+}
- 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)
diff --git a/damus/Views/PostView.swift b/damus/Views/PostView.swift
@@ -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()
- }
- .foregroundColor(.primary)
+ 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()
+ Spacer()
- if !is_post_empty {
- PostButton
+ 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() {