Sketch out the GIF picker.

// FREEBIE
This commit is contained in:
Matthew Chen 2017-09-28 23:30:33 -04:00
parent ee9101eb16
commit 4242001828
3 changed files with 355 additions and 598 deletions

View File

@ -9,10 +9,27 @@ class GifPickerCell: UICollectionViewCell {
// MARK: Properties
var imageInfo: GiphyImageInfo?
var imageInfo: GiphyImageInfo? {
didSet {
AssertIsOnMainThread()
ensureLoad()
}
}
var shouldLoad = false {
didSet {
AssertIsOnMainThread()
ensureLoad()
}
}
var assetRequest: GiphyAssetRequest?
var asset: GiphyAsset?
// MARK: Initializers
// // MARK: Initializers
//
@available(*, unavailable, message:"use other constructor instead.")
required init?(coder aDecoder: NSCoder) {
// self.searchBar = UISearchBar()
@ -39,594 +56,52 @@ class GifPickerCell: UICollectionViewCell {
override func prepareForReuse() {
super.prepareForReuse()
imageInfo = nil
shouldLoad = false
asset = nil
assetRequest?.cancel()
assetRequest = nil
// TODO:
self.backgroundColor = UIColor.red
}
private func clearAssetRequest() {
assetRequest?.cancel()
assetRequest = nil
}
private func ensureLoad() {
guard shouldLoad else {
clearAssetRequest()
return
}
guard let imageInfo = imageInfo else {
clearAssetRequest()
return
}
guard self.assetRequest == nil else {
return
}
guard let rendition = imageInfo.pickGifRendition() else {
Logger.warn("\(TAG) could not pick rendition")
clearAssetRequest()
return
}
Logger.verbose("\(TAG) picked rendition: \(rendition.name)")
assetRequest = GifManager.sharedInstance.downloadAssetAsync(rendition:rendition,
success: { [weak self] asset in
guard let strongSelf = self else { return }
strongSelf.clearAssetRequest()
strongSelf.asset = asset
// TODO:
strongSelf.backgroundColor = UIColor.blue
},
failure: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.clearAssetRequest()
})
}
//
// // MARK: View Lifecycle
//
// override func viewDidLoad() {
// super.viewDidLoad()
//
// view.backgroundColor = UIColor.black
//
// self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem:.stop,
// target:self,
// action:#selector(donePressed))
// self.navigationItem.title = NSLocalizedString("GIF_PICKER_VIEW_TITLE",
// comment: "Title for the 'gif picker' dialog.")
//
// createViews()
// }
//
// // MARK: Views
//
// private func createViews() {
//
// view.backgroundColor = UIColor.black
//
// // Search
//// searchBar.searchBarStyle = .minimal
// searchBar.searchBarStyle = .default
// searchBar.delegate = self
// searchBar.placeholder = NSLocalizedString("GIF_VIEW_SEARCH_PLACEHOLDER_TEXT",
// comment:"Placeholder text for the search field in gif view")
//// searchBar.backgroundColor = UIColor(white:0.6, alpha:1.0)
//// searchBar.backgroundColor = UIColor.white
//// searchBar.backgroundColor = UIColor.black
//// searchBar.barTintColor = UIColor.red
// searchBar.isTranslucent = false
//// searchBar.backgroundColor = UIColor.white
// searchBar.backgroundImage = UIImage(color:UIColor.clear)
// searchBar.barTintColor = UIColor.black
// searchBar.tintColor = UIColor.white
// self.view.addSubview(searchBar)
// searchBar.autoPinWidthToSuperview()
// searchBar.autoPin(toTopLayoutGuideOf: self, withInset:0)
// // [searchBar sizeToFit];
//
// self.collectionView.delegate = self
// self.collectionView.dataSource = self
// self.collectionView.backgroundColor = UIColor.black
// self.view.addSubview(self.collectionView)
// self.collectionView.autoPinWidthToSuperview()
// self.collectionView.autoPinEdge(.top, to:.bottom, of:searchBar)
// self.collectionView.autoPin(toBottomLayoutGuideOf: self, withInset:0)
//
// let logoImage = UIImage(named:"giphy_logo")
// let logoImageView = UIImageView(image:logoImage)
// self.logoImageView = logoImageView
// self.view.addSubview(logoImageView)
// logoImageView.autoCenterInSuperview()
//
// self.updateContents()
// // [self updateTableContents];
// }
//
// private func setContentVisible(_ isVisible:Bool) {
// self.collectionView.isHidden = !isVisible
// if let logoImageView = self.logoImageView {
// logoImageView.isHidden = isVisible
// }
// }
//
// private func updateContents() {
// if imageInfos.count < 1 {
// setContentVisible(false)
// } else {
// setContentVisible(true)
// }
//
// self.collectionView.collectionViewLayout.invalidateLayout()
// self.collectionView.reloadData()
// }
//
// // override func viewDidLoad() {
// // super.viewDidLoad()
// //
// // view.backgroundColor = UIColor.white
// //
// // self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem:.stop,
// // target:self,
// // action:#selector(donePressed))
// // self.navigationItem.title = dialogTitle()
// //
// // createViews()
// // }
// //
// // private func dialogTitle() -> String {
// // guard let filename = formattedFileName() else {
// // return NSLocalizedString("ATTACHMENT_APPROVAL_DIALOG_TITLE",
// // comment: "Title for the 'attachment approval' dialog.")
// // }
// // return filename
// // }
// //
// // override func viewWillAppear(_ animated: Bool) {
// // super.viewWillAppear(animated)
// //
// // ViewControllerUtils.setAudioIgnoresHardwareMuteSwitch(true)
// // }
// //
// // override func viewWillDisappear(_ animated: Bool) {
// // super.viewWillDisappear(animated)
// //
// // ViewControllerUtils.setAudioIgnoresHardwareMuteSwitch(false)
// // }
// //
// // // MARK: - Create Views
// //
// // private func createViews() {
// // let previewTopMargin: CGFloat = 30
// // let previewHMargin: CGFloat = 20
// //
// // let attachmentPreviewView = UIView()
// // self.view.addSubview(attachmentPreviewView)
// // attachmentPreviewView.autoPinWidthToSuperview(withMargin:previewHMargin)
// // attachmentPreviewView.autoPin(toTopLayoutGuideOf: self, withInset:previewTopMargin)
// //
// // createButtonRow(attachmentPreviewView:attachmentPreviewView)
// //
// // if attachment.isAnimatedImage {
// // createAnimatedPreview(attachmentPreviewView:attachmentPreviewView)
// // } else if attachment.isImage {
// // createImagePreview(attachmentPreviewView:attachmentPreviewView)
// // } else if attachment.isVideo {
// // createVideoPreview(attachmentPreviewView:attachmentPreviewView)
// // } else if attachment.isAudio {
// // createAudioPreview(attachmentPreviewView:attachmentPreviewView)
// // } else {
// // createGenericPreview(attachmentPreviewView:attachmentPreviewView)
// // }
// // }
// //
// // private func wrapViewsInVerticalStack(subviews: [UIView]) -> UIView {
// // assert(subviews.count > 0)
// //
// // let stackView = UIView()
// //
// // var lastView: UIView?
// // for subview in subviews {
// //
// // stackView.addSubview(subview)
// // subview.autoHCenterInSuperview()
// //
// // if lastView == nil {
// // subview.autoPinEdge(toSuperviewEdge:.top)
// // } else {
// // subview.autoPinEdge(.top, to:.bottom, of:lastView!, withOffset:10)
// // }
// //
// // lastView = subview
// // }
// //
// // lastView?.autoPinEdge(toSuperviewEdge:.bottom)
// //
// // return stackView
// // }
// //
// // private func createAudioPreview(attachmentPreviewView: UIView) {
// // guard let dataUrl = attachment.dataUrl else {
// // createGenericPreview(attachmentPreviewView:attachmentPreviewView)
// // return
// // }
// //
// // audioPlayer = OWSAudioAttachmentPlayer(mediaUrl: dataUrl, delegate: self)
// //
// // var subviews = [UIView]()
// //
// // let audioPlayButton = UIButton()
// // self.audioPlayButton = audioPlayButton
// // setAudioIconToPlay()
// // audioPlayButton.imageView?.layer.minificationFilter = kCAFilterTrilinear
// // audioPlayButton.imageView?.layer.magnificationFilter = kCAFilterTrilinear
// // audioPlayButton.addTarget(self, action:#selector(audioPlayButtonPressed), for:.touchUpInside)
// // let buttonSize = createHeroViewSize()
// // audioPlayButton.autoSetDimension(.width, toSize:buttonSize)
// // audioPlayButton.autoSetDimension(.height, toSize:buttonSize)
// // subviews.append(audioPlayButton)
// //
// // let fileNameLabel = createFileNameLabel()
// // if let fileNameLabel = fileNameLabel {
// // subviews.append(fileNameLabel)
// // }
// //
// // let fileSizeLabel = createFileSizeLabel()
// // subviews.append(fileSizeLabel)
// //
// // let audioStatusLabel = createAudioStatusLabel()
// // self.audioStatusLabel = audioStatusLabel
// // updateAudioStatusLabel()
// // subviews.append(audioStatusLabel)
// //
// // let stackView = wrapViewsInVerticalStack(subviews:subviews)
// // attachmentPreviewView.addSubview(stackView)
// // fileNameLabel?.autoPinWidthToSuperview(withMargin: 32)
// // stackView.autoPinWidthToSuperview()
// // stackView.autoVCenterInSuperview()
// // }
// //
// // private func createAnimatedPreview(attachmentPreviewView: UIView) {
// // guard attachment.isValidImage else {
// // return
// // }
// // let data = attachment.data
// // // Use Flipboard FLAnimatedImage library to display gifs
// // guard let animatedImage = FLAnimatedImage(gifData:data) else {
// // createGenericPreview(attachmentPreviewView:attachmentPreviewView)
// // return
// // }
// // let animatedImageView = FLAnimatedImageView()
// // animatedImageView.animatedImage = animatedImage
// // animatedImageView.contentMode = .scaleAspectFit
// // attachmentPreviewView.addSubview(animatedImageView)
// // animatedImageView.autoPinWidthToSuperview()
// // animatedImageView.autoPinHeightToSuperview()
// // }
// //
// // private func createImagePreview(attachmentPreviewView: UIView) {
// // var image = attachment.image
// // if image == nil {
// // image = UIImage(data:attachment.data)
// // }
// // guard image != nil else {
// // createGenericPreview(attachmentPreviewView:attachmentPreviewView)
// // return
// // }
// //
// // let imageView = UIImageView(image:image)
// // imageView.layer.minificationFilter = kCAFilterTrilinear
// // imageView.layer.magnificationFilter = kCAFilterTrilinear
// // imageView.contentMode = .scaleAspectFit
// // attachmentPreviewView.addSubview(imageView)
// // imageView.autoPinWidthToSuperview()
// // imageView.autoPinHeightToSuperview()
// // }
// //
// // private func createVideoPreview(attachmentPreviewView: UIView) {
// // guard let dataUrl = attachment.dataUrl else {
// // createGenericPreview(attachmentPreviewView:attachmentPreviewView)
// // return
// // }
// // guard let videoPlayer = MPMoviePlayerController(contentURL:dataUrl) else {
// // createGenericPreview(attachmentPreviewView:attachmentPreviewView)
// // return
// // }
// // videoPlayer.prepareToPlay()
// //
// // videoPlayer.controlStyle = .default
// // videoPlayer.shouldAutoplay = false
// //
// // attachmentPreviewView.addSubview(videoPlayer.view)
// // self.videoPlayer = videoPlayer
// // videoPlayer.view.autoPinWidthToSuperview()
// // videoPlayer.view.autoPinHeightToSuperview()
// // }
// //
// // private func createGenericPreview(attachmentPreviewView: UIView) {
// // var subviews = [UIView]()
// //
// // let imageView = createHeroImageView(imageName: "file-thin-black-filled-large")
// // subviews.append(imageView)
// //
// // let fileNameLabel = createFileNameLabel()
// // if let fileNameLabel = fileNameLabel {
// // subviews.append(fileNameLabel)
// // }
// //
// // let fileSizeLabel = createFileSizeLabel()
// // subviews.append(fileSizeLabel)
// //
// // let stackView = wrapViewsInVerticalStack(subviews:subviews)
// // attachmentPreviewView.addSubview(stackView)
// // fileNameLabel?.autoPinWidthToSuperview(withMargin: 32)
// // stackView.autoPinWidthToSuperview()
// // stackView.autoVCenterInSuperview()
// // }
// //
// // private func createHeroViewSize() -> CGFloat {
// // return ScaleFromIPhone5To7Plus(175, 225)
// // }
// //
// // private func createHeroImageView(imageName: String) -> UIView {
// // let imageSize = createHeroViewSize()
// // let image = UIImage(named:imageName)
// // assert(image != nil)
// // let imageView = UIImageView(image:image)
// // imageView.layer.minificationFilter = kCAFilterTrilinear
// // imageView.layer.magnificationFilter = kCAFilterTrilinear
// // imageView.layer.shadowColor = UIColor.black.cgColor
// // let shadowScaling = 5.0
// // imageView.layer.shadowRadius = CGFloat(2.0 * shadowScaling)
// // imageView.layer.shadowOpacity = 0.25
// // imageView.layer.shadowOffset = CGSize(width: 0.75 * shadowScaling, height: 0.75 * shadowScaling)
// // imageView.autoSetDimension(.width, toSize:imageSize)
// // imageView.autoSetDimension(.height, toSize:imageSize)
// //
// // return imageView
// // }
// //
// // private func labelFont() -> UIFont {
// // return UIFont.ows_regularFont(withSize:ScaleFromIPhone5To7Plus(18, 24))
// // }
// //
// // private func formattedFileExtension() -> String? {
// // guard let fileExtension = attachment.fileExtension else {
// // return nil
// // }
// //
// // return String(format:NSLocalizedString("ATTACHMENT_APPROVAL_FILE_EXTENSION_FORMAT",
// // comment: "Format string for file extension label in call interstitial view"),
// // fileExtension.uppercased())
// // }
// //
// // private func formattedFileName() -> String? {
// // guard let sourceFilename = attachment.sourceFilename else {
// // return nil
// // }
// // let filename = sourceFilename.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
// // guard filename.characters.count > 0 else {
// // return nil
// // }
// // return filename
// // }
// //
// // private func createFileNameLabel() -> UIView? {
// // let filename = formattedFileName() ?? formattedFileExtension()
// //
// // guard filename != nil else {
// // return nil
// // }
// //
// // let label = UILabel()
// // label.text = filename
// // label.textColor = UIColor.ows_materialBlue()
// // label.font = labelFont()
// // label.textAlignment = .center
// // label.lineBreakMode = .byTruncatingMiddle
// // return label
// // }
// //
// // private func createFileSizeLabel() -> UIView {
// // let label = UILabel()
// // let fileSize = attachment.dataLength
// // label.text = String(format:NSLocalizedString("ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT",
// // comment: "Format string for file size label in call interstitial view. Embeds: {{file size as 'N mb' or 'N kb'}}."),
// // ViewControllerUtils.formatFileSize(UInt(fileSize)))
// //
// // label.textColor = UIColor.ows_materialBlue()
// // label.font = labelFont()
// // label.textAlignment = .center
// //
// // return label
// // }
// //
// // private func createAudioStatusLabel() -> UILabel {
// // let label = UILabel()
// // label.textColor = UIColor.ows_materialBlue()
// // label.font = labelFont()
// // label.textAlignment = .center
// //
// // return label
// // }
// //
// // private func createButtonRow(attachmentPreviewView: UIView) {
// // let buttonTopMargin = ScaleFromIPhone5To7Plus(30, 40)
// // let buttonBottomMargin = ScaleFromIPhone5To7Plus(25, 40)
// // let buttonHSpacing = ScaleFromIPhone5To7Plus(20, 30)
// //
// // let buttonRow = UIView()
// // self.view.addSubview(buttonRow)
// // buttonRow.autoPinWidthToSuperview()
// // buttonRow.autoPinEdge(toSuperviewEdge:.bottom, withInset:buttonBottomMargin)
// // buttonRow.autoPinEdge(.top, to:.bottom, of:attachmentPreviewView, withOffset:buttonTopMargin)
// //
// // // We use this invisible subview to ensure that the buttons are centered
// // // horizontally.
// // let buttonSpacer = UIView()
// // buttonRow.addSubview(buttonSpacer)
// // // Vertical positioning of this view doesn't matter.
// // buttonSpacer.autoPinEdge(toSuperviewEdge:.top)
// // buttonSpacer.autoSetDimension(.width, toSize:buttonHSpacing)
// // buttonSpacer.autoHCenterInSuperview()
// //
// // let cancelButton = createButton(title: CommonStrings.cancelButton,
// // color : UIColor.ows_destructiveRed(),
// // action: #selector(cancelPressed))
// // buttonRow.addSubview(cancelButton)
// // cancelButton.autoPinEdge(toSuperviewEdge:.top)
// // cancelButton.autoPinEdge(toSuperviewEdge:.bottom)
// // cancelButton.autoPinEdge(.right, to:.left, of:buttonSpacer)
// //
// // let sendButton = createButton(title: NSLocalizedString("ATTACHMENT_APPROVAL_SEND_BUTTON",
// // comment: "Label for 'send' button in the 'attachment approval' dialog."),
// // color : UIColor(rgbHex:0x2ecc71),
// // action: #selector(sendPressed))
// // buttonRow.addSubview(sendButton)
// // sendButton.autoPinEdge(toSuperviewEdge:.top)
// // sendButton.autoPinEdge(toSuperviewEdge:.bottom)
// // sendButton.autoPinEdge(.left, to:.right, of:buttonSpacer)
// // }
// //
// // private func createButton(title: String, color: UIColor, action: Selector) -> UIView {
// // let buttonWidth = ScaleFromIPhone5To7Plus(110, 140)
// // let buttonHeight = ScaleFromIPhone5To7Plus(35, 45)
// //
// // return OWSFlatButton.button(title:title,
// // titleColor:UIColor.white,
// // backgroundColor:color,
// // width:buttonWidth,
// // height:buttonHeight,
// // target:target,
// // selector:action)
// // }
// //
// // // MARK: - Event Handlers
// //
// // func donePressed(sender: UIButton) {
// // dismiss(animated: true, completion:nil)
// // }
// //
// // func cancelPressed(sender: UIButton) {
// // dismiss(animated: true, completion:nil)
// // }
// //
// // func sendPressed(sender: UIButton) {
// // let successCompletion = self.successCompletion
// // dismiss(animated: true, completion: {
// // successCompletion?()
// // })
// // }
// //
// // func audioPlayButtonPressed(sender: UIButton) {
// // audioPlayer?.togglePlayState()
// // }
// //
// // // MARK: - OWSAudioAttachmentPlayerDelegate
// //
// // public func isAudioPlaying() -> Bool {
// // return isAudioPlayingFlag
// // }
// //
// // public func setIsAudioPlaying(_ isAudioPlaying: Bool) {
// // isAudioPlayingFlag = isAudioPlaying
// //
// // updateAudioStatusLabel()
// // }
// //
// // public func isPaused() -> Bool {
// // return isAudioPaused
// // }
// //
// // public func setIsPaused(_ isPaused: Bool) {
// // isAudioPaused = isPaused
// // }
// //
// // public func setAudioProgress(_ progress: CGFloat, duration: CGFloat) {
// // audioProgressSeconds = progress
// // audioDurationSeconds = duration
// //
// // updateAudioStatusLabel()
// // }
// //
// // private func updateAudioStatusLabel() {
// // guard let audioStatusLabel = self.audioStatusLabel else {
// // owsFail("Missing audio status label")
// // return
// // }
// //
// // if isAudioPlayingFlag && audioProgressSeconds > 0 && audioDurationSeconds > 0 {
// // audioStatusLabel.text = String(format:"%@ / %@",
// // ViewControllerUtils.formatDurationSeconds(Int(round(self.audioProgressSeconds))),
// // ViewControllerUtils.formatDurationSeconds(Int(round(self.audioDurationSeconds))))
// // } else {
// // audioStatusLabel.text = " "
// // }
// // }
// //
// // public func setAudioIconToPlay() {
// // let image = UIImage(named:"audio_play_black_large")?.withRenderingMode(.alwaysTemplate)
// // assert(image != nil)
// // audioPlayButton?.setImage(image, for:.normal)
// // audioPlayButton?.imageView?.tintColor = UIColor.ows_materialBlue()
// // }
// //
// // public func setAudioIconToPause() {
// // let image = UIImage(named:"audio_pause_black_large")?.withRenderingMode(.alwaysTemplate)
// // assert(image != nil)
// // audioPlayButton?.setImage(image, for:.normal)
// // audioPlayButton?.imageView?.tintColor = UIColor.ows_materialBlue()
// // }
//
// // MARK: - UICollectionViewDataSource
//
// override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// return imageInfos.count
// }
//
// // The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
// override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// let cell = self.dequeueReusableCell(withReuseIdentifier identifier: String, for indexPath: IndexPath) -> UICollectionViewCell
// }
//
//
// // MARK: - UICollectionViewDelegate
//
//// @available(iOS 6.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool
////
//// @available(iOS 6.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath)
////
//// @available(iOS 6.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath)
////
//// @available(iOS 6.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool
////
//// @available(iOS 6.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, shouldDeselectItemAt indexPath: IndexPath) -> Bool // called when the user taps on an already-selected item in multi-select mode
////
//// @available(iOS 6.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
////
//// @available(iOS 6.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath)
////
////
//// @available(iOS 8.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
////
//// @available(iOS 8.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath)
////
//// @available(iOS 6.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
////
//// @available(iOS 6.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, didEndDisplayingSupplementaryView view: UICollectionReusableView, forElementOfKind elementKind: String, at indexPath: IndexPath)
////
////
//// // These methods provide support for copy/paste actions on cells.
//// // All three should be implemented if any are.
//// @available(iOS 6.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, shouldShowMenuForItemAt indexPath: IndexPath) -> Bool
////
//// @available(iOS 6.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool
////
//// @available(iOS 6.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?)
////
////
//// // support for custom transition layout
//// @available(iOS 7.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, transitionLayoutForOldLayout fromLayout: UICollectionViewLayout, newLayout toLayout: UICollectionViewLayout) -> UICollectionViewTransitionLayout
////
////
//// // Focus
//// @available(iOS 9.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, canFocusItemAt indexPath: IndexPath) -> Bool
////
//// @available(iOS 9.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, shouldUpdateFocusIn context: UICollectionViewFocusUpdateContext) -> Bool
////
//// @available(iOS 9.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator)
////
//// @available(iOS 9.0, *)
//// optional public func indexPathForPreferredFocusedView(in collectionView: UICollectionView) -> IndexPath?
////
////
//// @available(iOS 9.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath
////
////
//// @available(iOS 9.0, *)
//// optional public func collectionView(_ collectionView: UICollectionView, targetContentOffsetForProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint // customize the content offset to be applied during transition or update animations
////}
//
// // MARK: - Event Handlers
//
// func donePressed(sender: UIButton) {
// dismiss(animated: true, completion:nil)
// }
}

View File

@ -512,11 +512,19 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect
}
public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
guard let cell = cell as? GifPickerCell else {
owsFail("\(TAG) unexpected cell.")
return
}
cell.shouldLoad = true
}
public func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
guard let cell = cell as? GifPickerCell else {
owsFail("\(TAG) unexpected cell.")
return
}
cell.shouldLoad = false
}
// MARK: - Event Handlers

