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
|
let task = session.dataTask(with: request) { data, response, error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
completion(.failure(error))
|
completion(.failure(.networkError(error)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let data = data,
|
guard let data = data,
|
||||||
let response = response as? HTTPURLResponse else {
|
let response = response as? HTTPURLResponse else {
|
||||||
completion(.failure(Error.invalidResponse))
|
completion(.failure(.invalidResponse))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard response.statusCode == 200 else {
|
guard response.statusCode == 200 else {
|
||||||
let mastodonError = try? self.decoder.decode(MastodonError.self, from: data)
|
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))
|
completion(.failure(error))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let result = try? self.decoder.decode(Result.self, from: data) else {
|
guard let result = try? self.decoder.decode(Result.self, from: data) else {
|
||||||
completion(.failure(Error.invalidModel))
|
completion(.failure(.invalidModel))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let pagination = response.allHeaderFields["Link"].flatMap { $0 as? String }.flatMap(Pagination.init)
|
let pagination = response.allHeaderFields["Link"].flatMap { $0 as? String }.flatMap(Pagination.init)
|
||||||
|
@ -315,7 +315,8 @@ public class Client {
|
||||||
|
|
||||||
extension Client {
|
extension Client {
|
||||||
public enum Error: LocalizedError {
|
public enum Error: LocalizedError {
|
||||||
case unknownError
|
case networkError(Swift.Error)
|
||||||
|
case unexpectedStatus(Int)
|
||||||
case invalidRequest
|
case invalidRequest
|
||||||
case invalidResponse
|
case invalidResponse
|
||||||
case invalidModel
|
case invalidModel
|
||||||
|
@ -323,8 +324,13 @@ extension Client {
|
||||||
|
|
||||||
public var localizedDescription: String {
|
public var localizedDescription: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .unknownError:
|
case .networkError(let error):
|
||||||
return "Unknown 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:
|
case .invalidRequest:
|
||||||
return "Invalid Request"
|
return "Invalid Request"
|
||||||
case .invalidResponse:
|
case .invalidResponse:
|
||||||
|
|
|
@ -10,5 +10,5 @@ import Foundation
|
||||||
|
|
||||||
public enum Response<Result: Decodable> {
|
public enum Response<Result: Decodable> {
|
||||||
case success(Result, Pagination?)
|
case success(Result, Pagination?)
|
||||||
case failure(Error)
|
case failure(Client.Error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,16 +22,16 @@ public class InstanceSelector {
|
||||||
let request = URLRequest(url: url)
|
let request = URLRequest(url: url)
|
||||||
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
|
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
completion(.failure(error))
|
completion(.failure(.networkError(error)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let data = data,
|
guard let data = data,
|
||||||
let response = response as? HTTPURLResponse else {
|
let response = response as? HTTPURLResponse else {
|
||||||
completion(.failure(Client.Error.invalidResponse))
|
completion(.failure(.invalidResponse))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard response.statusCode == 200 else {
|
guard response.statusCode == 200 else {
|
||||||
completion(.failure(Client.Error.unknownError))
|
completion(.failure(.unexpectedStatus(response.statusCode)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let result = try? decoder.decode([Instance].self, from: data) else {
|
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 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? {
|
var animationImage: UIImage? {
|
||||||
if let page = pages[currentIndex] as? LargeImageAnimatableViewController,
|
if let page = pages[currentIndex] as? LargeImageAnimatableViewController,
|
||||||
let image = page.animationImage {
|
let image = page.animationImage {
|
||||||
|
@ -48,6 +52,9 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
|
||||||
override var prefersStatusBarHidden: Bool {
|
override var prefersStatusBarHidden: Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
|
||||||
|
return .none
|
||||||
|
}
|
||||||
override var childForHomeIndicatorAutoHidden: UIViewController? {
|
override var childForHomeIndicatorAutoHidden: UIViewController? {
|
||||||
return viewControllers?.first
|
return viewControllers?.first
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,14 +12,14 @@ import Combine
|
||||||
|
|
||||||
struct ComposeView: View {
|
struct ComposeView: View {
|
||||||
@ObservedObject var draft: Draft
|
@ObservedObject var draft: Draft
|
||||||
|
|
||||||
@EnvironmentObject var mastodonController: MastodonController
|
@EnvironmentObject var mastodonController: MastodonController
|
||||||
@EnvironmentObject var uiState: ComposeUIState
|
@EnvironmentObject var uiState: ComposeUIState
|
||||||
@State var isPosting = false
|
|
||||||
@State var postProgress: Double = 0
|
@State private var isPosting = false
|
||||||
@State var postTotalProgress: Double = 0
|
@State private var postProgress: Double = 0
|
||||||
@State var isShowingPostErrorAlert = false
|
@State private var postTotalProgress: Double = 0
|
||||||
@State var postError: Error?
|
@State private var isShowingPostErrorAlert = false
|
||||||
|
@State private var postError: PostError?
|
||||||
|
|
||||||
private let stackPadding: CGFloat = 8
|
private let stackPadding: CGFloat = 8
|
||||||
|
|
||||||
|
@ -191,6 +191,9 @@ struct ComposeView: View {
|
||||||
case let .failure(error):
|
case let .failure(error):
|
||||||
self.isShowingPostErrorAlert = true
|
self.isShowingPostErrorAlert = true
|
||||||
self.postError = error
|
self.postError = error
|
||||||
|
self.postProgress = 0
|
||||||
|
self.postTotalProgress = 0
|
||||||
|
self.isPosting = false
|
||||||
|
|
||||||
case let .success(uploadedAttachments):
|
case let .success(uploadedAttachments):
|
||||||
let request = Client.createStatus(text: draft.text,
|
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()
|
let group = DispatchGroup()
|
||||||
|
|
||||||
var anyFailed = false
|
var anyFailed = false
|
||||||
|
@ -239,13 +242,13 @@ struct ComposeView: View {
|
||||||
let formAttachment = FormAttachment(mimeType: mimeType, data: data, fileName: "file")
|
let formAttachment = FormAttachment(mimeType: mimeType, data: data, fileName: "file")
|
||||||
let request = Client.upload(attachment: formAttachment, description: compAttachment.attachmentDescription)
|
let request = Client.upload(attachment: formAttachment, description: compAttachment.attachmentDescription)
|
||||||
self.mastodonController.run(request) { (response) in
|
self.mastodonController.run(request) { (response) in
|
||||||
postProgress += 1
|
|
||||||
|
|
||||||
switch response {
|
switch response {
|
||||||
case let .failure(error):
|
case let .failure(error):
|
||||||
uploadedAttachments[index] = .failure(error)
|
uploadedAttachments[index] = .failure(error)
|
||||||
anyFailed = true
|
anyFailed = true
|
||||||
|
|
||||||
case let .success(attachment, _):
|
case let .success(attachment, _):
|
||||||
|
postProgress += 1
|
||||||
uploadedAttachments[index] = .success(attachment)
|
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?]
|
let errors: [Error?]
|
||||||
|
|
||||||
var localizedDescription: String {
|
var localizedDescription: String {
|
||||||
return errors.enumerated().compactMap { (index, error) -> String? in
|
return errors.enumerated().compactMap { (index, error) -> String? in
|
||||||
guard let error = error else { return nil }
|
guard let error = error else { return nil }
|
||||||
return "Attachment \(index + 1): \(error.localizedDescription)"
|
let description: String
|
||||||
}.joined(separator: ", ")
|
// 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
|
typealias ContentView = UIView & LargeImageContentView
|
||||||
|
|
||||||
weak var animationSourceView: UIImageView?
|
weak var animationSourceView: UIImageView?
|
||||||
|
var largeImageController: LargeImageViewController? { self }
|
||||||
var animationImage: UIImage? { contentView.animationImage }
|
var animationImage: UIImage? { contentView.animationImage }
|
||||||
var animationGifData: Data? { contentView.animationGifData }
|
var animationGifData: Data? { contentView.animationGifData }
|
||||||
var dismissInteractionController: LargeImageInteractionController?
|
var dismissInteractionController: LargeImageInteractionController?
|
||||||
|
@ -49,6 +50,9 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
||||||
override var prefersStatusBarHidden: Bool {
|
override var prefersStatusBarHidden: Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
|
||||||
override var prefersHomeIndicatorAutoHidden: Bool {
|
override var prefersHomeIndicatorAutoHidden: Bool {
|
||||||
return !controlsVisible
|
return !controlsVisible
|
||||||
|
|
|
@ -36,6 +36,7 @@ class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableVie
|
||||||
var shrinkGestureEnabled = true
|
var shrinkGestureEnabled = true
|
||||||
|
|
||||||
weak var animationSourceView: UIImageView?
|
weak var animationSourceView: UIImageView?
|
||||||
|
var largeImageController: LargeImageViewController? { largeImageVC }
|
||||||
var animationImage: UIImage? { largeImageVC?.animationImage ?? animationSourceView?.image }
|
var animationImage: UIImage? { largeImageVC?.animationImage ?? animationSourceView?.image }
|
||||||
var animationGifData: Data? { largeImageVC?.animationGifData }
|
var animationGifData: Data? { largeImageVC?.animationGifData }
|
||||||
var dismissInteractionController: LargeImageInteractionController?
|
var dismissInteractionController: LargeImageInteractionController?
|
||||||
|
@ -43,6 +44,9 @@ class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableVie
|
||||||
override var prefersStatusBarHidden: Bool {
|
override var prefersStatusBarHidden: Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
|
||||||
|
return .none
|
||||||
|
}
|
||||||
override var childForHomeIndicatorAutoHidden: UIViewController? {
|
override var childForHomeIndicatorAutoHidden: UIViewController? {
|
||||||
return largeImageVC
|
return largeImageVC
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import Gifu
|
||||||
|
|
||||||
protocol LargeImageAnimatableViewController: UIViewController {
|
protocol LargeImageAnimatableViewController: UIViewController {
|
||||||
var animationSourceView: UIImageView? { get }
|
var animationSourceView: UIImageView? { get }
|
||||||
|
var largeImageController: LargeImageViewController? { get }
|
||||||
var animationImage: UIImage? { get }
|
var animationImage: UIImage? { get }
|
||||||
var animationGifData: Data? { get }
|
var animationGifData: Data? { get }
|
||||||
var dismissInteractionController: LargeImageInteractionController? { get }
|
var dismissInteractionController: LargeImageInteractionController? { get }
|
||||||
|
@ -37,7 +38,7 @@ extension LargeImageAnimatableViewController {
|
||||||
class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
||||||
|
|
||||||
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||||
return 0.2
|
return 0.5
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||||
|
@ -47,19 +48,22 @@ class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTra
|
||||||
}
|
}
|
||||||
|
|
||||||
let containerView = transitionContext.containerView
|
let containerView = transitionContext.containerView
|
||||||
containerView.addSubview(toVC.view)
|
|
||||||
|
|
||||||
let finalVCFrame = transitionContext.finalFrame(for: toVC)
|
let finalVCFrame = transitionContext.finalFrame(for: toVC)
|
||||||
guard let sourceView = toVC.animationSourceView,
|
guard let sourceView = toVC.animationSourceView,
|
||||||
let sourceFrame = toVC.sourceViewFrame(in: fromVC.view),
|
let sourceFrame = toVC.sourceViewFrame(in: fromVC.view),
|
||||||
let image = toVC.animationImage else {
|
let image = toVC.animationImage else {
|
||||||
toVC.view.frame = finalVCFrame
|
toVC.view.frame = finalVCFrame
|
||||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// use alpha, becaus isHidden makes stack views re-layout
|
// use alpha, because isHidden makes stack views re-layout
|
||||||
sourceView.alpha = 0
|
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
|
var finalFrameSize = finalVCFrame.inset(by: fromVC.view.safeAreaInsets).size
|
||||||
let newWidth = finalFrameSize.width / image.size.width
|
let newWidth = finalFrameSize.width / image.size.width
|
||||||
|
@ -81,21 +85,17 @@ class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTra
|
||||||
imageView.layer.maskedCorners = sourceView.layer.maskedCorners
|
imageView.layer.maskedCorners = sourceView.layer.maskedCorners
|
||||||
imageView.layer.masksToBounds = true
|
imageView.layer.masksToBounds = true
|
||||||
|
|
||||||
let blackView = UIView(frame: finalVCFrame)
|
containerView.addSubview(toVC.view)
|
||||||
blackView.backgroundColor = .black
|
|
||||||
blackView.alpha = 0
|
|
||||||
|
|
||||||
containerView.addSubview(blackView)
|
|
||||||
containerView.addSubview(imageView)
|
containerView.addSubview(imageView)
|
||||||
|
|
||||||
toVC.view.isHidden = true
|
|
||||||
|
|
||||||
let duration = transitionDuration(using: transitionContext)
|
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.frame = finalFrame
|
||||||
imageView.layer.cornerRadius = 0
|
imageView.layer.cornerRadius = 0
|
||||||
blackView.alpha = 1
|
toVC.view.alpha = 1
|
||||||
}, completion: { _ in
|
toVC.largeImageController?.setControlsVisible(true, animated: false)
|
||||||
|
} completion: { (_) in
|
||||||
// This shouldn't be necessary. I believe it's a workaround for using a XIB
|
// 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
|
// 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
|
// 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.
|
// (or UIKit does layout differently when loading the view) and this is not necessary.
|
||||||
toVC.view.frame = finalVCFrame
|
toVC.view.frame = finalVCFrame
|
||||||
|
|
||||||
toVC.view.isHidden = false
|
toVC.largeImageController?.contentView.isHidden = false
|
||||||
fromVC.view.isHidden = false
|
fromVC.view.isHidden = false
|
||||||
blackView.removeFromSuperview()
|
|
||||||
imageView.removeFromSuperview()
|
imageView.removeFromSuperview()
|
||||||
|
|
||||||
sourceView.alpha = 1
|
sourceView.alpha = 1
|
||||||
|
|
||||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,11 +77,11 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
||||||
let peopleStr: String
|
let peopleStr: String
|
||||||
switch people.count {
|
switch people.count {
|
||||||
case 1:
|
case 1:
|
||||||
peopleStr = people.first!.displayName
|
peopleStr = people.first!.displayOrUserName
|
||||||
case 2:
|
case 2:
|
||||||
peopleStr = people.first!.displayName + " and " + people.last!.displayName
|
peopleStr = people.first!.displayOrUserName + " and " + people.last!.displayOrUserName
|
||||||
default:
|
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)"
|
actionLabel.text = "Followed by \(peopleStr)"
|
||||||
|
|
|
@ -35,7 +35,7 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
profileAccessibilityElement.accessibilityFrameInContainerSpace = profileDetailContainerView.convert(profileDetailContainerView.frame, to: self)
|
profileAccessibilityElement.accessibilityFrameInContainerSpace = profileDetailContainerView.convert(profileDetailContainerView.frame, to: self)
|
||||||
accessibilityElements = [profileAccessibilityElement!, contentWarningLabel!, collapseButton!, contentTextView!, totalFavoritesButton!, totalReblogsButton!, timestampAndClientLabel!, replyButton!, favoriteButton!, reblogButton!, moreButton!]
|
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) {
|
override func updateUI(statusID: String, state: StatusState) {
|
||||||
|
|
|
@ -48,6 +48,8 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
|
|
||||||
// todo: double check this on RTL layouts
|
// todo: double check this on RTL layouts
|
||||||
replyButton.imageView!.leadingAnchor.constraint(equalTo: contentTextView.leadingAnchor).isActive = true
|
replyButton.imageView!.leadingAnchor.constraint(equalTo: contentTextView.leadingAnchor).isActive = true
|
||||||
|
|
||||||
|
contentTextView.defaultFont = .systemFont(ofSize: 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func createObserversIfNecessary() {
|
override func createObserversIfNecessary() {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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"/>
|
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<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="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.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"/>
|
<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>
|
<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"/>
|
<color key="textColor" systemColor="labelColor"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||||
</textView>
|
</textView>
|
||||||
<view hidden="YES" contentMode="scaleToFill" verticalCompressionResistancePriority="1" translatesAutoresizingMaskIntoConstraints="NO" id="nbq-yr-2mA" customClass="AttachmentsContainerView" customModule="Tusker" customModuleProvider="target">
|
<view hidden="YES" contentMode="scaleToFill" verticalCompressionResistancePriority="1" translatesAutoresizingMaskIntoConstraints="NO" id="nbq-yr-2mA" customClass="AttachmentsContainerView" customModule="Tusker" customModuleProvider="target">
|
||||||
|
@ -240,7 +240,7 @@
|
||||||
</view>
|
</view>
|
||||||
</objects>
|
</objects>
|
||||||
<resources>
|
<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="bubble.left.and.bubble.right" catalog="system" width="128" height="96"/>
|
||||||
<image name="chevron.down" catalog="system" width="128" height="72"/>
|
<image name="chevron.down" catalog="system" width="128" height="72"/>
|
||||||
<image name="ellipsis" catalog="system" width="128" height="37"/>
|
<image name="ellipsis" catalog="system" width="128" height="37"/>
|
||||||
|
|
|
@ -21,6 +21,8 @@ struct WrappedProgressView: UIViewRepresentable {
|
||||||
func updateUIView(_ uiView: UIProgressView, context: Context) {
|
func updateUIView(_ uiView: UIProgressView, context: Context) {
|
||||||
if total > 0 {
|
if total > 0 {
|
||||||
uiView.setProgress(Float(value / total), animated: true)
|
uiView.setProgress(Float(value / total), animated: true)
|
||||||
|
} else {
|
||||||
|
uiView.setProgress(0, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue