mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-22 16:31:00 +00:00
Videos: Compress them before upload + error handling close #430
This commit is contained in:
parent
92c1f40535
commit
7f7a967d87
13 changed files with 77 additions and 9 deletions
|
@ -4,6 +4,7 @@
|
|||
"action.save" = "Sichern";
|
||||
"action.done" = "Fertig";
|
||||
"action.retry" = "Wiederholen";
|
||||
"action.view.error" = "View error";
|
||||
|
||||
"alert.button.ok" = "Ok";
|
||||
"alert.error" = "Fehler!";
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"action.save" = "Save";
|
||||
"action.done" = "Done";
|
||||
"action.retry" = "Retry";
|
||||
"action.view.error" = "View error";
|
||||
|
||||
"alert.button.ok" = "OK";
|
||||
"alert.error" = "Error!";
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"action.save" = "Guardar";
|
||||
"action.done" = "Hecho";
|
||||
"action.retry" = "Reintentar";
|
||||
"action.view.error" = "View error";
|
||||
|
||||
"alert.button.ok" = "Ok";
|
||||
"alert.error" = "¡Error!";
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"action.save" = "Salva";
|
||||
"action.done" = "Fatto";
|
||||
"action.retry" = "Riprova";
|
||||
"action.view.error" = "View error";
|
||||
|
||||
"alert.button.ok" = "Ok";
|
||||
"alert.error" = "Errore!";
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"action.save" = "保存";
|
||||
"action.done" = "完了";
|
||||
"action.retry" = "リトライ";
|
||||
"action.view.error" = "View error";
|
||||
|
||||
"alert.button.ok" = "OK";
|
||||
"alert.error" = "エラー!";
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"action.save" = "Opslaan";
|
||||
"action.done" = "Gereed";
|
||||
"action.retry" = "Opnieuw";
|
||||
"action.view.error" = "View error";
|
||||
|
||||
"alert.button.ok" = "OK";
|
||||
"alert.error" = "Fout!";
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"action.save" = "Kaydet";
|
||||
"action.done" = "Tamamlandı";
|
||||
"action.retry" = "Yeniden Dene";
|
||||
"action.view.error" = "View error";
|
||||
|
||||
"alert.button.ok" = "Tamam";
|
||||
"alert.error" = "Hata!";
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"action.save" = "保存";
|
||||
"action.done" = "完成";
|
||||
"action.retry" = "重试";
|
||||
"action.view.error" = "View error";
|
||||
|
||||
"alert.button.ok" = "OK";
|
||||
"alert.error" = "错误!";
|
||||
|
|
|
@ -203,7 +203,14 @@ public class Client: ObservableObject, Equatable {
|
|||
request.httpBody = httpBody as Data
|
||||
let (data, httpResponse) = try await urlSession.data(for: request)
|
||||
logResponseOnError(httpResponse: httpResponse, data: data)
|
||||
return try decoder.decode(Entity.self, from: data)
|
||||
do {
|
||||
return try decoder.decode(Entity.self, from: data)
|
||||
} catch {
|
||||
if let serverError = try? decoder.decode(ServerError.self, from: data) {
|
||||
throw serverError
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private func logResponseOnError(httpResponse: URLResponse, data: Data) {
|
||||
|
|
|
@ -24,7 +24,11 @@ struct StatusEditorAccessoryView: View {
|
|||
HStack(alignment: .center, spacing: 16) {
|
||||
PhotosPicker(selection: $viewModel.selectedMedias,
|
||||
matching: .any(of: [.images, .videos])) {
|
||||
Image(systemName: "photo.fill.on.rectangle.fill")
|
||||
if viewModel.isMediasLoading {
|
||||
ProgressView()
|
||||
} else {
|
||||
Image(systemName: "photo.fill.on.rectangle.fill")
|
||||
}
|
||||
}
|
||||
.disabled(viewModel.showPoll)
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@ struct StatusEditorMediaView: View {
|
|||
@EnvironmentObject private var theme: Theme
|
||||
@ObservedObject var viewModel: StatusEditorViewModel
|
||||
@State private var editingContainer: StatusEditorMediaContainer?
|
||||
|
||||
@State private var isErrorDisplayed: Bool = false
|
||||
|
||||
var body: some View {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
|
@ -24,6 +26,8 @@ struct StatusEditorMediaView: View {
|
|||
makeLocalImage(container: container)
|
||||
} else if container.movieTransferable != nil {
|
||||
makeVideoAttachement(container: container)
|
||||
} else if let error = container.error as? ServerError {
|
||||
makeErrorView(error: error)
|
||||
}
|
||||
if container.mediaAttachment?.description?.isEmpty == false {
|
||||
altMarker
|
||||
|
@ -121,15 +125,24 @@ struct StatusEditorMediaView: View {
|
|||
|
||||
@ViewBuilder
|
||||
private func makeImageMenu(container: StatusEditorMediaContainer) -> some View {
|
||||
if !viewModel.mode.isEditing {
|
||||
if container.mediaAttachment != nil {
|
||||
if !viewModel.mode.isEditing {
|
||||
Button {
|
||||
editingContainer = container
|
||||
} label: {
|
||||
Label(container.mediaAttachment?.description?.isEmpty == false ?
|
||||
"status.editor.description.edit" : "status.editor.description.add",
|
||||
systemImage: "pencil.line")
|
||||
}
|
||||
}
|
||||
} else if container.error != nil {
|
||||
Button {
|
||||
editingContainer = container
|
||||
isErrorDisplayed = true
|
||||
} label: {
|
||||
Label(container.mediaAttachment?.description?.isEmpty == false ?
|
||||
"status.editor.description.edit" : "status.editor.description.add",
|
||||
systemImage: "pencil.line")
|
||||
Label("action.view.error", systemImage: "exclamationmark.triangle")
|
||||
}
|
||||
}
|
||||
|
||||
Button(role: .destructive) {
|
||||
withAnimation {
|
||||
viewModel.mediasImages.removeAll(where: { $0.id == container.id })
|
||||
|
@ -138,6 +151,20 @@ struct StatusEditorMediaView: View {
|
|||
Label("action.delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
|
||||
private func makeErrorView(error: ServerError) -> some View {
|
||||
ZStack {
|
||||
placeholderView
|
||||
Text("alert.error")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
.alert("alert.error", isPresented: $isErrorDisplayed) {
|
||||
Button("Ok", action: { })
|
||||
} message: {
|
||||
Text(error.error ?? "")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private var altMarker: some View {
|
||||
Button {} label: {
|
||||
|
|
|
@ -3,6 +3,7 @@ import PhotosUI
|
|||
import SwiftUI
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
import AVFoundation
|
||||
|
||||
@MainActor
|
||||
enum StatusEditorUTTypeSupported: String, CaseIterable {
|
||||
|
@ -70,7 +71,25 @@ enum StatusEditorUTTypeSupported: String, CaseIterable {
|
|||
}
|
||||
|
||||
struct MovieFileTranseferable: Transferable {
|
||||
let url: URL
|
||||
private let url: URL
|
||||
var compressedVideoURL: URL? {
|
||||
get async {
|
||||
return await withCheckedContinuation { continuation in
|
||||
let urlAsset = AVURLAsset(url: url, options: nil)
|
||||
guard let exportSession = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPresetMediumQuality) else {
|
||||
continuation.resume(returning: nil)
|
||||
return
|
||||
}
|
||||
let outputURL = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).\(url.pathExtension)")
|
||||
exportSession.outputURL = outputURL
|
||||
exportSession.outputFileType = .mp4
|
||||
exportSession.shouldOptimizeForNetworkUse = true
|
||||
exportSession.exportAsynchronously { () -> Void in
|
||||
continuation.resume(returning: outputURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static var transferRepresentation: some TransferRepresentation {
|
||||
FileRepresentation(contentType: .movie) { movie in
|
||||
|
|
|
@ -52,9 +52,11 @@ public class StatusEditorViewModel: ObservableObject {
|
|||
if selectedMedias.count > 4 {
|
||||
selectedMedias = selectedMedias.prefix(4).map { $0 }
|
||||
}
|
||||
isMediasLoading = true
|
||||
inflateSelectedMedias()
|
||||
}
|
||||
}
|
||||
@Published var isMediasLoading: Bool = false
|
||||
|
||||
@Published var mediasImages: [StatusEditorMediaContainer] = []
|
||||
@Published var replyToStatus: Status?
|
||||
|
@ -490,6 +492,7 @@ public class StatusEditorViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
private func processMediasToUpload() {
|
||||
isMediasLoading = false
|
||||
uploadTask?.cancel()
|
||||
let mediasCopy = mediasImages
|
||||
uploadTask = Task {
|
||||
|
@ -522,7 +525,7 @@ public class StatusEditorViewModel: ObservableObject {
|
|||
if let uploadedMedia, uploadedMedia.url == nil {
|
||||
scheduleAsyncMediaRefresh(mediaAttachement: uploadedMedia)
|
||||
}
|
||||
} else if let videoURL = originalContainer.movieTransferable?.url,
|
||||
} else if let videoURL = await originalContainer.movieTransferable?.compressedVideoURL,
|
||||
let data = try? Data(contentsOf: videoURL)
|
||||
{
|
||||
let uploadedMedia = try await uploadMedia(data: data, mimeType: videoURL.mimeType())
|
||||
|
|
Loading…
Reference in a new issue