From 3b9726a4fa71b07f34f74847d8c0d4ca9087b760 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 28 Sep 2017 14:09:11 -0400 Subject: [PATCH] Sketch out the GIF picker. // FREEBIE --- Podfile | 1 + Podfile.lock | 7 +- Signal.xcodeproj/project.pbxproj | 4 + .../giphy_logo.imageset/Contents.json | 23 + .../giphy_logo.imageset/giphy_logo@1x.png | Bin 0 -> 6936 bytes .../giphy_logo.imageset/giphy_logo@2x.png | Bin 0 -> 13011 bytes .../giphy_logo.imageset/giphy_logo@3x.png | Bin 0 -> 14981 bytes .../GifPicker/GifPickerCell.swift | 652 ++++++++++++++++++ .../GifPicker/GifPickerViewController.swift | 190 ++--- Signal/src/network/GifManager.swift | 77 ++- 10 files changed, 855 insertions(+), 99 deletions(-) create mode 100644 Signal/Images.xcassets/giphy_logo.imageset/Contents.json create mode 100644 Signal/Images.xcassets/giphy_logo.imageset/giphy_logo@1x.png create mode 100644 Signal/Images.xcassets/giphy_logo.imageset/giphy_logo@2x.png create mode 100644 Signal/Images.xcassets/giphy_logo.imageset/giphy_logo@3x.png create mode 100644 Signal/src/ViewControllers/GifPicker/GifPickerCell.swift diff --git a/Podfile b/Podfile index 070f1f1e2..f1aa82087 100644 --- a/Podfile +++ b/Podfile @@ -12,6 +12,7 @@ target 'Signal' do pod 'Reachability' pod 'SignalServiceKit', path: '.' pod 'SocketRocket', :git => 'https://github.com/facebook/SocketRocket.git' + pod 'YYImage' target 'SignalTests' do inherit! :search_paths end diff --git a/Podfile.lock b/Podfile.lock index b5c15e928..d144bb215 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -110,6 +110,9 @@ PODS: - YapDatabase/SQLCipher/Core - YapDatabase/SQLCipher/Extensions/Views (2.9.3): - YapDatabase/SQLCipher/Core + - YYImage (1.0.4): + - YYImage/Core (= 1.0.4) + - YYImage/Core (1.0.4) DEPENDENCIES: - ATAppUpdater @@ -120,6 +123,7 @@ DEPENDENCIES: - Reachability - SignalServiceKit (from `.`) - SocketRocket (from `https://github.com/facebook/SocketRocket.git`) + - YYImage EXTERNAL SOURCES: AxolotlKit: @@ -170,7 +174,8 @@ SPEC CHECKSUMS: TwistedOakCollapsingFutures: f359b90f203e9ab13dfb92c9ff41842a7fe1cd0c UnionFind: c33be5adb12983981d6e827ea94fc7f9e370f52d YapDatabase: cd911121580ff16675f65ad742a9eb0ab4d9e266 + YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54 -PODFILE CHECKSUM: 2f847bb25e70d1d376f38cf21ae08624fa6ed67d +PODFILE CHECKSUM: 00831faaa7677029090c311c00ceadaa44f65c0f COCOAPODS: 1.2.1 diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 8acc49725..c2ab286c5 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -91,6 +91,7 @@ 34CE88E71F2FB9A10098030F /* ProfileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CE88E61F2FB9A10098030F /* ProfileViewController.m */; }; 34CE88EC1F3237260098030F /* OWSProfileManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CE88EA1F3237260098030F /* OWSProfileManager.m */; }; 34CE88ED1F3237260098030F /* ProfileFetcherJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34CE88EB1F3237260098030F /* ProfileFetcherJob.swift */; }; + 34D1F0501F7D45A60066283D /* GifPickerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F04F1F7D45A60066283D /* GifPickerCell.swift */; }; 34D5CC961EA6AFAD005515DB /* OWSContactsSyncing.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CC951EA6AFAD005515DB /* OWSContactsSyncing.m */; }; 34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */; }; 34D5CCB11EAE7E7F005515DB /* SelectRecipientViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCB01EAE7E7F005515DB /* SelectRecipientViewController.m */; }; @@ -549,6 +550,7 @@ 34CE88E91F3237260098030F /* OWSProfileManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSProfileManager.h; sourceTree = ""; }; 34CE88EA1F3237260098030F /* OWSProfileManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSProfileManager.m; sourceTree = ""; }; 34CE88EB1F3237260098030F /* ProfileFetcherJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileFetcherJob.swift; sourceTree = ""; }; + 34D1F04F1F7D45A60066283D /* GifPickerCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GifPickerCell.swift; sourceTree = ""; }; 34D5CC941EA6AFAD005515DB /* OWSContactsSyncing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactsSyncing.h; sourceTree = ""; }; 34D5CC951EA6AFAD005515DB /* OWSContactsSyncing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSyncing.m; sourceTree = ""; }; 34D5CC981EA6EB79005515DB /* OWSMessageCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageCollectionViewCell.h; sourceTree = ""; }; @@ -1132,6 +1134,7 @@ 34BECE2C1F7ABCE000D7438D /* GifPicker */ = { isa = PBXGroup; children = ( + 34D1F04F1F7D45A60066283D /* GifPickerCell.swift */, 34BECE2F1F7ABCF800D7438D /* GifPickerLayout.swift */, 34BECE2D1F7ABCE000D7438D /* GifPickerViewController.swift */, ); @@ -2220,6 +2223,7 @@ 34B3F8741E8DF1700035BE1A /* AttachmentSharing.m in Sources */, 34D8C02B1ED3685800188D7C /* DebugUIContacts.m in Sources */, 45C9DEB81DF4E35A0065CA84 /* WebRTCCallMessageHandler.swift in Sources */, + 34D1F0501F7D45A60066283D /* GifPickerCell.swift in Sources */, 34D99C931F2937CC00D284D6 /* OWSAnalytics.swift in Sources */, 34D9134B1F62D4A500722898 /* SignalAttachment.swift in Sources */, 34B3F88E1E8DF1700035BE1A /* PrivacySettingsTableViewController.m in Sources */, diff --git a/Signal/Images.xcassets/giphy_logo.imageset/Contents.json b/Signal/Images.xcassets/giphy_logo.imageset/Contents.json new file mode 100644 index 000000000..3220d7b54 --- /dev/null +++ b/Signal/Images.xcassets/giphy_logo.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "giphy_logo@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "giphy_logo@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "giphy_logo@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/giphy_logo.imageset/giphy_logo@1x.png b/Signal/Images.xcassets/giphy_logo.imageset/giphy_logo@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..77c2b062d2376e23029eabf6698e3a0e1b20d4f1 GIT binary patch literal 6936 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 I%FvMi136;2E&u=k literal 0 HcmV?d00001 diff --git a/Signal/Images.xcassets/giphy_logo.imageset/giphy_logo@2x.png b/Signal/Images.xcassets/giphy_logo.imageset/giphy_logo@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9c79f27ec06aa8cbb1a8d41b56db1f182bb0b605 GIT binary patch literal 13011 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 D>Iu$3 literal 0 HcmV?d00001 diff --git a/Signal/Images.xcassets/giphy_logo.imageset/giphy_logo@3x.png b/Signal/Images.xcassets/giphy_logo.imageset/giphy_logo@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6af136d611b2e50c92c1ffb0b280b239c9e396a0 GIT binary patch literal 14981 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 V-LjYRXzll?s;O{||<29T)%r literal 0 HcmV?d00001 diff --git a/Signal/src/ViewControllers/GifPicker/GifPickerCell.swift b/Signal/src/ViewControllers/GifPicker/GifPickerCell.swift new file mode 100644 index 000000000..fe3afe0e2 --- /dev/null +++ b/Signal/src/ViewControllers/GifPicker/GifPickerCell.swift @@ -0,0 +1,652 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +import Foundation +//import MediaPlayer + +class GifPickerCell: UICollectionViewCell + //, OWSAudioAttachmentPlayerDelegate +{ + let TAG = "[GifPickerCell]" + + // MARK: Properties + + var imageInfo: GiphyImageInfo? +// +// let searchBar: UISearchBar +// let layout: GifPickerLayout +// let collectionView: UICollectionView +// var logoImageView : UIImageView? +// +// var imageInfos = [GiphyImageInfo]() +// +// // let attachment: SignalAttachment +// // +// // var successCompletion : (() -> Void)? +// // +// // var videoPlayer: MPMoviePlayerController? +// // +// // var audioPlayer: OWSAudioAttachmentPlayer? +// // var audioStatusLabel: UILabel? +// // var audioPlayButton: UIButton? +// // var isAudioPlayingFlag = false +// // var isAudioPaused = false +// // var audioProgressSeconds: CGFloat = 0 +// // var audioDurationSeconds: CGFloat = 0 +// +// // MARK: Initializers +// +// @available(*, unavailable, message:"use attachment: constructor instead.") +// required init?(coder aDecoder: NSCoder) { +// self.searchBar = UISearchBar() +// self.layout = GifPickerLayout() +// self.collectionView = UICollectionView(frame:CGRect.zero, collectionViewLayout:self.layout) +// // self.attachment = SignalAttachment.empty() +// super.init(coder: aDecoder) +// owsFail("\(self.TAG) invalid constructor") +// } +// +// required init() { +// self.searchBar = UISearchBar() +// self.layout = GifPickerLayout() +// self.collectionView = UICollectionView(frame:CGRect.zero, collectionViewLayout:self.layout) +// // assert(!attachment.hasError) +// // self.attachment = attachment +// // self.successCompletion = successCompletion +// super.init(nibName: nil, bundle: nil) +// } + + override func prepareForReuse() { + super.prepareForReuse() + } +// +// // 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) +// } +} diff --git a/Signal/src/ViewControllers/GifPicker/GifPickerViewController.swift b/Signal/src/ViewControllers/GifPicker/GifPickerViewController.swift index 7549a5f5e..549116ba8 100644 --- a/Signal/src/ViewControllers/GifPicker/GifPickerViewController.swift +++ b/Signal/src/ViewControllers/GifPicker/GifPickerViewController.swift @@ -5,9 +5,7 @@ import Foundation //import MediaPlayer -class GifPickerViewController: OWSViewController, UISearchBarDelegate - //, OWSAudioAttachmentPlayerDelegate -{ +class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollectionViewDataSource, UICollectionViewDelegate { let TAG = "[GifPickerViewController]" // MARK: Properties @@ -15,20 +13,11 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate let searchBar: UISearchBar let layout: GifPickerLayout let collectionView: UICollectionView + var logoImageView: UIImageView? - // let attachment: SignalAttachment - // - // var successCompletion : (() -> Void)? - // - // var videoPlayer: MPMoviePlayerController? - // - // var audioPlayer: OWSAudioAttachmentPlayer? - // var audioStatusLabel: UILabel? - // var audioPlayButton: UIButton? - // var isAudioPlayingFlag = false - // var isAudioPaused = false - // var audioProgressSeconds: CGFloat = 0 - // var audioDurationSeconds: CGFloat = 0 + var imageInfos = [GiphyImageInfo]() + + private let kCellReuseIdentifier = "kCellReuseIdentifier" // MARK: Initializers @@ -57,7 +46,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = UIColor.white + view.backgroundColor = UIColor.black self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem:.stop, target:self, @@ -68,37 +57,76 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate createViews() } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + search(query:"funny") + +// self.view.layoutSubviews() +// updateImageLayout() + } + // MARK: Views private func createViews() { - // @property (nonatomic, readonly) UISearchBar *searchBar; - view.backgroundColor = UIColor.white + view.backgroundColor = UIColor.black // Search - searchBar.searchBarStyle = .minimal +// 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 +// 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.delegate = self + self.collectionView.dataSource = self + self.collectionView.backgroundColor = UIColor.black + self.collectionView.register(GifPickerCell.self, forCellWithReuseIdentifier: kCellReuseIdentifier) 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() { @@ -457,70 +485,66 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate // 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 + + public 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: + public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let imageInfo = imageInfos[indexPath.row] + + let cell = collectionView.dequeueReusableCell(withReuseIdentifier:kCellReuseIdentifier, for: indexPath) as! GifPickerCell + cell.imageInfo = imageInfo + return cell + } + + // MARK: - UICollectionViewDelegate + + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let imageInfo = imageInfos[indexPath.row] + } + + public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + + } + + public func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + + } // MARK: - Event Handlers func donePressed(sender: UIButton) { dismiss(animated: true, completion:nil) } + + // MARK: - UISearchBarDelegate + + public func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + // TODO: We could do progressive search as the user types. + } + + public func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { + guard let text = searchBar.text else { + // TODO: Alert? + return + } + search(query:text) + } + + private func search(query: String) { + GifManager.sharedInstance.search(query: query, success: { [weak self] imageInfos in + guard let strongSelf = self else { return } + Logger.info("\(strongSelf.TAG) search complete") + strongSelf.imageInfos = imageInfos + strongSelf.updateContents() + }, + failure: { [weak self] in + guard let strongSelf = self else { return } + Logger.info("\(strongSelf.TAG) search failed.") + }) + } } diff --git a/Signal/src/network/GifManager.swift b/Signal/src/network/GifManager.swift index ce8b127f3..9f2c36635 100644 --- a/Signal/src/network/GifManager.swift +++ b/Signal/src/network/GifManager.swift @@ -40,6 +40,42 @@ enum GiphyFormat { self.giphyId = giphyId self.renditions = renditions } + + let kMaxDimension = UInt(618) + let kMinDimension = UInt(101) + let kMaxFileSize = SignalAttachment.kMaxFileSizeAnimatedImage + + public func pickGifRendition() -> GiphyRendition? { + var bestRendition: GiphyRendition? + + for rendition in renditions { + guard rendition.format == .gif else { + continue + } + guard !rendition.name.hasSuffix("_still") + 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 + } + } else { + bestRendition = rendition + } + } + + return bestRendition + } } @objc class GifManager: NSObject { @@ -81,13 +117,23 @@ enum GiphyFormat { return sessionManager } + // TODO: public func test() { + search(query:"monkey", + success: { _ in + }, failure: { + }) + } + + public func search(query: String, success: @escaping (([GiphyImageInfo]) -> Void), failure: @escaping (() -> Void)) { guard let sessionManager = giphySessionManager() else { Logger.error("\(GifManager.TAG) Couldn't create session manager.") + failure() return } guard NSURL(string:kGiphyBaseURL) != nil else { Logger.error("\(GifManager.TAG) Invalid base URL.") + failure() return } @@ -96,27 +142,27 @@ enum GiphyFormat { let kGiphyPageSize = 200 // TODO: let kGiphyPageOffset = 0 - // TODO: - let query = "monkey" - // TODO: guard let queryEncoded = query.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { Logger.error("\(GifManager.TAG) Could not URL encode query: \(query).") + failure() return } - // Logger.error("\(GifManager.TAG) queryEncoded: \(queryEncoded) \(queryEncoded).") let urlString = "/v1/gifs/search?api_key=\(kGiphyApiKey)&offset=\(kGiphyPageOffset)&limit=\(kGiphyPageSize)&q=\(queryEncoded)" - // Logger.error("\(GifManager.TAG) urlString: \(urlString).") - // Logger.error("\(GifManager.TAG) baseUrl: \(baseUrl).") sessionManager.get(urlString, parameters: {}, progress:nil, success: { _, value in Logger.error("\(GifManager.TAG) search request succeeded") - self.parseGiphyImages(responseJson:value) + guard let imageInfos = self.parseGiphyImages(responseJson:value) else { + failure() + return + } + success(imageInfos) }, failure: { _, error in Logger.error("\(GifManager.TAG) search request failed: \(error)") + failure() }) } @@ -172,7 +218,7 @@ enum GiphyFormat { Logger.warn("\(GifManager.TAG) Image has no valid renditions.") return nil } - Logger.debug("\(GifManager.TAG) Image successfully parsed.") +// Logger.debug("\(GifManager.TAG) Image successfully parsed.") return GiphyImageInfo(giphyId : giphyId, renditions : renditions) } @@ -204,13 +250,12 @@ enum GiphyFormat { Logger.warn("\(GifManager.TAG) Rendition url missing file extension.") return nil } - Logger.error("\(GifManager.TAG) fileExtension: \(fileExtension).") guard fileExtension.lowercased() == "gif" else { - Logger.debug("\(GifManager.TAG) Rendition has invalid type: \(fileExtension).") +// Logger.verbose("\(GifManager.TAG) Rendition has invalid type: \(fileExtension).") return nil } - Logger.debug("\(GifManager.TAG) Rendition successfully parsed.") +// Logger.debug("\(GifManager.TAG) Rendition successfully parsed.") return GiphyRendition( format : .gif, name : renditionName, @@ -221,6 +266,8 @@ enum GiphyFormat { ) } + // Giphy API results are often incompl + // // { // height = 65; // mp4 = "https://media3.giphy.com/media/42YlR8u9gV5Cw/100w.mp4"; @@ -233,19 +280,19 @@ enum GiphyFormat { // } private func parsePositiveUInt(dict: [String:Any], key: String, typeName: String) -> UInt? { guard let value = dict[key] else { - Logger.debug("\(GifManager.TAG) \(typeName) missing \(key).") +// Logger.verbose("\(GifManager.TAG) \(typeName) missing \(key).") return nil } guard let stringValue = value as? String else { - Logger.warn("\(GifManager.TAG) \(typeName) has invalid \(key): \(value).") +// Logger.verbose("\(GifManager.TAG) \(typeName) has invalid \(key): \(value).") return nil } guard let parsedValue = UInt(stringValue) else { - Logger.warn("\(GifManager.TAG) \(typeName) has invalid \(key): \(stringValue).") +// Logger.verbose("\(GifManager.TAG) \(typeName) has invalid \(key): \(stringValue).") return nil } guard parsedValue > 0 else { - Logger.debug("\(GifManager.TAG) \(typeName) has non-positive \(key): \(parsedValue).") + Logger.verbose("\(GifManager.TAG) \(typeName) has non-positive \(key): \(parsedValue).") return nil } return parsedValue