Compare commits
4 Commits
fe1db72f19
...
5b03e0cf12
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 5b03e0cf12 | |
Shadowfacts | 7c4bbfd730 | |
Shadowfacts | e19a6528ad | |
Shadowfacts | f5110c773a |
|
@ -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:
|
||||
|
|
|
@ -10,5 +10,5 @@ import Foundation
|
|||
|
||||
public enum Response<Result: Decodable> {
|
||||
case success(Result, Pagination?)
|
||||
case failure(Error)
|
||||
case failure(Client.Error)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -27,6 +27,10 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
|
|||
}
|
||||
|
||||
var animationSourceView: UIImageView? { sourceViews[currentIndex] }
|
||||
var largeImageController: LargeImageViewController? {
|
||||
// use protocol because page controllers may be loading or non-loading VCs
|
||||
(pages[currentIndex] as? LargeImageAnimatableViewController)?.largeImageController
|
||||
}
|
||||
var animationImage: UIImage? {
|
||||
if let page = pages[currentIndex] as? LargeImageAnimatableViewController,
|
||||
let image = page.animationImage {
|
||||
|
@ -48,6 +52,9 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
|
|||
override var prefersStatusBarHidden: Bool {
|
||||
return true
|
||||
}
|
||||
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
|
||||
return .none
|
||||
}
|
||||
override var childForHomeIndicatorAutoHidden: UIViewController? {
|
||||
return viewControllers?.first
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
|||
typealias ContentView = UIView & LargeImageContentView
|
||||
|
||||
weak var animationSourceView: UIImageView?
|
||||
var largeImageController: LargeImageViewController? { self }
|
||||
var animationImage: UIImage? { contentView.animationImage }
|
||||
var animationGifData: Data? { contentView.animationGifData }
|
||||
var dismissInteractionController: LargeImageInteractionController?
|
||||
|
@ -49,6 +50,9 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
|||
override var prefersStatusBarHidden: Bool {
|
||||
return true
|
||||
}
|
||||
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
|
||||
return .none
|
||||
}
|
||||
|
||||
override var prefersHomeIndicatorAutoHidden: Bool {
|
||||
return !controlsVisible
|
||||
|
|
|
@ -36,6 +36,7 @@ class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableVie
|
|||
var shrinkGestureEnabled = true
|
||||
|
||||
weak var animationSourceView: UIImageView?
|
||||
var largeImageController: LargeImageViewController? { largeImageVC }
|
||||
var animationImage: UIImage? { largeImageVC?.animationImage ?? animationSourceView?.image }
|
||||
var animationGifData: Data? { largeImageVC?.animationGifData }
|
||||
var dismissInteractionController: LargeImageInteractionController?
|
||||
|
@ -43,6 +44,9 @@ class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableVie
|
|||
override var prefersStatusBarHidden: Bool {
|
||||
return true
|
||||
}
|
||||
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
|
||||
return .none
|
||||
}
|
||||
override var childForHomeIndicatorAutoHidden: UIViewController? {
|
||||
return largeImageVC
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import Gifu
|
|||
|
||||
protocol LargeImageAnimatableViewController: UIViewController {
|
||||
var animationSourceView: UIImageView? { get }
|
||||
var largeImageController: LargeImageViewController? { get }
|
||||
var animationImage: UIImage? { get }
|
||||
var animationGifData: Data? { get }
|
||||
var dismissInteractionController: LargeImageInteractionController? { get }
|
||||
|
@ -37,7 +38,7 @@ extension LargeImageAnimatableViewController {
|
|||
class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
|
||||
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
return 0.2
|
||||
return 0.5
|
||||
}
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
|
@ -47,7 +48,7 @@ class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTra
|
|||
}
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
containerView.addSubview(toVC.view)
|
||||
|
||||
|
||||
let finalVCFrame = transitionContext.finalFrame(for: toVC)
|
||||
guard let sourceView = toVC.animationSourceView,
|
||||
|
@ -58,8 +59,11 @@ class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTra
|
|||
return
|
||||
}
|
||||
|
||||
// use alpha, becaus isHidden makes stack views re-layout
|
||||
// use alpha, because isHidden makes stack views re-layout
|
||||
sourceView.alpha = 0
|
||||
toVC.view.alpha = 0
|
||||
toVC.largeImageController?.contentView.isHidden = true
|
||||
toVC.largeImageController?.setControlsVisible(false, animated: false)
|
||||
|
||||
var finalFrameSize = finalVCFrame.inset(by: fromVC.view.safeAreaInsets).size
|
||||
let newWidth = finalFrameSize.width / image.size.width
|
||||
|
@ -81,21 +85,17 @@ class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTra
|
|||
imageView.layer.maskedCorners = sourceView.layer.maskedCorners
|
||||
imageView.layer.masksToBounds = true
|
||||
|
||||
let blackView = UIView(frame: finalVCFrame)
|
||||
blackView.backgroundColor = .black
|
||||
blackView.alpha = 0
|
||||
|
||||
containerView.addSubview(blackView)
|
||||
containerView.addSubview(toVC.view)
|
||||
containerView.addSubview(imageView)
|
||||
|
||||
toVC.view.isHidden = true
|
||||
|
||||
let duration = transitionDuration(using: transitionContext)
|
||||
UIView.animate(withDuration: duration, animations: {
|
||||
let velocity = 1 / CGFloat(duration)
|
||||
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.65, initialSpringVelocity: velocity, options: []) {
|
||||
imageView.frame = finalFrame
|
||||
imageView.layer.cornerRadius = 0
|
||||
blackView.alpha = 1
|
||||
}, completion: { _ in
|
||||
toVC.view.alpha = 1
|
||||
toVC.largeImageController?.setControlsVisible(true, animated: false)
|
||||
} completion: { (_) in
|
||||
// This shouldn't be necessary. I believe it's a workaround for using a XIB
|
||||
// for the large image VC. Without this, the final frame of the large image VC
|
||||
// is not set to the propper rect (it uses the frame of the preview device
|
||||
|
@ -103,15 +103,14 @@ class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTra
|
|||
// (or UIKit does layout differently when loading the view) and this is not necessary.
|
||||
toVC.view.frame = finalVCFrame
|
||||
|
||||
toVC.view.isHidden = false
|
||||
toVC.largeImageController?.contentView.isHidden = false
|
||||
fromVC.view.isHidden = false
|
||||
blackView.removeFromSuperview()
|
||||
imageView.removeFromSuperview()
|
||||
|
||||
sourceView.alpha = 1
|
||||
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -77,11 +77,11 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
|||
let peopleStr: String
|
||||
switch people.count {
|
||||
case 1:
|
||||
peopleStr = people.first!.displayName
|
||||
peopleStr = people.first!.displayOrUserName
|
||||
case 2:
|
||||
peopleStr = people.first!.displayName + " and " + people.last!.displayName
|
||||
peopleStr = people.first!.displayOrUserName + " and " + people.last!.displayOrUserName
|
||||
default:
|
||||
peopleStr = people.dropLast().map { $0.displayName }.joined(separator: ", ") + ", and " + people.last!.displayName
|
||||
peopleStr = people.dropLast().map { $0.displayOrUserName }.joined(separator: ", ") + ", and " + people.last!.displayOrUserName
|
||||
|
||||
}
|
||||
actionLabel.text = "Followed by \(peopleStr)"
|
||||
|
|
|
@ -35,7 +35,7 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
|
|||
profileAccessibilityElement.accessibilityFrameInContainerSpace = profileDetailContainerView.convert(profileDetailContainerView.frame, to: self)
|
||||
accessibilityElements = [profileAccessibilityElement!, contentWarningLabel!, collapseButton!, contentTextView!, totalFavoritesButton!, totalReblogsButton!, timestampAndClientLabel!, replyButton!, favoriteButton!, reblogButton!, moreButton!]
|
||||
|
||||
contentTextView.defaultFont = .systemFont(ofSize: 20)
|
||||
contentTextView.defaultFont = .systemFont(ofSize: 18)
|
||||
}
|
||||
|
||||
override func updateUI(statusID: String, state: StatusState) {
|
||||
|
|
|
@ -48,6 +48,8 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
|||
|
||||
// todo: double check this on RTL layouts
|
||||
replyButton.imageView!.leadingAnchor.constraint(equalTo: contentTextView.leadingAnchor).isActive = true
|
||||
|
||||
contentTextView.defaultFont = .systemFont(ofSize: 16)
|
||||
}
|
||||
|
||||
override func createObserversIfNecessary() {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17147" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17154" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17120"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17124"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
|
@ -111,7 +111,7 @@
|
|||
<rect key="frame" x="0.0" y="83" width="277" height="86.5"/>
|
||||
<string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.</string>
|
||||
<color key="textColor" systemColor="labelColor"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
<view hidden="YES" contentMode="scaleToFill" verticalCompressionResistancePriority="1" translatesAutoresizingMaskIntoConstraints="NO" id="nbq-yr-2mA" customClass="AttachmentsContainerView" customModule="Tusker" customModuleProvider="target">
|
||||
|
@ -240,7 +240,7 @@
|
|||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="arrowshape.turn.up.left.fill" catalog="system" width="128" height="104"/>
|
||||
<image name="arrowshape.turn.up.left.fill" catalog="system" width="128" height="106"/>
|
||||
<image name="bubble.left.and.bubble.right" catalog="system" width="128" height="96"/>
|
||||
<image name="chevron.down" catalog="system" width="128" height="72"/>
|
||||
<image name="ellipsis" catalog="system" width="128" height="37"/>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue