forked from shadowfacts/Tusker
Use Gifu for GIF playback
This commit is contained in:
parent
86d064dc6e
commit
f684591888
|
@ -4,3 +4,6 @@
|
||||||
[submodule "Cache"]
|
[submodule "Cache"]
|
||||||
path = Cache
|
path = Cache
|
||||||
url = git@github.com:hyperoslo/Cache.git
|
url = git@github.com:hyperoslo/Cache.git
|
||||||
|
[submodule "Gifu"]
|
||||||
|
path = Gifu
|
||||||
|
url = git://github.com/kaishin/Gifu.git
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit ed572f53ce58b8e23499abeb3a926033cbe480f7
|
|
@ -115,6 +115,8 @@
|
||||||
D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */; };
|
D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */; };
|
||||||
D6A5FAFB217B86CE003DB2D9 /* OnboardingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAFA217B86CE003DB2D9 /* OnboardingViewController.xib */; };
|
D6A5FAFB217B86CE003DB2D9 /* OnboardingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAFA217B86CE003DB2D9 /* OnboardingViewController.xib */; };
|
||||||
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */; };
|
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */; };
|
||||||
|
D6BC874521961F73006163F1 /* Gifu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6BC874421961F73006163F1 /* Gifu.framework */; };
|
||||||
|
D6BC874621961F73006163F1 /* Gifu.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BC874421961F73006163F1 /* Gifu.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */; };
|
D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */; };
|
||||||
D6C693CA2161253F007D6A6D /* SilentActionPermissionsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693C92161253F007D6A6D /* SilentActionPermissionsTableViewController.swift */; };
|
D6C693CA2161253F007D6A6D /* SilentActionPermissionsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693C92161253F007D6A6D /* SilentActionPermissionsTableViewController.swift */; };
|
||||||
|
@ -194,6 +196,7 @@
|
||||||
04496BD0216252E5001F1B23 /* TTTAttributedLabel.framework in Embed Frameworks */,
|
04496BD0216252E5001F1B23 /* TTTAttributedLabel.framework in Embed Frameworks */,
|
||||||
D61099C12144B0CC00432DC2 /* Pachyderm.framework in Embed Frameworks */,
|
D61099C12144B0CC00432DC2 /* Pachyderm.framework in Embed Frameworks */,
|
||||||
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */,
|
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */,
|
||||||
|
D6BC874621961F73006163F1 /* Gifu.framework in Embed Frameworks */,
|
||||||
0461A3912163CBAE00C0A807 /* Cache.framework in Embed Frameworks */,
|
0461A3912163CBAE00C0A807 /* Cache.framework in Embed Frameworks */,
|
||||||
);
|
);
|
||||||
name = "Embed Frameworks";
|
name = "Embed Frameworks";
|
||||||
|
@ -309,6 +312,7 @@
|
||||||
D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeViewController.xib; sourceTree = "<group>"; };
|
D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeViewController.xib; sourceTree = "<group>"; };
|
||||||
D6A5FAFA217B86CE003DB2D9 /* OnboardingViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OnboardingViewController.xib; sourceTree = "<group>"; };
|
D6A5FAFA217B86CE003DB2D9 /* OnboardingViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OnboardingViewController.xib; sourceTree = "<group>"; };
|
||||||
D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Visibility.swift"; sourceTree = "<group>"; };
|
D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Visibility.swift"; sourceTree = "<group>"; };
|
||||||
|
D6BC874421961F73006163F1 /* Gifu.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Gifu.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSoup.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSoup.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = "<group>"; };
|
D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D6C693C92161253F007D6A6D /* SilentActionPermissionsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SilentActionPermissionsTableViewController.swift; sourceTree = "<group>"; };
|
D6C693C92161253F007D6A6D /* SilentActionPermissionsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SilentActionPermissionsTableViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -369,6 +373,7 @@
|
||||||
04496BCF216252E5001F1B23 /* TTTAttributedLabel.framework in Frameworks */,
|
04496BCF216252E5001F1B23 /* TTTAttributedLabel.framework in Frameworks */,
|
||||||
D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */,
|
D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */,
|
||||||
D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */,
|
D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */,
|
||||||
|
D6BC874521961F73006163F1 /* Gifu.framework in Frameworks */,
|
||||||
0461A3902163CBAE00C0A807 /* Cache.framework in Frameworks */,
|
0461A3902163CBAE00C0A807 /* Cache.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -742,6 +747,7 @@
|
||||||
D6D4DDC3212518A000E1C4BB = {
|
D6D4DDC3212518A000E1C4BB = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D6BC874421961F73006163F1 /* Gifu.framework */,
|
||||||
0461A38F2163CBAE00C0A807 /* Cache.framework */,
|
0461A38F2163CBAE00C0A807 /* Cache.framework */,
|
||||||
D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */,
|
D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */,
|
||||||
D61099AC2144B0CC00432DC2 /* Pachyderm */,
|
D61099AC2144B0CC00432DC2 /* Pachyderm */,
|
||||||
|
|
|
@ -10,4 +10,7 @@
|
||||||
<FileRef
|
<FileRef
|
||||||
location = "group:SwiftSoup/SwiftSoup.xcodeproj">
|
location = "group:SwiftSoup/SwiftSoup.xcodeproj">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Gifu/Gifu.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
</Workspace>
|
</Workspace>
|
||||||
|
|
|
@ -91,6 +91,13 @@ class AppRouter {
|
||||||
return vc
|
return vc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func largeImage(gifData: Data, description: String?, sourceFrame: CGRect, sourceCornerRadius: CGFloat, transitioningDelegate: UIViewControllerTransitioningDelegate?) -> LargeImageViewController {
|
||||||
|
let vc = LargeImageViewController(image: UIImage(data: gifData)!, description: description, sourceFrame: sourceFrame, sourceCornerRadius: sourceCornerRadius, router: self)
|
||||||
|
vc.gifData = gifData
|
||||||
|
vc.transitioningDelegate = transitioningDelegate
|
||||||
|
return vc
|
||||||
|
}
|
||||||
|
|
||||||
func moreOptions(forStatus statusID: String) -> UIAlertController {
|
func moreOptions(forStatus statusID: String) -> UIAlertController {
|
||||||
guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
|
guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
|
||||||
|
|
||||||
|
|
|
@ -15,45 +15,45 @@ class ImageCache {
|
||||||
static let headers = ImageCache(name: "Headers", memoryExpiry: .seconds(60 * 60), diskExpiry: .seconds(60 * 60 * 24))
|
static let headers = ImageCache(name: "Headers", memoryExpiry: .seconds(60 * 60), diskExpiry: .seconds(60 * 60 * 24))
|
||||||
static let attachments = ImageCache(name: "Attachments", memoryExpiry: .seconds(60 * 2))
|
static let attachments = ImageCache(name: "Attachments", memoryExpiry: .seconds(60 * 2))
|
||||||
|
|
||||||
let cache: Cache<UIImage>
|
let cache: Cache<Data>
|
||||||
|
|
||||||
var requests = [URL: Request]()
|
var requests = [URL: Request]()
|
||||||
|
|
||||||
init(name: String, memoryExpiry expiry: Expiry) {
|
init(name: String, memoryExpiry expiry: Expiry) {
|
||||||
let storage = MemoryStorage<UIImage>(config: MemoryConfig(expiry: expiry))
|
let storage = MemoryStorage<Data>(config: MemoryConfig(expiry: expiry))
|
||||||
self.cache = .memory(storage)
|
self.cache = .memory(storage)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(name: String, diskExpiry expiry: Expiry) {
|
init(name: String, diskExpiry expiry: Expiry) {
|
||||||
let storage = try! DiskStorage<UIImage>(config: DiskConfig(name: name, expiry: expiry), transformer: TransformerFactory.forImage())
|
let storage = try! DiskStorage<Data>(config: DiskConfig(name: name, expiry: expiry), transformer: TransformerFactory.forData())
|
||||||
self.cache = .disk(storage)
|
self.cache = .disk(storage)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(name: String, memoryExpiry: Expiry, diskExpiry: Expiry) {
|
init(name: String, memoryExpiry: Expiry, diskExpiry: Expiry) {
|
||||||
let memory = MemoryStorage<UIImage>(config: MemoryConfig(expiry: memoryExpiry))
|
let memory = MemoryStorage<Data>(config: MemoryConfig(expiry: memoryExpiry))
|
||||||
let disk = try! DiskStorage<UIImage>(config: DiskConfig(name: name, expiry: diskExpiry), transformer: TransformerFactory.forImage())
|
let disk = try! DiskStorage<Data>(config: DiskConfig(name: name, expiry: diskExpiry), transformer: TransformerFactory.forData())
|
||||||
self.cache = .hybrid(HybridStorage(memoryStorage: memory, diskStorage: disk))
|
self.cache = .hybrid(HybridStorage(memoryStorage: memory, diskStorage: disk))
|
||||||
}
|
}
|
||||||
|
|
||||||
func get(_ url: URL, completion: ((UIImage?) -> Void)?) {
|
func get(_ url: URL, completion: ((Data?) -> Void)?) {
|
||||||
let key = url.absoluteString
|
let key = url.absoluteString
|
||||||
if (try? cache.existsObject(forKey: key)) ?? false,
|
if (try? cache.existsObject(forKey: key)) ?? false,
|
||||||
let image = try? cache.object(forKey: key) {
|
let data = try? cache.object(forKey: key) {
|
||||||
completion?(image)
|
completion?(data)
|
||||||
} else {
|
} else {
|
||||||
if let completion = completion, let request = requests[url] {
|
if let completion = completion, let request = requests[url] {
|
||||||
request.callbacks.append(completion)
|
request.callbacks.append(completion)
|
||||||
} else {
|
} else {
|
||||||
let request = Request(url: url, completion: completion)
|
let request = Request(url: url, completion: completion)
|
||||||
requests[url] = request
|
requests[url] = request
|
||||||
request.run { (image) in
|
request.run { (data) in
|
||||||
try? self.cache.setObject(image, forKey: key)
|
try? self.cache.setObject(data, forKey: key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func get(_ url: URL) -> UIImage? {
|
func get(_ url: URL) -> Data? {
|
||||||
return try? cache.object(forKey: url.absoluteString)
|
return try? cache.object(forKey: url.absoluteString)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,9 +64,9 @@ class ImageCache {
|
||||||
class Request {
|
class Request {
|
||||||
let url: URL
|
let url: URL
|
||||||
var task: URLSessionDataTask?
|
var task: URLSessionDataTask?
|
||||||
var callbacks: [(UIImage?) -> Void]
|
var callbacks: [(Data?) -> Void]
|
||||||
|
|
||||||
init(url: URL, completion: ((UIImage?) -> Void)?) {
|
init(url: URL, completion: ((Data?) -> Void)?) {
|
||||||
if let completion = completion {
|
if let completion = completion {
|
||||||
self.callbacks = [completion]
|
self.callbacks = [completion]
|
||||||
} else {
|
} else {
|
||||||
|
@ -75,14 +75,14 @@ class ImageCache {
|
||||||
self.url = url
|
self.url = url
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(cache: @escaping (UIImage) -> Void) {
|
func run(cache: @escaping (Data) -> Void) {
|
||||||
task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
|
task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
|
||||||
guard error == nil, let data = data, let image = UIImage(data: data) else {
|
guard error == nil, let data = data else {
|
||||||
self.complete(with: nil)
|
self.complete(with: nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cache(image)
|
cache(data)
|
||||||
self.complete(with: image)
|
self.complete(with: data)
|
||||||
})
|
})
|
||||||
task!.resume()
|
task!.resume()
|
||||||
}
|
}
|
||||||
|
@ -92,8 +92,8 @@ class ImageCache {
|
||||||
complete(with: nil)
|
complete(with: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func complete(with image: UIImage?) {
|
func complete(with data: Data?) {
|
||||||
callbacks.forEach { $0(image) }
|
callbacks.forEach { $0(data) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,9 +98,10 @@ class ComposeViewController: UIViewController {
|
||||||
inReplyToAvatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: inReplyToAvatarImageView)
|
inReplyToAvatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: inReplyToAvatarImageView)
|
||||||
inReplyToAvatarImageView.layer.masksToBounds = true
|
inReplyToAvatarImageView.layer.masksToBounds = true
|
||||||
inReplyToAvatarImageView.image = nil
|
inReplyToAvatarImageView.image = nil
|
||||||
ImageCache.avatars.get(inReplyTo.account.avatar) { (image) in
|
ImageCache.avatars.get(inReplyTo.account.avatar) { (data) in
|
||||||
|
guard let data = data else { return }
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.inReplyToAvatarImageView.image = image
|
self.inReplyToAvatarImageView.image = UIImage(data: data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inReplyToLabel.text = "In reply to \(inReplyTo.account.realDisplayName)"
|
inReplyToLabel.text = "In reply to \(inReplyTo.account.realDisplayName)"
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Photos
|
import Photos
|
||||||
|
import Gifu
|
||||||
|
|
||||||
class LargeImageViewController: UIViewController, UIScrollViewDelegate {
|
class LargeImageViewController: UIViewController, UIScrollViewDelegate {
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
|
||||||
var dismissInteractionController: LargeImageInteractionController?
|
var dismissInteractionController: LargeImageInteractionController?
|
||||||
|
|
||||||
@IBOutlet weak var scrollView: UIScrollView!
|
@IBOutlet weak var scrollView: UIScrollView!
|
||||||
@IBOutlet weak var imageView: UIImageView!
|
@IBOutlet weak var imageView: GIFImageView!
|
||||||
@IBOutlet weak var imageViewLeadingConstraint: NSLayoutConstraint!
|
@IBOutlet weak var imageViewLeadingConstraint: NSLayoutConstraint!
|
||||||
@IBOutlet weak var imageViewTrailingConstraint: NSLayoutConstraint!
|
@IBOutlet weak var imageViewTrailingConstraint: NSLayoutConstraint!
|
||||||
@IBOutlet weak var imageViewTopConstraint: NSLayoutConstraint!
|
@IBOutlet weak var imageViewTopConstraint: NSLayoutConstraint!
|
||||||
|
@ -39,6 +40,7 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
|
||||||
var initializedTopControlsConstrains = false
|
var initializedTopControlsConstrains = false
|
||||||
|
|
||||||
var image: UIImage?
|
var image: UIImage?
|
||||||
|
var gifData: Data?
|
||||||
var imageDescription: String?
|
var imageDescription: String?
|
||||||
|
|
||||||
var controlsVisible = true {
|
var controlsVisible = true {
|
||||||
|
@ -70,6 +72,16 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
|
||||||
super.init(nibName: "LargeImageViewController", bundle: nil)
|
super.init(nibName: "LargeImageViewController", bundle: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// init(gifData: Data?, description: String?, sourceFrame: CGRect, sourceCornerRadius: CGFloat, router: AppRouter) {
|
||||||
|
// self.router = router
|
||||||
|
// self.gifData = gifData
|
||||||
|
// self.imageDescription = description
|
||||||
|
// self.originFrame = sourceFrame
|
||||||
|
// self.originCornerRadius = sourceCornerRadius
|
||||||
|
//
|
||||||
|
// super.init(nibName: "LargeImageViewController", bundle: nil)
|
||||||
|
// }
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
@ -78,8 +90,12 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
imageView.image = image
|
imageView.image = image
|
||||||
|
if let gifData = gifData {
|
||||||
|
imageView.animate(withGIFData: gifData)
|
||||||
|
}
|
||||||
|
|
||||||
scrollView.delegate = self
|
scrollView.delegate = self
|
||||||
imageView.bounds = CGRect(origin: .zero, size: image!.size)
|
imageView.bounds = CGRect(origin: .zero, size: imageView.image!.size)
|
||||||
|
|
||||||
if let imageDescription = imageDescription {
|
if let imageDescription = imageDescription {
|
||||||
descriptionLabel.text = imageDescription
|
descriptionLabel.text = imageDescription
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<?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="14460.30.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
<device id="retina4_7" orientation="portrait">
|
<device id="retina4_7" orientation="portrait">
|
||||||
<adaptation id="fullscreen"/>
|
<adaptation id="fullscreen"/>
|
||||||
</device>
|
</device>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.19.1"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.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"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" minimumZoomScale="0.25" maximumZoomScale="2" translatesAutoresizingMaskIntoConstraints="NO" id="Skj-xq-AgQ">
|
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" minimumZoomScale="0.25" maximumZoomScale="2" translatesAutoresizingMaskIntoConstraints="NO" id="Skj-xq-AgQ">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qcn-1t-3sS">
|
<imageView contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qcn-1t-3sS" customClass="GIFImageView" customModule="Gifu">
|
||||||
<rect key="frame" x="0.0" y="-10" width="375" height="647"/>
|
<rect key="frame" x="0.0" y="-10" width="375" height="647"/>
|
||||||
<gestureRecognizers/>
|
<gestureRecognizers/>
|
||||||
</imageView>
|
</imageView>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Gifu
|
||||||
|
|
||||||
class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
||||||
|
|
||||||
|
@ -23,14 +24,17 @@ class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTra
|
||||||
|
|
||||||
let containerView = transitionContext.containerView
|
let containerView = transitionContext.containerView
|
||||||
let finalVCFrame = transitionContext.finalFrame(for: toVC)
|
let finalVCFrame = transitionContext.finalFrame(for: toVC)
|
||||||
let image = toVC.image!
|
let image = toVC.imageView.image!
|
||||||
let ratio = image.size.width / image.size.height
|
let ratio = image.size.width / image.size.height
|
||||||
let width = finalVCFrame.width
|
let width = finalVCFrame.width
|
||||||
let height = width / ratio
|
let height = width / ratio
|
||||||
let finalFrame = CGRect(x: finalVCFrame.midX - width / 2, y: finalVCFrame.midY - height / 2, width: width, height: height)
|
let finalFrame = CGRect(x: finalVCFrame.midX - width / 2, y: finalVCFrame.midY - height / 2, width: width, height: height)
|
||||||
|
|
||||||
let imageView = UIImageView(frame: originFrame)
|
let imageView = GIFImageView(frame: originFrame)
|
||||||
imageView.image = toVC.image!
|
imageView.image = toVC.imageView.image!
|
||||||
|
if let gifData = toVC.gifData {
|
||||||
|
imageView.animate(withGIFData: gifData)
|
||||||
|
}
|
||||||
imageView.contentMode = .scaleAspectFill
|
imageView.contentMode = .scaleAspectFill
|
||||||
imageView.layer.cornerRadius = toVC.originCornerRadius!
|
imageView.layer.cornerRadius = toVC.originCornerRadius!
|
||||||
imageView.layer.masksToBounds = true
|
imageView.layer.masksToBounds = true
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Gifu
|
||||||
|
|
||||||
class LargeImageShrinkAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
class LargeImageShrinkAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
||||||
|
|
||||||
|
@ -36,8 +37,11 @@ class LargeImageShrinkAnimationController: NSObject, UIViewControllerAnimatedTra
|
||||||
let height = width / ratio
|
let height = width / ratio
|
||||||
let originalFrame = CGRect(x: originalVCFrame.midX - width / 2, y: originalVCFrame.midY - height / 2, width: width, height: height)
|
let originalFrame = CGRect(x: originalVCFrame.midX - width / 2, y: originalVCFrame.midY - height / 2, width: width, height: height)
|
||||||
|
|
||||||
let imageView = UIImageView(frame: originalFrame)
|
let imageView = GIFImageView(frame: originalFrame)
|
||||||
imageView.image = fromVC.image!
|
imageView.image = fromVC.image!
|
||||||
|
if let gifData = fromVC.gifData {
|
||||||
|
imageView.animate(withGIFData: gifData)
|
||||||
|
}
|
||||||
imageView.contentMode = .scaleAspectFill
|
imageView.contentMode = .scaleAspectFill
|
||||||
imageView.layer.cornerRadius = 0
|
imageView.layer.cornerRadius = 0
|
||||||
imageView.layer.masksToBounds = true
|
imageView.layer.masksToBounds = true
|
||||||
|
|
|
@ -30,8 +30,12 @@ protocol TuskerNavigationDelegate {
|
||||||
|
|
||||||
func largeImage(_ image: UIImage, description: String?, sourceView: UIView) -> LargeImageViewController
|
func largeImage(_ image: UIImage, description: String?, sourceView: UIView) -> LargeImageViewController
|
||||||
|
|
||||||
|
func largeImage(gifData: Data, description: String?, sourceView: UIView) -> LargeImageViewController
|
||||||
|
|
||||||
func showLargeImage(_ image: UIImage, description: String?, animatingFrom sourceView: UIView)
|
func showLargeImage(_ image: UIImage, description: String?, animatingFrom sourceView: UIView)
|
||||||
|
|
||||||
|
func showLargeImage(gifData: Data, description: String?, animatingFrom sourceView: UIView)
|
||||||
|
|
||||||
func showMoreOptions(forStatus statusID: String)
|
func showMoreOptions(forStatus statusID: String)
|
||||||
|
|
||||||
func showMoreOptions(forURL url: URL)
|
func showMoreOptions(forURL url: URL)
|
||||||
|
@ -95,10 +99,28 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
||||||
return router.largeImage(image, description: description, sourceFrame: sourceFrame, sourceCornerRadius: sourceCornerRadius, transitioningDelegate: self)
|
return router.largeImage(image, description: description, sourceFrame: sourceFrame, sourceCornerRadius: sourceCornerRadius, transitioningDelegate: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func largeImage(gifData: Data, description: String?, sourceView: UIView) -> LargeImageViewController {
|
||||||
|
var sourceFrame = sourceView.convert(sourceView.bounds, to: view)
|
||||||
|
if let scrollView = view as? UIScrollView {
|
||||||
|
let scale = scrollView.zoomScale
|
||||||
|
let width = sourceFrame.width * scale
|
||||||
|
let height = sourceFrame.height * scale
|
||||||
|
let x = sourceFrame.minX * scale - scrollView.contentOffset.x + scrollView.frame.minX
|
||||||
|
let y = sourceFrame.minY * scale - scrollView.contentOffset.y + scrollView.frame.minY
|
||||||
|
sourceFrame = CGRect(x: x, y: y, width: width, height: height)
|
||||||
|
}
|
||||||
|
let sourceCornerRadius = sourceView.layer.cornerRadius
|
||||||
|
return router.largeImage(gifData: gifData, description: description, sourceFrame: sourceFrame, sourceCornerRadius: sourceCornerRadius, transitioningDelegate: self)
|
||||||
|
}
|
||||||
|
|
||||||
func showLargeImage(_ image: UIImage, description: String?, animatingFrom sourceView: UIView) {
|
func showLargeImage(_ image: UIImage, description: String?, animatingFrom sourceView: UIView) {
|
||||||
router.present(largeImage(image, description: description, sourceView: sourceView), animated: true)
|
router.present(largeImage(image, description: description, sourceView: sourceView), animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func showLargeImage(gifData: Data, description: String?, animatingFrom sourceView: UIView) {
|
||||||
|
router.present(largeImage(gifData: gifData, description: description, sourceView: sourceView), animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
func showMoreOptions(forStatus statusID: String) {
|
func showMoreOptions(forStatus statusID: String) {
|
||||||
router.present(router.moreOptions(forStatus: statusID), animated: true)
|
router.present(router.moreOptions(forStatus: statusID), animated: true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,16 +8,20 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
import Gifu
|
||||||
|
|
||||||
protocol AttachmentViewDelegate {
|
protocol AttachmentViewDelegate {
|
||||||
func showLargeAttachment(for attachmentView: AttachmentView)
|
func showLargeAttachment(for attachmentView: AttachmentView)
|
||||||
}
|
}
|
||||||
|
|
||||||
class AttachmentView: UIImageView {
|
class AttachmentView: UIImageView, GIFAnimatable {
|
||||||
|
|
||||||
var delegate: AttachmentViewDelegate?
|
var delegate: AttachmentViewDelegate?
|
||||||
|
|
||||||
var attachment: Attachment!
|
var attachment: Attachment!
|
||||||
|
var gifData: Data?
|
||||||
|
|
||||||
|
public lazy var animator: Animator? = Animator(withDelegate: self)
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
super.init(coder: aDecoder)
|
super.init(coder: aDecoder)
|
||||||
|
@ -43,13 +47,23 @@ class AttachmentView: UIImageView {
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadImage() {
|
func loadImage() {
|
||||||
ImageCache.attachments.get(attachment.url) { (image) in
|
ImageCache.attachments.get(attachment.url) { (data) in
|
||||||
|
guard let data = data else { return }
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.image = image
|
if self.attachment.url.pathExtension == "gif" {
|
||||||
|
self.animate(withGIFData: data)
|
||||||
|
self.gifData = data
|
||||||
|
} else {
|
||||||
|
self.image = UIImage(data: data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func display(_ layer: CALayer) {
|
||||||
|
updateImageIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
@objc func imagePressed() {
|
@objc func imagePressed() {
|
||||||
if image != nil {
|
if image != nil {
|
||||||
delegate?.showLargeAttachment(for: self)
|
delegate?.showLargeAttachment(for: self)
|
||||||
|
|
|
@ -79,17 +79,19 @@ class ActionNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
usernameLabel.text = "@\(status.account.acct)"
|
usernameLabel.text = "@\(status.account.acct)"
|
||||||
opAvatarImageView.image = nil
|
opAvatarImageView.image = nil
|
||||||
opAvatarURL = status.account.avatar
|
opAvatarURL = status.account.avatar
|
||||||
ImageCache.avatars.get(status.account.avatar) { (image) in
|
ImageCache.avatars.get(status.account.avatar) { (data) in
|
||||||
|
guard let data = data else { return }
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.opAvatarImageView.image = image
|
self.opAvatarImageView.image = UIImage(data: data)
|
||||||
self.opAvatarURL = nil
|
self.opAvatarURL = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
actionAvatarImageView.image = nil
|
actionAvatarImageView.image = nil
|
||||||
actionAvatarURL = notification.account.avatar
|
actionAvatarURL = notification.account.avatar
|
||||||
ImageCache.avatars.get(notification.account.avatar) { (image) in
|
ImageCache.avatars.get(notification.account.avatar) { (data) in
|
||||||
|
guard let data = data else { return }
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.actionAvatarImageView.image = image
|
self.actionAvatarImageView.image = UIImage(data: data)
|
||||||
self.actionAvatarURL = nil
|
self.actionAvatarURL = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,9 +51,10 @@ class FollowNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
usernameLabel.text = "@\(account.acct)"
|
usernameLabel.text = "@\(account.acct)"
|
||||||
avatarImageView.image = nil
|
avatarImageView.image = nil
|
||||||
avatarURL = account.avatar
|
avatarURL = account.avatar
|
||||||
ImageCache.avatars.get(account.avatar) { (image) in
|
ImageCache.avatars.get(account.avatar) { (data) in
|
||||||
|
guard let data = data else { return }
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.avatarImageView.image = image
|
self.avatarImageView.image = UIImage(data: data)
|
||||||
self.avatarURL = nil
|
self.avatarURL = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,15 +61,17 @@ class ProfileHeaderTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
|
|
||||||
avatarImageView.image = nil
|
avatarImageView.image = nil
|
||||||
avatarURL = account.avatar
|
avatarURL = account.avatar
|
||||||
ImageCache.avatars.get(account.avatar) { (image) in
|
ImageCache.avatars.get(account.avatar) { (data) in
|
||||||
|
guard let data = data else { return }
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.avatarImageView.image = image
|
self.avatarImageView.image = UIImage(data: data)
|
||||||
self.avatarURL = nil
|
self.avatarURL = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImageCache.headers.get(account.header) { (image) in
|
ImageCache.headers.get(account.header) { (data) in
|
||||||
|
guard let data = data else { return }
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.headerImageView.image = image
|
self.headerImageView.image = UIImage(data: data)
|
||||||
self.headerURL = nil
|
self.headerURL = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,9 +80,10 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive
|
||||||
usernameLabel.text = "@\(account.acct)"
|
usernameLabel.text = "@\(account.acct)"
|
||||||
avatarImageView.image = nil
|
avatarImageView.image = nil
|
||||||
avatarURL = account.avatar
|
avatarURL = account.avatar
|
||||||
ImageCache.avatars.get(account.avatar) { (image) in
|
ImageCache.avatars.get(account.avatar) { (data) in
|
||||||
|
guard let data = data else { return }
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.avatarImageView.image = image
|
self.avatarImageView.image = UIImage(data: data)
|
||||||
self.avatarURL = nil
|
self.avatarURL = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,7 +222,11 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive
|
||||||
|
|
||||||
extension ConversationMainStatusTableViewCell: AttachmentViewDelegate {
|
extension ConversationMainStatusTableViewCell: AttachmentViewDelegate {
|
||||||
func showLargeAttachment(for attachmentView: AttachmentView) {
|
func showLargeAttachment(for attachmentView: AttachmentView) {
|
||||||
delegate?.showLargeImage(attachmentView.image!, description: attachmentView.attachment.description, animatingFrom: attachmentView)
|
if let gifData = attachmentView.gifData {
|
||||||
|
delegate?.showLargeImage(gifData: gifData, description: attachmentView.attachment.description, animatingFrom: attachmentView)
|
||||||
|
} else {
|
||||||
|
delegate?.showLargeImage(attachmentView.image!, description: attachmentView.attachment.description, animatingFrom: attachmentView)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,9 +97,10 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
usernameLabel.text = "@\(account.acct)"
|
usernameLabel.text = "@\(account.acct)"
|
||||||
avatarImageView.image = nil
|
avatarImageView.image = nil
|
||||||
avatarURL = account.avatar
|
avatarURL = account.avatar
|
||||||
ImageCache.avatars.get(account.avatar) { (image) in
|
ImageCache.avatars.get(account.avatar) { (data) in
|
||||||
|
guard let data = data else { return }
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.avatarImageView.image = image
|
self.avatarImageView.image = UIImage(data: data)
|
||||||
self.avatarURL = nil
|
self.avatarURL = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,7 +353,11 @@ extension StatusTableViewCell: TableViewSwipeActionProvider {
|
||||||
|
|
||||||
extension StatusTableViewCell: AttachmentViewDelegate {
|
extension StatusTableViewCell: AttachmentViewDelegate {
|
||||||
func showLargeAttachment(for attachmentView: AttachmentView) {
|
func showLargeAttachment(for attachmentView: AttachmentView) {
|
||||||
delegate?.showLargeImage(attachmentView.image!, description: attachmentView.attachment.description, animatingFrom: attachmentView)
|
if let gifData = attachmentView.gifData {
|
||||||
|
delegate?.showLargeImage(gifData: gifData, description: attachmentView.attachment.description, animatingFrom: attachmentView)
|
||||||
|
} else {
|
||||||
|
delegate?.showLargeImage(attachmentView.image!, description: attachmentView.attachment.description, animatingFrom: attachmentView)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue