diff --git a/Pachyderm/Client.swift b/Pachyderm/Client.swift index bd02bd2b..abc163d5 100644 --- a/Pachyderm/Client.swift +++ b/Pachyderm/Client.swift @@ -50,22 +50,22 @@ public class Client { let task = session.dataTask(with: request) { data, response, error in if let error = error { - completion(.failure(error)) + completion(.failure(.networkError(error))) return } guard let data = data, let response = response as? HTTPURLResponse else { - completion(.failure(Error.invalidResponse)) + completion(.failure(.invalidResponse)) return } guard response.statusCode == 200 else { let mastodonError = try? self.decoder.decode(MastodonError.self, from: data) - let error = mastodonError.flatMap { Error.mastodonError($0.description) } ?? Error.unknownError + let error: Error = mastodonError.flatMap { .mastodonError($0.description) } ?? .unexpectedStatus(response.statusCode) completion(.failure(error)) return } guard let result = try? self.decoder.decode(Result.self, from: data) else { - completion(.failure(Error.invalidModel)) + completion(.failure(.invalidModel)) return } let pagination = response.allHeaderFields["Link"].flatMap { $0 as? String }.flatMap(Pagination.init) @@ -315,7 +315,8 @@ public class Client { extension Client { public enum Error: LocalizedError { - case unknownError + case networkError(Swift.Error) + case unexpectedStatus(Int) case invalidRequest case invalidResponse case invalidModel @@ -323,8 +324,13 @@ extension Client { public var localizedDescription: String { switch self { - case .unknownError: - return "Unknown Error" + case .networkError(let error): + return "Network Error: \(error.localizedDescription)" + // todo: support more status codes + case .unexpectedStatus(413): + return "HTTP 413: Payload Too Large" + case .unexpectedStatus(let code): + return "HTTP Code \(code)" case .invalidRequest: return "Invalid Request" case .invalidResponse: diff --git a/Pachyderm/Response/Response.swift b/Pachyderm/Response/Response.swift index 7e5cd6d0..8ec0a5d7 100644 --- a/Pachyderm/Response/Response.swift +++ b/Pachyderm/Response/Response.swift @@ -10,5 +10,5 @@ import Foundation public enum Response { case success(Result, Pagination?) - case failure(Error) + case failure(Client.Error) } diff --git a/Pachyderm/Utilities/InstanceSelector.swift b/Pachyderm/Utilities/InstanceSelector.swift index dd2ce064..c45723cc 100644 --- a/Pachyderm/Utilities/InstanceSelector.swift +++ b/Pachyderm/Utilities/InstanceSelector.swift @@ -22,16 +22,16 @@ public class InstanceSelector { let request = URLRequest(url: url) let task = URLSession.shared.dataTask(with: request) { (data, response, error) in if let error = error { - completion(.failure(error)) + completion(.failure(.networkError(error))) return } guard let data = data, let response = response as? HTTPURLResponse else { - completion(.failure(Client.Error.invalidResponse)) + completion(.failure(.invalidResponse)) return } guard response.statusCode == 200 else { - completion(.failure(Client.Error.unknownError)) + completion(.failure(.unexpectedStatus(response.statusCode))) return } guard let result = try? decoder.decode([Instance].self, from: data) else { diff --git a/Tusker/Screens/Compose/ComposeView.swift b/Tusker/Screens/Compose/ComposeView.swift index 5d21fcaa..7fa54983 100644 --- a/Tusker/Screens/Compose/ComposeView.swift +++ b/Tusker/Screens/Compose/ComposeView.swift @@ -12,14 +12,14 @@ import Combine struct ComposeView: View { @ObservedObject var draft: Draft - @EnvironmentObject var mastodonController: MastodonController @EnvironmentObject var uiState: ComposeUIState - @State var isPosting = false - @State var postProgress: Double = 0 - @State var postTotalProgress: Double = 0 - @State var isShowingPostErrorAlert = false - @State var postError: Error? + + @State private var isPosting = false + @State private var postProgress: Double = 0 + @State private var postTotalProgress: Double = 0 + @State private var isShowingPostErrorAlert = false + @State private var postError: PostError? private let stackPadding: CGFloat = 8 @@ -191,6 +191,9 @@ struct ComposeView: View { case let .failure(error): self.isShowingPostErrorAlert = true self.postError = error + self.postProgress = 0 + self.postTotalProgress = 0 + self.isPosting = false case let .success(uploadedAttachments): let request = Client.createStatus(text: draft.text, @@ -222,7 +225,7 @@ struct ComposeView: View { } } - private func uploadAttachments(_ completion: @escaping (Result<[Attachment], Error>) -> Void) { + private func uploadAttachments(_ completion: @escaping (Result<[Attachment], AttachmentUploadError>) -> Void) { let group = DispatchGroup() var anyFailed = false @@ -239,13 +242,13 @@ struct ComposeView: View { let formAttachment = FormAttachment(mimeType: mimeType, data: data, fileName: "file") let request = Client.upload(attachment: formAttachment, description: compAttachment.attachmentDescription) self.mastodonController.run(request) { (response) in - postProgress += 1 - switch response { case let .failure(error): uploadedAttachments[index] = .failure(error) anyFailed = true + case let .success(attachment, _): + postProgress += 1 uploadedAttachments[index] = .success(attachment) } @@ -274,14 +277,37 @@ struct ComposeView: View { } } -fileprivate struct AttachmentUploadError: LocalizedError { +fileprivate protocol PostError: LocalizedError {} + +extension PostError { + var localizedDescription: String { + if let self = self as? Client.Error { + return self.localizedDescription + } else if let self = self as? AttachmentUploadError { + return self.localizedDescription + } else { + return "Unknown Error" + } + } +} + +extension Client.Error: PostError {} + +fileprivate struct AttachmentUploadError: PostError { let errors: [Error?] var localizedDescription: String { return errors.enumerated().compactMap { (index, error) -> String? in guard let error = error else { return nil } - return "Attachment \(index + 1): \(error.localizedDescription)" - }.joined(separator: ", ") + let description: String + // need to downcast to use more specific localizedDescription impl from Pachyderm + if let error = error as? Client.Error { + description = error.localizedDescription + } else { + description = error.localizedDescription + } + return "Attachment \(index + 1): \(description)" + }.joined(separator: ",\n") } } diff --git a/Tusker/Views/WrappedProgressView.swift b/Tusker/Views/WrappedProgressView.swift index 60847a69..933328e7 100644 --- a/Tusker/Views/WrappedProgressView.swift +++ b/Tusker/Views/WrappedProgressView.swift @@ -21,6 +21,8 @@ struct WrappedProgressView: UIViewRepresentable { func updateUIView(_ uiView: UIProgressView, context: Context) { if total > 0 { uiView.setProgress(Float(value / total), animated: true) + } else { + uiView.setProgress(0, animated: true) } } }