mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-26 10:11:00 +00:00
Composer / Share sheet: add video upload support close #154
This commit is contained in:
parent
fd28864063
commit
eec5637c1c
10 changed files with 264 additions and 50 deletions
|
@ -12,6 +12,8 @@
|
||||||
<integer>4</integer>
|
<integer>4</integer>
|
||||||
<key>NSExtensionActivationSupportsText</key>
|
<key>NSExtensionActivationSupportsText</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
|
||||||
|
<integer>1</integer>
|
||||||
<key>NSExtensionActivationSupportsWebPageWithMaxCount</key>
|
<key>NSExtensionActivationSupportsWebPageWithMaxCount</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct ServerError: Decodable {
|
public struct ServerError: Decodable, Error {
|
||||||
public let error: String?
|
public let error: String?
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,7 +144,14 @@ public class Client: ObservableObject, Equatable {
|
||||||
let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: method)
|
let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: method)
|
||||||
let (data, httpResponse) = try await urlSession.data(for: request)
|
let (data, httpResponse) = try await urlSession.data(for: request)
|
||||||
logResponseOnError(httpResponse: httpResponse, data: data)
|
logResponseOnError(httpResponse: httpResponse, data: data)
|
||||||
|
do {
|
||||||
return try decoder.decode(Entity.self, from: data)
|
return try decoder.decode(Entity.self, from: data)
|
||||||
|
} catch let error {
|
||||||
|
if let serverError = try? decoder.decode(ServerError.self, from: data) {
|
||||||
|
throw serverError
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func oauthURL() async throws -> URL {
|
public func oauthURL() async throws -> URL {
|
||||||
|
|
|
@ -23,7 +23,7 @@ struct StatusEditorAccessoryView: View {
|
||||||
Divider()
|
Divider()
|
||||||
HStack(alignment: .center, spacing: 16) {
|
HStack(alignment: .center, spacing: 16) {
|
||||||
PhotosPicker(selection: $viewModel.selectedMedias,
|
PhotosPicker(selection: $viewModel.selectedMedias,
|
||||||
matching: .images) {
|
matching: .any(of: [.images, .videos])) {
|
||||||
Image(systemName: "photo.fill.on.rectangle.fill")
|
Image(systemName: "photo.fill.on.rectangle.fill")
|
||||||
}
|
}
|
||||||
.disabled(viewModel.showPoll)
|
.disabled(viewModel.showPoll)
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Models
|
||||||
|
import PhotosUI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct StatusEditorMediaContainer: Identifiable {
|
||||||
|
let id = UUID().uuidString
|
||||||
|
let image: UIImage?
|
||||||
|
let movieTransferable: MovieFileTranseferable?
|
||||||
|
let mediaAttachment: MediaAttachment?
|
||||||
|
let error: Error?
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ struct StatusEditorMediaEditView: View {
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
@ObservedObject var viewModel: StatusEditorViewModel
|
@ObservedObject var viewModel: StatusEditorViewModel
|
||||||
let container: StatusEditorViewModel.ImageContainer
|
let container: StatusEditorMediaContainer
|
||||||
|
|
||||||
@State private var imageDescription: String = ""
|
@State private var imageDescription: String = ""
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,12 @@ import Env
|
||||||
import Models
|
import Models
|
||||||
import NukeUI
|
import NukeUI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AVKit
|
||||||
|
|
||||||
struct StatusEditorMediaView: View {
|
struct StatusEditorMediaView: View {
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
@ObservedObject var viewModel: StatusEditorViewModel
|
@ObservedObject var viewModel: StatusEditorViewModel
|
||||||
@State private var editingContainer: StatusEditorViewModel.ImageContainer?
|
@State private var editingContainer: StatusEditorMediaContainer?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
@ -17,10 +18,12 @@ struct StatusEditorMediaView: View {
|
||||||
makeImageMenu(container: container)
|
makeImageMenu(container: container)
|
||||||
} label: {
|
} label: {
|
||||||
ZStack(alignment: .bottomTrailing) {
|
ZStack(alignment: .bottomTrailing) {
|
||||||
if container.image != nil {
|
if let attachement = container.mediaAttachment {
|
||||||
|
makeLazyImage(mediaAttachement: attachement)
|
||||||
|
} else if container.image != nil {
|
||||||
makeLocalImage(container: container)
|
makeLocalImage(container: container)
|
||||||
} else if let url = container.mediaAttachment?.url ?? container.mediaAttachment?.previewUrl {
|
} else if container.movieTransferable != nil {
|
||||||
makeLazyImage(url: url)
|
makeVideoAttachement(container: container)
|
||||||
}
|
}
|
||||||
if container.mediaAttachment?.description?.isEmpty == false {
|
if container.mediaAttachment?.description?.isEmpty == false {
|
||||||
altMarker
|
altMarker
|
||||||
|
@ -37,7 +40,18 @@ struct StatusEditorMediaView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeLocalImage(container: StatusEditorViewModel.ImageContainer) -> some View {
|
private func makeVideoAttachement(container: StatusEditorMediaContainer) -> some View {
|
||||||
|
ZStack(alignment: .center) {
|
||||||
|
placeholderView
|
||||||
|
if container.mediaAttachment == nil {
|
||||||
|
ProgressView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.cornerRadius(8)
|
||||||
|
.frame(width: 150, height: 150)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func makeLocalImage(container: StatusEditorMediaContainer) -> some View {
|
||||||
ZStack(alignment: .center) {
|
ZStack(alignment: .center) {
|
||||||
Image(uiImage: container.image!)
|
Image(uiImage: container.image!)
|
||||||
.resizable()
|
.resizable()
|
||||||
|
@ -75,15 +89,29 @@ struct StatusEditorMediaView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeLazyImage(url: URL?) -> some View {
|
private func makeLazyImage(mediaAttachement: MediaAttachment) -> some View {
|
||||||
|
ZStack(alignment: .center) {
|
||||||
|
if let url = mediaAttachement.url ?? mediaAttachement.previewUrl {
|
||||||
LazyImage(url: url) { state in
|
LazyImage(url: url) { state in
|
||||||
if let image = state.image {
|
if let image = state.image {
|
||||||
image
|
image
|
||||||
.resizingMode(.aspectFill)
|
.resizingMode(.aspectFill)
|
||||||
.frame(width: 150, height: 150)
|
.frame(width: 150, height: 150)
|
||||||
} else {
|
} else {
|
||||||
Rectangle()
|
placeholderView
|
||||||
.frame(width: 150, height: 150)
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
placeholderView
|
||||||
|
}
|
||||||
|
if mediaAttachement.url == nil {
|
||||||
|
ProgressView()
|
||||||
|
}
|
||||||
|
if mediaAttachement.url != nil,
|
||||||
|
mediaAttachement.supportedType == .video || mediaAttachement.supportedType == .gifv {
|
||||||
|
Image(systemName: "play.fill")
|
||||||
|
.font(.headline)
|
||||||
|
.tint(.white)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(width: 150, height: 150)
|
.frame(width: 150, height: 150)
|
||||||
|
@ -91,7 +119,7 @@ struct StatusEditorMediaView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func makeImageMenu(container: StatusEditorViewModel.ImageContainer) -> some View {
|
private func makeImageMenu(container: StatusEditorMediaContainer) -> some View {
|
||||||
if !viewModel.mode.isEditing {
|
if !viewModel.mode.isEditing {
|
||||||
Button {
|
Button {
|
||||||
editingContainer = container
|
editingContainer = container
|
||||||
|
@ -119,4 +147,10 @@ struct StatusEditorMediaView: View {
|
||||||
.background(.thinMaterial)
|
.background(.thinMaterial)
|
||||||
.cornerRadius(8)
|
.cornerRadius(8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var placeholderView: some View {
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(theme.secondaryBackgroundColor)
|
||||||
|
.frame(width: 150, height: 150)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
|
import SwiftUI
|
||||||
|
import PhotosUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
enum StatusEditorUTTypeSupported: String, CaseIterable {
|
enum StatusEditorUTTypeSupported: String, CaseIterable {
|
||||||
|
@ -11,12 +13,29 @@ enum StatusEditorUTTypeSupported: String, CaseIterable {
|
||||||
case jpeg = "public.jpeg"
|
case jpeg = "public.jpeg"
|
||||||
case png = "public.png"
|
case png = "public.png"
|
||||||
|
|
||||||
|
case video = "public.video"
|
||||||
|
case movie = "public.movie"
|
||||||
|
case mp4 = "public.mpeg-4"
|
||||||
|
case gif = "public.gif"
|
||||||
|
|
||||||
static func types() -> [UTType] {
|
static func types() -> [UTType] {
|
||||||
[.url, .text, .plainText, .image, .jpeg, .png]
|
[.url, .text, .plainText, .image, .jpeg, .png, .video, .mpeg4Movie, .gif, .movie]
|
||||||
|
}
|
||||||
|
|
||||||
|
var isVideo: Bool {
|
||||||
|
switch self {
|
||||||
|
case .video, .movie, .mp4, .gif:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadItemContent(item: NSItemProvider) async throws -> Any? {
|
func loadItemContent(item: NSItemProvider) async throws -> Any? {
|
||||||
let result = try await item.loadItem(forTypeIdentifier: rawValue)
|
let result = try await item.loadItem(forTypeIdentifier: rawValue)
|
||||||
|
if isVideo, let transferable = await getVideoTransferable(item: item) {
|
||||||
|
return transferable
|
||||||
|
}
|
||||||
if self == .jpeg || self == .png,
|
if self == .jpeg || self == .png,
|
||||||
let imageURL = result as? URL,
|
let imageURL = result as? URL,
|
||||||
let data = try? Data(contentsOf: imageURL),
|
let data = try? Data(contentsOf: imageURL),
|
||||||
|
@ -34,4 +53,59 @@ enum StatusEditorUTTypeSupported: String, CaseIterable {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func getVideoTransferable(item: NSItemProvider) async -> MovieFileTranseferable? {
|
||||||
|
return await withCheckedContinuation { continuation in
|
||||||
|
_ = item.loadTransferable(type: MovieFileTranseferable.self) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let success):
|
||||||
|
continuation.resume(with: .success(success))
|
||||||
|
case .failure:
|
||||||
|
continuation.resume(with: .success(nil))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MovieFileTranseferable: Transferable {
|
||||||
|
let url: URL
|
||||||
|
|
||||||
|
static var transferRepresentation: some TransferRepresentation {
|
||||||
|
FileRepresentation(contentType: .movie) { movie in
|
||||||
|
SentTransferredFile(movie.url)
|
||||||
|
} importing: { received in
|
||||||
|
let copy = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).\(received.file.pathExtension)")
|
||||||
|
try FileManager.default.copyItem(at: received.file, to: copy)
|
||||||
|
return Self.init(url: copy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ImageFileTranseferable: Transferable {
|
||||||
|
let url: URL
|
||||||
|
|
||||||
|
lazy var data: Data? = try? Data(contentsOf: url)
|
||||||
|
lazy var compressedData: Data? = image?.jpegData(compressionQuality: 0.90)
|
||||||
|
lazy var image: UIImage? = UIImage(data: data ?? Data())
|
||||||
|
|
||||||
|
static var transferRepresentation: some TransferRepresentation {
|
||||||
|
FileRepresentation(contentType: .image) { image in
|
||||||
|
SentTransferredFile(image.url)
|
||||||
|
} importing: { received in
|
||||||
|
let copy = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).\(received.file.pathExtension)")
|
||||||
|
try FileManager.default.copyItem(at: received.file, to: copy)
|
||||||
|
return Self.init(url: copy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension URL {
|
||||||
|
public func mimeType() -> String {
|
||||||
|
if let mimeType = UTType(filenameExtension: self.pathExtension)?.preferredMIMEType {
|
||||||
|
return mimeType
|
||||||
|
} else {
|
||||||
|
return "application/octet-stream"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,13 @@ public struct StatusEditorView: View {
|
||||||
.background(theme.primaryBackgroundColor)
|
.background(theme.primaryBackgroundColor)
|
||||||
.navigationTitle(viewModel.mode.title)
|
.navigationTitle(viewModel.mode.title)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.alert("Error while posting",
|
||||||
|
isPresented: $viewModel.showPostingErrorAlert,
|
||||||
|
actions: {
|
||||||
|
Button("Ok") { }
|
||||||
|
}, message: {
|
||||||
|
Text(viewModel.postingError ?? "")
|
||||||
|
})
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
AIMenu
|
AIMenu
|
||||||
|
|
|
@ -7,13 +7,6 @@ import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public class StatusEditorViewModel: ObservableObject {
|
public class StatusEditorViewModel: ObservableObject {
|
||||||
struct ImageContainer: Identifiable {
|
|
||||||
let id = UUID().uuidString
|
|
||||||
let image: UIImage?
|
|
||||||
let mediaAttachment: MediaAttachment?
|
|
||||||
let error: Error?
|
|
||||||
}
|
|
||||||
|
|
||||||
var mode: Mode
|
var mode: Mode
|
||||||
let generator = UINotificationFeedbackGenerator()
|
let generator = UINotificationFeedbackGenerator()
|
||||||
|
|
||||||
|
@ -50,12 +43,15 @@ public class StatusEditorViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Published var mediasImages: [ImageContainer] = []
|
@Published var mediasImages: [StatusEditorMediaContainer] = []
|
||||||
@Published var replyToStatus: Status?
|
@Published var replyToStatus: Status?
|
||||||
@Published var embeddedStatus: Status?
|
@Published var embeddedStatus: Status?
|
||||||
|
|
||||||
@Published var customEmojis: [Emoji] = []
|
@Published var customEmojis: [Emoji] = []
|
||||||
|
|
||||||
|
@Published var postingError: String?
|
||||||
|
@Published var showPostingErrorAlert: Bool = false
|
||||||
|
|
||||||
var canPost: Bool {
|
var canPost: Bool {
|
||||||
statusText.length > 0 || !mediasImages.isEmpty
|
statusText.length > 0 || !mediasImages.isEmpty
|
||||||
}
|
}
|
||||||
|
@ -119,7 +115,11 @@ public class StatusEditorViewModel: ObservableObject {
|
||||||
generator.notificationOccurred(.success)
|
generator.notificationOccurred(.success)
|
||||||
isPosting = false
|
isPosting = false
|
||||||
return postStatus
|
return postStatus
|
||||||
} catch {
|
} catch let error {
|
||||||
|
if let error = error as? Models.ServerError {
|
||||||
|
postingError = error.error
|
||||||
|
showPostingErrorAlert = true
|
||||||
|
}
|
||||||
isPosting = false
|
isPosting = false
|
||||||
generator.notificationOccurred(.error)
|
generator.notificationOccurred(.error)
|
||||||
return nil
|
return nil
|
||||||
|
@ -185,7 +185,10 @@ public class StatusEditorViewModel: ObservableObject {
|
||||||
spoilerOn = !status.spoilerText.asRawText.isEmpty
|
spoilerOn = !status.spoilerText.asRawText.isEmpty
|
||||||
spoilerText = status.spoilerText.asRawText
|
spoilerText = status.spoilerText.asRawText
|
||||||
visibility = status.visibility
|
visibility = status.visibility
|
||||||
mediasImages = status.mediaAttachments.map { .init(image: nil, mediaAttachment: $0, error: nil) }
|
mediasImages = status.mediaAttachments.map { .init(image: nil,
|
||||||
|
movieTransferable: nil,
|
||||||
|
mediaAttachment: $0,
|
||||||
|
error: nil) }
|
||||||
case let .quote(status):
|
case let .quote(status):
|
||||||
embeddedStatus = status
|
embeddedStatus = status
|
||||||
if let url = embeddedStatusURL {
|
if let url = embeddedStatusURL {
|
||||||
|
@ -247,7 +250,10 @@ public class StatusEditorViewModel: ObservableObject {
|
||||||
var mediaAdded = false
|
var mediaAdded = false
|
||||||
statusText.enumerateAttribute(.attachment, in: range) { attachment, range, _ in
|
statusText.enumerateAttribute(.attachment, in: range) { attachment, range, _ in
|
||||||
if let attachment = attachment as? NSTextAttachment, let image = attachment.image {
|
if let attachment = attachment as? NSTextAttachment, let image = attachment.image {
|
||||||
mediasImages.append(.init(image: image, mediaAttachment: nil, error: nil))
|
mediasImages.append(.init(image: image,
|
||||||
|
movieTransferable: nil,
|
||||||
|
mediaAttachment: nil,
|
||||||
|
error: nil))
|
||||||
statusText.removeAttribute(.attachment, range: range)
|
statusText.removeAttribute(.attachment, range: range)
|
||||||
statusText.mutableString.deleteCharacters(in: range)
|
statusText.mutableString.deleteCharacters(in: range)
|
||||||
mediaAdded = true
|
mediaAdded = true
|
||||||
|
@ -274,7 +280,15 @@ public class StatusEditorViewModel: ObservableObject {
|
||||||
if let text = content as? String {
|
if let text = content as? String {
|
||||||
initialText += "\(text) "
|
initialText += "\(text) "
|
||||||
} else if let image = content as? UIImage {
|
} else if let image = content as? UIImage {
|
||||||
mediasImages.append(.init(image: image, mediaAttachment: nil, error: nil))
|
mediasImages.append(.init(image: image,
|
||||||
|
movieTransferable: nil,
|
||||||
|
mediaAttachment: nil,
|
||||||
|
error: nil))
|
||||||
|
} else if let video = content as? MovieFileTranseferable {
|
||||||
|
mediasImages.append(.init(image: nil,
|
||||||
|
movieTransferable: video,
|
||||||
|
mediaAttachment: nil,
|
||||||
|
error: nil))
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
@ -380,7 +394,7 @@ public class StatusEditorViewModel: ObservableObject {
|
||||||
|
|
||||||
// MARK: - Media related function
|
// MARK: - Media related function
|
||||||
|
|
||||||
private func indexOf(container: ImageContainer) -> Int? {
|
private func indexOf(container: StatusEditorMediaContainer) -> Int? {
|
||||||
mediasImages.firstIndex(where: { $0.id == container.id })
|
mediasImages.firstIndex(where: { $0.id == container.id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -388,18 +402,35 @@ public class StatusEditorViewModel: ObservableObject {
|
||||||
mediasImages = []
|
mediasImages = []
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
var medias: [ImageContainer] = []
|
var medias: [StatusEditorMediaContainer] = []
|
||||||
for media in selectedMedias {
|
for media in selectedMedias {
|
||||||
|
var file: (any Transferable)?
|
||||||
do {
|
do {
|
||||||
if let data = try await media.loadTransferable(type: Data.self),
|
file = try await media.loadTransferable(type: ImageFileTranseferable.self)
|
||||||
let image = UIImage(data: data)
|
if file == nil {
|
||||||
{
|
file = try await media.loadTransferable(type: MovieFileTranseferable.self)
|
||||||
medias.append(.init(image: image, mediaAttachment: nil, error: nil))
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
medias.append(.init(image: nil, mediaAttachment: nil, error: error))
|
medias.append(.init(image: nil,
|
||||||
|
movieTransferable: nil,
|
||||||
|
mediaAttachment: nil,
|
||||||
|
error: error))
|
||||||
|
}
|
||||||
|
|
||||||
|
if var imageFile = file as? ImageFileTranseferable,
|
||||||
|
let image = imageFile.image {
|
||||||
|
medias.append(.init(image: image,
|
||||||
|
movieTransferable: nil,
|
||||||
|
mediaAttachment: nil,
|
||||||
|
error: nil))
|
||||||
|
} else if let videoFile = file as? MovieFileTranseferable {
|
||||||
|
medias.append(.init(image: nil,
|
||||||
|
movieTransferable: videoFile,
|
||||||
|
mediaAttachment: nil,
|
||||||
|
error: nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
self?.mediasImages = medias
|
self?.mediasImages = medias
|
||||||
self?.processMediasToUpload()
|
self?.processMediasToUpload()
|
||||||
|
@ -419,45 +450,91 @@ public class StatusEditorViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func upload(container: ImageContainer) async {
|
func upload(container: StatusEditorMediaContainer) async {
|
||||||
if let index = indexOf(container: container) {
|
if let index = indexOf(container: container) {
|
||||||
let originalContainer = mediasImages[index]
|
let originalContainer = mediasImages[index]
|
||||||
let newContainer = ImageContainer(image: originalContainer.image, mediaAttachment: nil, error: nil)
|
let newContainer = StatusEditorMediaContainer(image: originalContainer.image,
|
||||||
|
movieTransferable: originalContainer.movieTransferable,
|
||||||
|
mediaAttachment: nil,
|
||||||
|
error: nil)
|
||||||
mediasImages[index] = newContainer
|
mediasImages[index] = newContainer
|
||||||
do {
|
do {
|
||||||
if let data = originalContainer.image?.jpegData(compressionQuality: 0.90) {
|
|
||||||
let uploadedMedia = try await uploadMedia(data: data)
|
|
||||||
if let index = indexOf(container: newContainer) {
|
if let index = indexOf(container: newContainer) {
|
||||||
|
if let image = originalContainer.image,
|
||||||
|
let data = image.jpegData(compressionQuality: 0.90) {
|
||||||
|
let uploadedMedia = try await uploadMedia(data: data, mimeType: "image/jpeg")
|
||||||
mediasImages[index] = .init(image: mode.isInShareExtension ? originalContainer.image : nil,
|
mediasImages[index] = .init(image: mode.isInShareExtension ? originalContainer.image : nil,
|
||||||
|
movieTransferable: nil,
|
||||||
mediaAttachment: uploadedMedia,
|
mediaAttachment: uploadedMedia,
|
||||||
error: nil)
|
error: nil)
|
||||||
|
} else if let videoURL = originalContainer.movieTransferable?.url,
|
||||||
|
let data = try? Data(contentsOf: videoURL) {
|
||||||
|
let uploadedMedia = try await uploadMedia(data: data, mimeType: videoURL.mimeType())
|
||||||
|
mediasImages[index] = .init(image: mode.isInShareExtension ? originalContainer.image : nil,
|
||||||
|
movieTransferable: originalContainer.movieTransferable,
|
||||||
|
mediaAttachment: uploadedMedia,
|
||||||
|
error: nil)
|
||||||
|
if let uploadedMedia, uploadedMedia.url == nil {
|
||||||
|
scheduleAsyncMediaRefresh(mediaAttachement: uploadedMedia)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
if let index = indexOf(container: newContainer) {
|
if let index = indexOf(container: newContainer) {
|
||||||
mediasImages[index] = .init(image: originalContainer.image, mediaAttachment: nil, error: error)
|
mediasImages[index] = .init(image: originalContainer.image,
|
||||||
|
movieTransferable: nil,
|
||||||
|
mediaAttachment: nil,
|
||||||
|
error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addDescription(container: ImageContainer, description: String) async {
|
private func scheduleAsyncMediaRefresh(mediaAttachement: MediaAttachment) {
|
||||||
|
Task {
|
||||||
|
repeat {
|
||||||
|
if let client,
|
||||||
|
let index = mediasImages.firstIndex(where: { $0.mediaAttachment?.id == mediaAttachement.id }) {
|
||||||
|
guard mediasImages[index].mediaAttachment?.url == nil else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
let newAttachement: MediaAttachment = try await client.get(endpoint: Media.media(id: mediaAttachement.id,
|
||||||
|
description: nil))
|
||||||
|
if newAttachement.url != nil {
|
||||||
|
let oldContainer = mediasImages[index]
|
||||||
|
mediasImages[index] = .init(image: oldContainer.image,
|
||||||
|
movieTransferable: oldContainer.movieTransferable,
|
||||||
|
mediaAttachment: newAttachement,
|
||||||
|
error: nil)
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
try? await Task.sleep(for: .seconds(5))
|
||||||
|
} while (!Task.isCancelled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDescription(container: StatusEditorMediaContainer, description: String) async {
|
||||||
guard let client, let attachment = container.mediaAttachment else { return }
|
guard let client, let attachment = container.mediaAttachment else { return }
|
||||||
if let index = indexOf(container: container) {
|
if let index = indexOf(container: container) {
|
||||||
do {
|
do {
|
||||||
let media: MediaAttachment = try await client.put(endpoint: Media.media(id: attachment.id,
|
let media: MediaAttachment = try await client.put(endpoint: Media.media(id: attachment.id,
|
||||||
description: description))
|
description: description))
|
||||||
mediasImages[index] = .init(image: nil, mediaAttachment: media, error: nil)
|
mediasImages[index] = .init(image: nil,
|
||||||
|
movieTransferable: nil,
|
||||||
|
mediaAttachment: media,
|
||||||
|
error: nil)
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func uploadMedia(data: Data) async throws -> MediaAttachment? {
|
private func uploadMedia(data: Data, mimeType: String) async throws -> MediaAttachment? {
|
||||||
guard let client else { return nil }
|
guard let client else { return nil }
|
||||||
return try await client.mediaUpload(endpoint: Media.medias,
|
return try await client.mediaUpload(endpoint: Media.medias,
|
||||||
version: .v2,
|
version: .v2,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
mimeType: "image/jpeg",
|
mimeType: mimeType,
|
||||||
filename: "file",
|
filename: "file",
|
||||||
data: data)
|
data: data)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue