parent
2dfd7aa0e9
commit
4f77a2a504
|
@ -25,8 +25,10 @@ class GifPickerCell: UICollectionViewCell {
|
|||
}
|
||||
}
|
||||
|
||||
var assetRequest: GiphyAssetRequest?
|
||||
var asset: GiphyAsset?
|
||||
var stillAssetRequest: GiphyAssetRequest?
|
||||
var stillAsset: GiphyAsset?
|
||||
var fullAssetRequest: GiphyAssetRequest?
|
||||
var fullAsset: GiphyAsset?
|
||||
var imageView: YYAnimatedImageView?
|
||||
|
||||
// MARK: Initializers
|
||||
|
@ -46,16 +48,29 @@ class GifPickerCell: UICollectionViewCell {
|
|||
|
||||
imageInfo = nil
|
||||
shouldLoad = false
|
||||
asset = nil
|
||||
assetRequest?.cancel()
|
||||
assetRequest = nil
|
||||
stillAsset = nil
|
||||
stillAssetRequest?.cancel()
|
||||
stillAssetRequest = nil
|
||||
fullAsset = nil
|
||||
fullAssetRequest?.cancel()
|
||||
fullAssetRequest = nil
|
||||
imageView?.removeFromSuperview()
|
||||
imageView = nil
|
||||
}
|
||||
|
||||
private func clearStillAssetRequest() {
|
||||
stillAssetRequest?.cancel()
|
||||
stillAssetRequest = nil
|
||||
}
|
||||
|
||||
private func clearFullAssetRequest() {
|
||||
fullAssetRequest?.cancel()
|
||||
fullAssetRequest = nil
|
||||
}
|
||||
|
||||
private func clearAssetRequest() {
|
||||
assetRequest?.cancel()
|
||||
assetRequest = nil
|
||||
clearStillAssetRequest()
|
||||
clearFullAssetRequest()
|
||||
}
|
||||
|
||||
private func ensureLoad() {
|
||||
|
@ -67,31 +82,60 @@ class GifPickerCell: UICollectionViewCell {
|
|||
clearAssetRequest()
|
||||
return
|
||||
}
|
||||
guard self.assetRequest == nil else {
|
||||
guard self.fullAsset == nil else {
|
||||
return
|
||||
}
|
||||
guard let rendition = imageInfo.pickGifRendition() else {
|
||||
Logger.warn("\(TAG) could not pick rendition")
|
||||
guard let fullRendition = imageInfo.pickGifRendition() else {
|
||||
Logger.warn("\(TAG) could not pick gif rendition: \(imageInfo.giphyId)")
|
||||
// imageInfo.log()
|
||||
clearAssetRequest()
|
||||
return
|
||||
}
|
||||
// Logger.verbose("\(TAG) picked rendition: \(rendition.name)")
|
||||
guard let stillRendition = imageInfo.pickStillRendition() else {
|
||||
Logger.warn("\(TAG) could not pick still rendition: \(imageInfo.giphyId)")
|
||||
// imageInfo.log()
|
||||
clearAssetRequest()
|
||||
return
|
||||
}
|
||||
// Logger.verbose("picked full: \(fullRendition.name)")
|
||||
// Logger.verbose("picked still: \(stillRendition.name)")
|
||||
|
||||
assetRequest = GifDownloader.sharedInstance.downloadAssetAsync(rendition:rendition,
|
||||
success: { [weak self] asset in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.clearAssetRequest()
|
||||
strongSelf.asset = asset
|
||||
strongSelf.tryToDisplayAsset()
|
||||
},
|
||||
failure: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.clearAssetRequest()
|
||||
})
|
||||
if stillAsset == nil && fullAsset == nil && stillAssetRequest == nil {
|
||||
stillAssetRequest = GifDownloader.sharedInstance.downloadAssetAsync(rendition:stillRendition,
|
||||
priority:.high,
|
||||
success: { [weak self] asset in
|
||||
// Logger.verbose("downloaded still")
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.clearStillAssetRequest()
|
||||
strongSelf.stillAsset = asset
|
||||
strongSelf.tryToDisplayAsset()
|
||||
},
|
||||
failure: { [weak self] in
|
||||
// Logger.verbose("failed to download still")
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.clearStillAssetRequest()
|
||||
})
|
||||
}
|
||||
if fullAsset == nil && fullAssetRequest == nil {
|
||||
fullAssetRequest = GifDownloader.sharedInstance.downloadAssetAsync(rendition:fullRendition,
|
||||
priority:.low,
|
||||
success: { [weak self] asset in
|
||||
// Logger.verbose("downloaded full")
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.clearAssetRequest()
|
||||
strongSelf.fullAsset = asset
|
||||
strongSelf.tryToDisplayAsset()
|
||||
},
|
||||
failure: { [weak self] in
|
||||
// Logger.verbose("failed to download full")
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.clearAssetRequest()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func tryToDisplayAsset() {
|
||||
guard let asset = asset else {
|
||||
guard let asset = pickBestAsset() else {
|
||||
owsFail("\(TAG) missing asset.")
|
||||
return
|
||||
}
|
||||
|
@ -99,11 +143,27 @@ class GifPickerCell: UICollectionViewCell {
|
|||
owsFail("\(TAG) could not load asset.")
|
||||
return
|
||||
}
|
||||
let imageView = YYAnimatedImageView()
|
||||
self.imageView = imageView
|
||||
if imageView == nil {
|
||||
let imageView = YYAnimatedImageView()
|
||||
self.imageView = imageView
|
||||
self.contentView.addSubview(imageView)
|
||||
imageView.autoPinWidthToSuperview()
|
||||
imageView.autoPinHeightToSuperview()
|
||||
}
|
||||
guard let imageView = imageView else {
|
||||
owsFail("\(TAG) missing imageview.")
|
||||
return
|
||||
}
|
||||
imageView.image = image
|
||||
self.contentView.addSubview(imageView)
|
||||
imageView.autoPinWidthToSuperview()
|
||||
imageView.autoPinHeightToSuperview()
|
||||
}
|
||||
|
||||
private func pickBestAsset() -> GiphyAsset? {
|
||||
if let fullAsset = fullAsset {
|
||||
return fullAsset
|
||||
}
|
||||
if let stillAsset = stillAsset {
|
||||
return stillAsset
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -164,7 +164,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect
|
|||
owsFail("\(TAG) unexpected cell.")
|
||||
return
|
||||
}
|
||||
guard let asset = cell.asset else {
|
||||
guard let asset = cell.fullAsset else {
|
||||
Logger.info("\(TAG) unload cell selected.")
|
||||
return
|
||||
}
|
||||
|
|
|
@ -5,20 +5,27 @@
|
|||
import Foundation
|
||||
import ObjectiveC
|
||||
|
||||
enum GiphyRequestPriority {
|
||||
case low, high
|
||||
}
|
||||
|
||||
@objc class GiphyAssetRequest: NSObject {
|
||||
static let TAG = "[GiphyAssetRequest]"
|
||||
|
||||
let rendition: GiphyRendition
|
||||
let priority: GiphyRequestPriority
|
||||
let success: ((GiphyAsset) -> Void)
|
||||
let failure: (() -> Void)
|
||||
var wasCancelled = false
|
||||
var assetFilePath: String?
|
||||
|
||||
init(rendition: GiphyRendition,
|
||||
priority: GiphyRequestPriority,
|
||||
success:@escaping ((GiphyAsset) -> Void),
|
||||
failure:@escaping (() -> Void)
|
||||
) {
|
||||
self.rendition = rendition
|
||||
self.priority = priority
|
||||
self.success = success
|
||||
self.failure = failure
|
||||
}
|
||||
|
@ -121,6 +128,7 @@ extension URLSessionTask {
|
|||
// The success and failure handlers are always called on main queue.
|
||||
// The success and failure handlers may be called synchronously on cache hit.
|
||||
public func downloadAssetAsync(rendition: GiphyRendition,
|
||||
priority: GiphyRequestPriority,
|
||||
success:@escaping ((GiphyAsset) -> Void),
|
||||
failure:@escaping (() -> Void)) -> GiphyAssetRequest? {
|
||||
AssertIsOnMainThread()
|
||||
|
@ -132,6 +140,7 @@ extension URLSessionTask {
|
|||
|
||||
var hasRequestCompleted = false
|
||||
let assetRequest = GiphyAssetRequest(rendition:rendition,
|
||||
priority:priority,
|
||||
success : { asset in
|
||||
DispatchQueue.main.async {
|
||||
// Ensure we call success or failure exactly once.
|
||||
|
@ -171,14 +180,9 @@ extension URLSessionTask {
|
|||
guard !self.isDownloading else {
|
||||
return
|
||||
}
|
||||
guard self.assetRequestQueue.count > 0 else {
|
||||
guard let assetRequest = self.popNextAssetRequest() else {
|
||||
return
|
||||
}
|
||||
guard let assetRequest = self.assetRequestQueue.first else {
|
||||
owsFail("\(GiphyAsset.TAG) could not pop asset requests")
|
||||
return
|
||||
}
|
||||
self.assetRequestQueue.removeFirst()
|
||||
guard !assetRequest.wasCancelled else {
|
||||
DispatchQueue.main.async {
|
||||
self.downloadIfNecessary()
|
||||
|
@ -188,7 +192,7 @@ extension URLSessionTask {
|
|||
self.isDownloading = true
|
||||
|
||||
if let asset = self.assetMap[assetRequest.rendition.url] {
|
||||
// Deferred cache hit, avoids re-downloading assets already in the
|
||||
// Deferred cache hit, avoids re-downloading assets already in the
|
||||
// asset cache.
|
||||
assetRequest.success(asset)
|
||||
return
|
||||
|
@ -206,6 +210,22 @@ extension URLSessionTask {
|
|||
}
|
||||
}
|
||||
|
||||
private func popNextAssetRequest() -> GiphyAssetRequest? {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
// var result : GiphyAssetRequest?
|
||||
for priority in [GiphyRequestPriority.high, GiphyRequestPriority.low] {
|
||||
for (assetRequestIndex, assetRequest) in assetRequestQueue.enumerated() {
|
||||
if assetRequest.priority == priority {
|
||||
assetRequestQueue.remove(at:assetRequestIndex)
|
||||
return assetRequest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: URLSessionDataDelegate
|
||||
|
||||
@nonobjc
|
||||
|
|
|
@ -7,7 +7,7 @@ import ObjectiveC
|
|||
|
||||
// There's no UTI type for webp!
|
||||
enum GiphyFormat {
|
||||
case gif, mp4
|
||||
case gif, mp4, jpg
|
||||
}
|
||||
|
||||
@objc class GiphyRendition: NSObject {
|
||||
|
@ -38,6 +38,8 @@ enum GiphyFormat {
|
|||
return "gif"
|
||||
case .mp4:
|
||||
return "mp4"
|
||||
case .jpg:
|
||||
return "jpg"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,8 +49,14 @@ enum GiphyFormat {
|
|||
return kUTTypeGIF as String
|
||||
case .mp4:
|
||||
return kUTTypeMPEG4 as String
|
||||
case .jpg:
|
||||
return kUTTypeJPEG as String
|
||||
}
|
||||
}
|
||||
|
||||
public func log() {
|
||||
Logger.verbose("\t \(format), \(name), \(width), \(height), \(fileSize)")
|
||||
}
|
||||
}
|
||||
|
||||
@objc class GiphyImageInfo: NSObject {
|
||||
|
@ -69,33 +77,67 @@ enum GiphyFormat {
|
|||
let kMinDimension = UInt(101)
|
||||
let kMaxFileSize = UInt(3 * 1024 * 1024)
|
||||
|
||||
public func log() {
|
||||
Logger.verbose("giphyId: \(giphyId), \(renditions.count)")
|
||||
for rendition in renditions {
|
||||
rendition.log()
|
||||
}
|
||||
}
|
||||
|
||||
public func pickStillRendition() -> GiphyRendition? {
|
||||
return pickRendition(isStill:true)
|
||||
}
|
||||
|
||||
public func pickGifRendition() -> GiphyRendition? {
|
||||
return pickRendition(isStill:false)
|
||||
}
|
||||
|
||||
private func pickRendition(isStill: Bool) -> GiphyRendition? {
|
||||
var bestRendition: GiphyRendition?
|
||||
|
||||
for rendition in renditions {
|
||||
guard rendition.format == .gif else {
|
||||
continue
|
||||
}
|
||||
guard !rendition.name.hasSuffix("_still")
|
||||
else {
|
||||
if isStill {
|
||||
guard [.gif, .jpg].contains(rendition.format) else {
|
||||
continue
|
||||
}
|
||||
guard !rendition.name.hasSuffix("_downsampled")
|
||||
else {
|
||||
continue
|
||||
}
|
||||
guard rendition.width >= kMinDimension &&
|
||||
rendition.width <= kMaxDimension &&
|
||||
rendition.height >= kMinDimension &&
|
||||
rendition.height <= kMaxDimension &&
|
||||
rendition.fileSize <= kMaxFileSize
|
||||
else {
|
||||
}
|
||||
guard rendition.name.hasSuffix("_still") else {
|
||||
continue
|
||||
}
|
||||
guard rendition.width >= kMinDimension &&
|
||||
rendition.height >= kMinDimension &&
|
||||
rendition.fileSize <= kMaxFileSize
|
||||
else {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
guard rendition.format == .gif else {
|
||||
continue
|
||||
}
|
||||
guard !rendition.name.hasSuffix("_still") else {
|
||||
continue
|
||||
}
|
||||
guard !rendition.name.hasSuffix("_downsampled") else {
|
||||
continue
|
||||
}
|
||||
guard rendition.width >= kMinDimension &&
|
||||
rendition.width <= kMaxDimension &&
|
||||
rendition.height >= kMinDimension &&
|
||||
rendition.height <= kMaxDimension &&
|
||||
rendition.fileSize <= kMaxFileSize
|
||||
else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if let currentBestRendition = bestRendition {
|
||||
if rendition.width > currentBestRendition.width {
|
||||
bestRendition = rendition
|
||||
if isStill {
|
||||
if rendition.width < currentBestRendition.width {
|
||||
bestRendition = rendition
|
||||
}
|
||||
} else {
|
||||
if rendition.width > currentBestRendition.width {
|
||||
bestRendition = rendition
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bestRendition = rendition
|
||||
|
@ -191,6 +233,8 @@ enum GiphyFormat {
|
|||
// MARK: Parse API Responses
|
||||
|
||||
private func parseGiphyImages(responseJson:Any?) -> [GiphyImageInfo]? {
|
||||
// Logger.verbose("\(responseJson)")
|
||||
|
||||
guard let responseJson = responseJson else {
|
||||
Logger.error("\(GifManager.TAG) Missing response.")
|
||||
return nil
|
||||
|
@ -292,14 +336,27 @@ enum GiphyFormat {
|
|||
Logger.warn("\(GifManager.TAG) Rendition url missing file extension.")
|
||||
return nil
|
||||
}
|
||||
guard fileExtension.lowercased() == "gif" else {
|
||||
// Logger.verbose("\(GifManager.TAG) Rendition has invalid type: \(fileExtension).")
|
||||
var format = GiphyFormat.gif
|
||||
if fileExtension.lowercased() == "gif" {
|
||||
format = .gif
|
||||
} else if fileExtension.lowercased() == "jpg" {
|
||||
format = .jpg
|
||||
} else if fileExtension.lowercased() == "mp4" {
|
||||
format = .mp4
|
||||
} else if fileExtension.lowercased() == "webp" {
|
||||
return nil
|
||||
} else {
|
||||
Logger.warn("\(GifManager.TAG) Invalid file extension: \(fileExtension).")
|
||||
return nil
|
||||
}
|
||||
// guard fileExtension.lowercased() == "gif" else {
|
||||
//// Logger.verbose("\(GifManager.TAG) Rendition has invalid type: \(fileExtension).")
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// Logger.debug("\(GifManager.TAG) Rendition successfully parsed.")
|
||||
return GiphyRendition(
|
||||
format : .gif,
|
||||
format : format,
|
||||
name : renditionName,
|
||||
width : width,
|
||||
height : height,
|
||||
|
|
Loading…
Reference in New Issue