Preview media uploads when posting
Changelog-Added: Preview media uploads when posting Closes: #894
This commit is contained in:
@@ -10,4 +10,5 @@ import Foundation
|
|||||||
class Drafts: ObservableObject {
|
class Drafts: ObservableObject {
|
||||||
@Published var post: NSMutableAttributedString = NSMutableAttributedString(string: "")
|
@Published var post: NSMutableAttributedString = NSMutableAttributedString(string: "")
|
||||||
@Published var replies: [NostrEvent: NSMutableAttributedString] = [:]
|
@Published var replies: [NostrEvent: NSMutableAttributedString] = [:]
|
||||||
|
@Published var medias: [UploadedMedia] = []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,15 @@ enum MediaUpload {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var localURL: URL {
|
||||||
|
switch self {
|
||||||
|
case .image(let url):
|
||||||
|
return url
|
||||||
|
case .video(let url):
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var is_image: Bool {
|
var is_image: Bool {
|
||||||
if case .image = self {
|
if case .image = self {
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
enum NostrPostResult {
|
enum NostrPostResult {
|
||||||
case post(NostrPost)
|
case post(NostrPost)
|
||||||
@@ -21,7 +22,7 @@ struct PostView: View {
|
|||||||
@State var attach_media: Bool = false
|
@State var attach_media: Bool = false
|
||||||
@State var attach_camera: Bool = false
|
@State var attach_camera: Bool = false
|
||||||
@State var error: String? = nil
|
@State var error: String? = nil
|
||||||
|
@State var uploadedMedias: [UploadedMedia] = []
|
||||||
@State var originalReferences: [ReferencedId] = []
|
@State var originalReferences: [ReferencedId] = []
|
||||||
@State var references: [ReferencedId] = []
|
@State var references: [ReferencedId] = []
|
||||||
|
|
||||||
@@ -57,7 +58,14 @@ struct PostView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = self.post.string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
|
||||||
|
|
||||||
|
var content = self.post.string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||||
|
|
||||||
|
let imagesString = uploadedMedias.map { $0.uploadedURL.absoluteString }.joined(separator: " ")
|
||||||
|
|
||||||
|
content.append(" " + imagesString + " ")
|
||||||
|
|
||||||
let new_post = NostrPost(content: content, references: references, kind: kind)
|
let new_post = NostrPost(content: content, references: references, kind: kind)
|
||||||
|
|
||||||
NotificationCenter.default.post(name: .post, object: NostrPostResult.post(new_post))
|
NotificationCenter.default.post(name: .post, object: NostrPostResult.post(new_post))
|
||||||
@@ -66,13 +74,15 @@ struct PostView: View {
|
|||||||
damus_state.drafts.replies.removeValue(forKey: replying_to)
|
damus_state.drafts.replies.removeValue(forKey: replying_to)
|
||||||
} else {
|
} else {
|
||||||
damus_state.drafts.post = NSMutableAttributedString(string: "")
|
damus_state.drafts.post = NSMutableAttributedString(string: "")
|
||||||
|
uploadedMedias = []
|
||||||
|
damus_state.drafts.medias = []
|
||||||
}
|
}
|
||||||
|
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
var is_post_empty: Bool {
|
var is_post_empty: Bool {
|
||||||
return post.string.allSatisfy { $0.isWhitespace }
|
return post.string.allSatisfy { $0.isWhitespace } && uploadedMedias.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
var ImageButton: some View {
|
var ImageButton: some View {
|
||||||
@@ -168,29 +178,20 @@ struct PostView: View {
|
|||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
|
|
||||||
func append_url(_ url: String) {
|
|
||||||
let uploadedImageURL = NSMutableAttributedString(string: url)
|
|
||||||
let combinedAttributedString = NSMutableAttributedString()
|
|
||||||
combinedAttributedString.append(post)
|
|
||||||
if !post.string.hasSuffix(" ") {
|
|
||||||
combinedAttributedString.append(NSAttributedString(string: " "))
|
|
||||||
}
|
|
||||||
combinedAttributedString.append(uploadedImageURL)
|
|
||||||
|
|
||||||
// make sure we have a space at the end
|
|
||||||
combinedAttributedString.append(NSAttributedString(string: " "))
|
|
||||||
post = combinedAttributedString
|
|
||||||
}
|
|
||||||
|
|
||||||
func handle_upload(media: MediaUpload) {
|
func handle_upload(media: MediaUpload) {
|
||||||
let uploader = get_media_uploader(damus_state.pubkey)
|
let uploader = get_media_uploader(damus_state.pubkey)
|
||||||
|
|
||||||
Task.init {
|
Task.init {
|
||||||
|
let img = getImage(media: media)
|
||||||
let res = await image_upload.start(media: media, uploader: uploader)
|
let res = await image_upload.start(media: media, uploader: uploader)
|
||||||
|
|
||||||
switch res {
|
switch res {
|
||||||
case .success(let url):
|
case .success(let url):
|
||||||
append_url(url)
|
guard let url = URL(string: url) else {
|
||||||
|
self.error = "Error uploading image :("
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let uploadedMedia = UploadedMedia(localURL: media.localURL, uploadedURL: url, representingImage: img)
|
||||||
|
uploadedMedias.append(uploadedMedia)
|
||||||
|
|
||||||
case .failed(let error):
|
case .failed(let error):
|
||||||
if let error {
|
if let error {
|
||||||
@@ -222,8 +223,14 @@ struct PostView: View {
|
|||||||
|
|
||||||
TextEntry
|
TextEntry
|
||||||
}
|
}
|
||||||
.frame(height: deviceSize.size.height*0.78)
|
.frame(height: uploadedMedias.isEmpty ? deviceSize.size.height*0.78 : deviceSize.size.height*0.2)
|
||||||
.id("post")
|
.id("post")
|
||||||
|
|
||||||
|
PVImageCarouselView(media: $uploadedMedias, deviceWidth: deviceSize.size.width)
|
||||||
|
.onChange(of: uploadedMedias) { _ in
|
||||||
|
damus_state.drafts.medias = uploadedMedias
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
}
|
}
|
||||||
@@ -272,6 +279,7 @@ struct PostView: View {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
post = damus_state.drafts.post
|
post = damus_state.drafts.post
|
||||||
|
uploadedMedias = damus_state.drafts.medias
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
@@ -283,6 +291,7 @@ struct PostView: View {
|
|||||||
damus_state.drafts.replies.removeValue(forKey: replying_to)
|
damus_state.drafts.replies.removeValue(forKey: replying_to)
|
||||||
} else if replying_to == nil && damus_state.drafts.post.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
} else if replying_to == nil && damus_state.drafts.post.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
damus_state.drafts.post = NSMutableAttributedString(string : "")
|
damus_state.drafts.post = NSMutableAttributedString(string : "")
|
||||||
|
damus_state.drafts.medias = uploadedMedias
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.alert(NSLocalizedString("Note contains \"nsec1\" private key. Are you sure?", comment: "Alert user that they might be attempting to paste a private key and ask them to confirm."), isPresented: $showPrivateKeyWarning, actions: {
|
.alert(NSLocalizedString("Note contains \"nsec1\" private key. Are you sure?", comment: "Alert user that they might be attempting to paste a private key and ask them to confirm."), isPresented: $showPrivateKeyWarning, actions: {
|
||||||
@@ -323,3 +332,76 @@ struct PostView_Previews: PreviewProvider {
|
|||||||
PostView(replying_to: nil, damus_state: test_damus_state())
|
PostView(replying_to: nil, damus_state: test_damus_state())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct PVImageCarouselView: View {
|
||||||
|
@Binding var media: [UploadedMedia]
|
||||||
|
|
||||||
|
let deviceWidth: CGFloat
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
HStack {
|
||||||
|
ForEach(media.map({$0.representingImage}), id: \.self) { image in
|
||||||
|
ZStack(alignment: .topTrailing) {
|
||||||
|
Image(uiImage: image)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(width: media.count == 1 ? deviceWidth*0.8 : 250, height: media.count == 1 ? 400 : 250)
|
||||||
|
.cornerRadius(10)
|
||||||
|
.padding()
|
||||||
|
Image(systemName: "xmark.circle.fill")
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.padding(20)
|
||||||
|
.onTapGesture {
|
||||||
|
if let index = media.map({$0.representingImage}).firstIndex(of: image) {
|
||||||
|
media.remove(at: index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fileprivate func getImage(media: MediaUpload) -> UIImage {
|
||||||
|
var uiimage: UIImage = UIImage()
|
||||||
|
if media.is_image {
|
||||||
|
// fetch the image data
|
||||||
|
if let data = try? Data(contentsOf: media.localURL) {
|
||||||
|
uiimage = UIImage(data: data) ?? UIImage()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let asset = AVURLAsset(url: media.localURL)
|
||||||
|
let generator = AVAssetImageGenerator(asset: asset)
|
||||||
|
generator.appliesPreferredTrackTransform = true
|
||||||
|
let time = CMTimeMake(value: 1, timescale: 60) // get the thumbnail image at the 1st second
|
||||||
|
do {
|
||||||
|
let cgImage = try generator.copyCGImage(at: time, actualTime: nil)
|
||||||
|
uiimage = UIImage(cgImage: cgImage)
|
||||||
|
} catch {
|
||||||
|
print("No thumbnail: \(error)")
|
||||||
|
}
|
||||||
|
// create a play icon on the top to differentiate if media upload is image or a video, gif is an image
|
||||||
|
let playIcon = UIImage(systemName: "play.fill")?.withTintColor(.white, renderingMode: .alwaysOriginal)
|
||||||
|
let size = uiimage.size
|
||||||
|
let scale = UIScreen.main.scale
|
||||||
|
UIGraphicsBeginImageContextWithOptions(size, false, scale)
|
||||||
|
uiimage.draw(at: .zero)
|
||||||
|
let playIconSize = CGSize(width: 60, height: 60)
|
||||||
|
let playIconOrigin = CGPoint(x: (size.width - playIconSize.width) / 2, y: (size.height - playIconSize.height) / 2)
|
||||||
|
playIcon?.draw(in: CGRect(origin: playIconOrigin, size: playIconSize))
|
||||||
|
let newImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||||
|
UIGraphicsEndImageContext()
|
||||||
|
uiimage = newImage ?? UIImage()
|
||||||
|
}
|
||||||
|
return uiimage
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UploadedMedia: Equatable {
|
||||||
|
let localURL: URL
|
||||||
|
let uploadedURL: URL
|
||||||
|
let representingImage: UIImage
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user