View File

@ -3,6 +3,7 @@
//
import Foundation
import ObjectiveC
enum GiphyFormat {
case gif, webp, mp4
@ -44,9 +45,10 @@ enum GiphyFormat {
self.originalRendition = originalRendition
}
// TODO:
let kMaxDimension = UInt(618)
let kMinDimension = UInt(101)
let kMaxFileSize = SignalAttachment.kMaxFileSizeAnimatedImage
let kMaxFileSize = UInt(3 * 1024 * 1024)
public func pickGifRendition() -> GiphyRendition? {
var bestRendition: GiphyRendition?
@ -81,7 +83,68 @@ enum GiphyFormat {
}
}
@objc class GifManager: NSObject {
@objc class GiphyAssetRequest: NSObject {
static let TAG = "[GiphyAssetRequest]"
let rendition: GiphyRendition
let success: ((GiphyAsset) -> Void)
let failure: (() -> Void)
var wasCancelled = false
var assetFilePath: String?
init(rendition: GiphyRendition,
success:@escaping ((GiphyAsset) -> Void),
failure:@escaping (() -> Void)
) {
self.rendition = rendition
self.success = success
self.failure = failure
}
public func cancel() {
wasCancelled = true
}
}
@objc class GiphyAsset: NSObject {
static let TAG = "[GiphyAsset]"
let rendition: GiphyRendition
let filePath: String
init(rendition: GiphyRendition,
filePath: String) {
self.rendition = rendition
self.filePath = filePath
}
deinit {
let filePathCopy = filePath
DispatchQueue.global().async {
do {
let fileManager = FileManager.default
try fileManager.removeItem(atPath:filePathCopy)
} catch let error as NSError {
owsFail("\(GiphyAsset.TAG) file cleanup failed: \(filePathCopy), \(error)")
}
}
}
}
private var URLSessionTask_GiphyAssetRequest: UInt8 = 0
extension URLSessionTask {
var assetRequest: GiphyAssetRequest {
get {
return objc_getAssociatedObject(self, &URLSessionTask_GiphyAssetRequest) as! GiphyAssetRequest
}
set {
objc_setAssociatedObject(self, &URLSessionTask_GiphyAssetRequest, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
@objc class GifManager: NSObject, URLSessionTaskDelegate, URLSessionDownloadDelegate {
// MARK: - Properties
@ -89,6 +152,8 @@ enum GiphyFormat {
static let sharedInstance = GifManager()
private let operationQueue = OperationQueue()
// Force usage as a singleton
override private init() {}
@ -98,7 +163,7 @@ enum GiphyFormat {
private let kGiphyBaseURL = "https://api.giphy.com/"
private func giphySessionManager() -> AFHTTPSessionManager? {
private func giphyAPISessionManager() -> AFHTTPSessionManager? {
guard let baseUrl = NSURL(string:kGiphyBaseURL) else {
Logger.error("\(GifManager.TAG) Invalid base URL.")
return nil
@ -120,6 +185,33 @@ enum GiphyFormat {
return sessionManager
}
private func giphyDownloadSession() -> URLSession? {
// guard let baseUrl = NSURL(string:kGiphyBaseURL) else {
// Logger.error("\(GifManager.TAG) Invalid base URL.")
// return nil
// }
// TODO: Is this right?
let configuration = URLSessionConfiguration.ephemeral
// TODO: Is this right?
configuration.connectionProxyDictionary = [
kCFProxyHostNameKey as String: "giphy-proxy-production.whispersystems.org",
kCFProxyPortNumberKey as String: "80",
kCFProxyTypeKey as String: kCFProxyTypeHTTPS
]
configuration.urlCache = nil
configuration.requestCachePolicy = .reloadIgnoringCacheData
let session = URLSession(configuration:configuration, delegate:self, delegateQueue:operationQueue)
return session
// NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
//
// let sessionManager = AFHTTPSessionManager(baseURL:baseUrl as URL,
// sessionConfiguration:sessionConf)
// sessionManager.requestSerializer = AFJSONRequestSerializer()
// sessionManager.responseSerializer = AFJSONResponseSerializer()
//
// return sessionManager
}
// TODO:
public func test() {
search(query:"monkey",
@ -128,8 +220,10 @@ enum GiphyFormat {
})
}
// MARK: Search
public func search(query: String, success: @escaping (([GiphyImageInfo]) -> Void), failure: @escaping (() -> Void)) {
guard let sessionManager = giphySessionManager() else {
guard let sessionManager = giphyAPISessionManager() else {
Logger.error("\(GifManager.TAG) Couldn't create session manager.")
failure()
return
@ -169,6 +263,8 @@ enum GiphyFormat {
})
}
// MARK: Parse API Responses
private func parseGiphyImages(responseJson:Any?) -> [GiphyImageInfo]? {
guard let responseJson = responseJson else {
Logger.error("\(GifManager.TAG) Missing response.")
@ -316,4 +412,182 @@ enum GiphyFormat {
}
return parsedValue
}
// MARK: Rendition Download
// private static let serialQueue = DispatchQueue(label: "org.signal.gif.download")
// TODO: Use a proper cache.
// TODO: Write to cache.
private var assetMap = [NSURL: GiphyAsset]()
// TODO: We could use a proper queue.
private var assetRequestQueue = [GiphyAssetRequest]()
private var isDownloading = false
// 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,
success:@escaping ((GiphyAsset) -> Void),
failure:@escaping (() -> Void)) -> GiphyAssetRequest? {
AssertIsOnMainThread()
if let asset = assetMap[rendition.url] {
success(asset)
return nil
}
let assetRequest = GiphyAssetRequest(rendition:rendition,
success : { asset in
DispatchQueue.main.async {
self.assetMap[rendition.url] = asset
success(asset)
self.isDownloading = false
self.downloadIfNecessary()
}
},
failure : {
DispatchQueue.main.async {
failure()
self.isDownloading = false
self.downloadIfNecessary()
}
})
assetRequestQueue.append(assetRequest)
downloadIfNecessary()
return assetRequest
}
private func downloadIfNecessary() {
AssertIsOnMainThread()
DispatchQueue.main.async {
// GifManager.serialQueue.async {
guard !self.isDownloading else {
return
}
guard self.assetRequestQueue.count > 0 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()
}
return
}
self.isDownloading = true
if let asset = self.assetMap[assetRequest.rendition.url] {
// Deferred cache hit, avoids re-downloading assets already in the
// asset cache.
assetRequest.success(asset)
return
}
guard let downloadSession = self.giphyDownloadSession() else {
Logger.error("\(GifManager.TAG) Couldn't create session manager.")
assetRequest.failure()
return
}
let task = downloadSession.downloadTask(with:assetRequest.rendition.url as URL)
task.assetRequest = assetRequest
task.resume()
}
}
// MARK: URLSessionDataDelegate
@nonobjc
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
completionHandler(.allow)
}
// MARK: URLSessionTaskDelegate
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
let assetRequest = task.assetRequest
guard !assetRequest.wasCancelled else {
task.cancel()
return
}
if let error = error {
Logger.error("\(GifManager.TAG) download failed with error: \(error)")
assetRequest.failure()
return
}
guard let httpResponse = task.response as? HTTPURLResponse else {
Logger.error("\(GifManager.TAG) missing or unexpected response: \(task.response)")
assetRequest.failure()
return
}
let statusCode = httpResponse.statusCode
guard statusCode >= 200 && statusCode < 400 else {
Logger.error("\(GifManager.TAG) response has invalid status code: \(statusCode)")
assetRequest.failure()
return
}
guard let assetFilePath = assetRequest.assetFilePath else {
Logger.error("\(GifManager.TAG) task is missing asset file")
assetRequest.failure()
return
}
Logger.verbose("\(GifManager.TAG) download succeeded: \(assetRequest.rendition.url)")
let asset = GiphyAsset(rendition: assetRequest.rendition, filePath : assetFilePath)
assetRequest.success(asset)
}
// MARK: URLSessionDownloadDelegate
private func fileExtension(forFormat format: GiphyFormat) -> String {
switch format {
case .gif:
return "gif"
case .webp:
return "webp"
case .mp4:
return "mp4"
}
}
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
let assetRequest = downloadTask.assetRequest
guard !assetRequest.wasCancelled else {
downloadTask.cancel()
return
}
let dirPath = NSTemporaryDirectory()
let fileExtension = self.fileExtension(forFormat:assetRequest.rendition.format)
let fileName = (NSUUID().uuidString as NSString).appendingPathExtension(fileExtension)!
let filePath = (dirPath as NSString).appendingPathComponent(fileName)
do {
try FileManager.default.moveItem(at: location, to: URL(fileURLWithPath:filePath))
assetRequest.assetFilePath = filePath
} catch let error as NSError {
owsFail("\(GiphyAsset.TAG) file move failed from: \(location), to: \(filePath), \(error)")
}
}
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
let assetRequest = downloadTask.assetRequest
guard !assetRequest.wasCancelled else {
downloadTask.cancel()
return
}
}
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
let assetRequest = downloadTask.assetRequest
guard !assetRequest.wasCancelled else {
downloadTask.cancel()
return
}
}
}