From 48df37519f84d0b3bbbae9a4eec345760ebd042f Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 24 Apr 2019 16:38:45 -0400 Subject: [PATCH] Fix sticker keyboard layout. --- Signal.xcodeproj/project.pbxproj | 4 + .../plus-24.imageset/Contents.json | 23 ++++ .../plus-24.imageset/plus-24@1x.png | Bin 0 -> 168 bytes .../plus-24.imageset/plus-24@2x.png | Bin 0 -> 248 bytes .../plus-24.imageset/plus-24@3x.png | Bin 0 -> 348 bytes .../search-24.imageset/Contents.json | 23 ++++ .../search-24.imageset/search-24@1x.png | Bin 0 -> 321 bytes .../search-24.imageset/search-24@2x.png | Bin 0 -> 654 bytes .../search-24.imageset/search-24@3x.png | Bin 0 -> 999 bytes .../Stickers/LinearHorizontalLayout.swift | 104 ++++++++++++++ .../Stickers/StickerKeyboard.swift | 129 +++++++++++++++++- .../Stickers/StickerPackCollectionView.swift | 12 +- 12 files changed, 285 insertions(+), 10 deletions(-) create mode 100644 Signal/Images.xcassets/plus-24.imageset/Contents.json create mode 100644 Signal/Images.xcassets/plus-24.imageset/plus-24@1x.png create mode 100644 Signal/Images.xcassets/plus-24.imageset/plus-24@2x.png create mode 100644 Signal/Images.xcassets/plus-24.imageset/plus-24@3x.png create mode 100644 Signal/Images.xcassets/search-24.imageset/Contents.json create mode 100644 Signal/Images.xcassets/search-24.imageset/search-24@1x.png create mode 100644 Signal/Images.xcassets/search-24.imageset/search-24@2x.png create mode 100644 Signal/Images.xcassets/search-24.imageset/search-24@3x.png create mode 100644 SignalMessaging/ViewControllers/Stickers/LinearHorizontalLayout.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 2e4c3def24..56406aed02 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -274,6 +274,7 @@ 34C2EEB02270B8E200BCA1D0 /* StickerKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C2EEAF2270B8E100BCA1D0 /* StickerKeyboard.swift */; }; 34C2EEB22270CC8E00BCA1D0 /* StickerPackCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C2EEB12270CC8D00BCA1D0 /* StickerPackCollectionView.swift */; }; 34C2EEB42270D1CE00BCA1D0 /* StickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C2EEB32270D1CE00BCA1D0 /* StickerView.swift */; }; + 34C2EEB62270FF7C00BCA1D0 /* LinearHorizontalLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C2EEB52270FF7B00BCA1D0 /* LinearHorizontalLayout.swift */; }; 34C3C78D20409F320000134C /* Opening.m4r in Resources */ = {isa = PBXBuildFile; fileRef = 34C3C78C20409F320000134C /* Opening.m4r */; }; 34C3C78F2040A4F70000134C /* sonarping.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 34C3C78E2040A4F70000134C /* sonarping.mp3 */; }; 34C3C7922040B0DD0000134C /* OWSAudioPlayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 34C3C7902040B0DC0000134C /* OWSAudioPlayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -985,6 +986,7 @@ 34C2EEAF2270B8E100BCA1D0 /* StickerKeyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerKeyboard.swift; sourceTree = ""; }; 34C2EEB12270CC8D00BCA1D0 /* StickerPackCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerPackCollectionView.swift; sourceTree = ""; }; 34C2EEB32270D1CE00BCA1D0 /* StickerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerView.swift; sourceTree = ""; }; + 34C2EEB52270FF7B00BCA1D0 /* LinearHorizontalLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinearHorizontalLayout.swift; sourceTree = ""; }; 34C3C78C20409F320000134C /* Opening.m4r */ = {isa = PBXFileReference; lastKnownFileType = file; path = Opening.m4r; sourceTree = ""; }; 34C3C78E2040A4F70000134C /* sonarping.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = sonarping.mp3; path = Signal/AudioFiles/sonarping.mp3; sourceTree = SOURCE_ROOT; }; 34C3C7902040B0DC0000134C /* OWSAudioPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAudioPlayer.h; sourceTree = ""; }; @@ -1726,6 +1728,7 @@ 344DC9AD226E483C004E7322 /* Stickers */ = { isa = PBXGroup; children = ( + 34C2EEB52270FF7B00BCA1D0 /* LinearHorizontalLayout.swift */, 344DC9AE226E483C004E7322 /* ManageStickersViewController.swift */, 34C2EEAF2270B8E100BCA1D0 /* StickerKeyboard.swift */, 34C2EEB12270CC8D00BCA1D0 /* StickerPackCollectionView.swift */, @@ -3484,6 +3487,7 @@ 346129F51FD5F31400532771 /* OWS102MoveLoggingPreferenceToUserDefaults.m in Sources */, 45194F8F1FD71FF500333B2C /* ThreadUtil.m in Sources */, 34BEDB0E21C405B0007B0EAE /* ImageEditorModel.swift in Sources */, + 34C2EEB62270FF7C00BCA1D0 /* LinearHorizontalLayout.swift in Sources */, 451F8A3B1FD71297005CB9DA /* UIUtil.m in Sources */, 340872C122394CAA00CB25B0 /* ImageEditorTransform.swift in Sources */, 450C800F20AD1AB900F3A091 /* OWSWindowManager.m in Sources */, diff --git a/Signal/Images.xcassets/plus-24.imageset/Contents.json b/Signal/Images.xcassets/plus-24.imageset/Contents.json new file mode 100644 index 0000000000..72e7fa1e25 --- /dev/null +++ b/Signal/Images.xcassets/plus-24.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "plus-24@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "plus-24@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "plus-24@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/plus-24.imageset/plus-24@1x.png b/Signal/Images.xcassets/plus-24.imageset/plus-24@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..5a11ea9a1d129a1dbf19f106db44dd7ba5faebb8 GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR0pkS1z zi(`mKXY!x_|LvJ|8HIg))vq$SxgBFx=uvEB=h2hzi89Dy409;ve&EVFQSt*H$#5Cl>NYP zm`Q1oE6eY!mrb*O$OjZR%woPGv)~znDfRJJK8PFVEm@e~)3!%Pww@ zf427{4=ny3>2d61`A@-Fmn9#}J{M!0(lV*G#Z8Cd_nJ-X&0g8n^{xWz=R+8|_4`|| hkjH{St7RD;u*)zk3gf%b%LH^agQu&X%Q~loCICf4Ui$z5 literal 0 HcmV?d00001 diff --git a/Signal/Images.xcassets/plus-24.imageset/plus-24@3x.png b/Signal/Images.xcassets/plus-24.imageset/plus-24@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..d63c48c7145d591dab4fc313375b0df64da993ba GIT binary patch literal 348 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!oCO|{#S9FJ<{->y95KI&fq{|F z)5S5QV$R!J2YH(u1RO3(COb}PP>t-=jbxJT4b0bhY~Oh7&-8+usx37MmJVT|E6TId zza%LnfZ+$>Mx)|$XQFaHZ+I+T!OqA`Dr_t^ul=z6ex^j{45MO+JKmvpxAWcom-y|E zpB>M?jjL|$k+MrV;BdfULF^LcM@Btwp6~RyALhQ_?MTrB%bYCc($&A@W^KP(^u)5H r=14;$snFqf{e*janqk`PIy>0^d{X4{+hKPY7+egVu6{1-oD!M2L!FbIT9;1M!HM(GG1p(AA!kC0Ka0hKFUAs{qK z?MIQ2B6Hk}*$9lmvfm*Ymcm zUab1WBbj)}_Jd)IEX>jOAOzpBw*V0};`s(gGAq)4JmlrdA&(u?;TMh0KNX9pJgnXb z(DldeDZbMw0d*#_QWBkIf!iVxLww^Vr%1#k^cFGTP9l%)0z%|NUj5JiDgfXG0Q`mj Tgk?a`00000NkvXXu0mjf$$o?h literal 0 HcmV?d00001 diff --git a/Signal/Images.xcassets/search-24.imageset/search-24@2x.png b/Signal/Images.xcassets/search-24.imageset/search-24@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..5520b610fb0956ec5334baf5e25dda312a82cfd1 GIT binary patch literal 654 zcmV;90&)F`P)!5cIqbcBx35i&wXa5hLc z$O!HPRH)aAa?gnYJ2p|Cgk&8D$Ag1ANQ|`>CF7~+(LV^R0MDsIFve^Fv;Z0aFG}GD zM*tmwgSGagYNvqP#4!cH4uBhsjV!LsF4o?)GF}m=Wj1369v0U}&1|HUQOBq;#<|KK zz$U2ra_%96ItO*NxK}&S&aK8l8H>e;nctCFJd3FPQ)_uxEOiBtmyenjj^R&QHOH zjrREw1l*qRH};9?MH2w9oy&EIt#GBcOz%=*xlrq$*Vfv-(wGhhi3!a1C@=zz_njYT zF2Gvb2R52vZ4O4jPs}=1J3V|GLrLKv7y%z)dN2pFZ2Ti&jH#5y%5h&;O4(FJ;o1t+ zN-{mNY%l^x?>qWfOMq|V7~7l?@Q<~2&W$a7`@t*v*k%O$z_Lk+ju8lNYw>}T7U^`-%}fbA1La<5>^)p5ZRwZ#3p4ijtitq3`%3x+31jJ(XajOQk&ScFf;NUr{8KAcBaSz4t4t(uvOC zWnJ7YnS>H7QF2HqL1`qEpfnOnP#Os(STPBjFR}Z;;*en0NO1cNd~a-At9_O5NE{_X o(_c9se>GSd4+2Z0Yytqj0c85LWmBGtFaQ7m07*qoM6N<$g1!SD>i_@% literal 0 HcmV?d00001 diff --git a/Signal/Images.xcassets/search-24.imageset/search-24@3x.png b/Signal/Images.xcassets/search-24.imageset/search-24@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..ddcb373baf3dbed24992e3f4563370a488aa3b7e GIT binary patch literal 999 zcmVZo*+1?`t%I=jNJqpQA|kJeI~G z+xWtv8LtT%xwGe#YQbX_;gMwvARL-S_Bq)jM-a9nd9St4E)egKHA30eoEDs{HOYO) zEgYhO{uDWAZATVq6kfv7GI~Z^cP!)eNaN-jMX7wyMtA_8i0CN1BZE0SDWhtfdn5WT zhKOh7J?lFWJ&VQ#fQU^L{Ru{XC8C8F8#979uM>)Mnu<8^5k;rKeaGBgU<$g$je9ScYY-wfQ|Z4PG7+I@H~}DH_#s}1Xz9T$7C~Wx-AZ;% z0UMyfjpSDn3vJN}xXG6Np8SScmxCXHqaY3GO)r z%DO{!gvLqZ-V0p?a!n;TLF1%x?*(%W0z2LDa|-z&D4p(LXlLLL!cz_!7mag&Xlcez zo^o~NCLE%{bd&a4k;Ax2`$QDgW1lNRQ#v@@C_Ks?9Mb1GdR}Pvj3N-lk9@}}Uf3Ao zWQ}Cuh}lHTU$U-+!qI~4*acMD)9Tu?7Iq4UgGj{JdlP42Qe#}~nF~8bKg9&1@{sz5OSz#Hm1 V)}SbafgJz<002ovPDHLkV1gXezFz 0 else { + contentSize = .zero + return + } + + for row in 0.. [UICollectionViewLayoutAttributes]? { + return itemAttributesMap.values.filter { itemAttributes in + return itemAttributes.frame.intersects(rect) + } + } + + override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { + let result = itemAttributesMap[UInt(indexPath.row)] + return result + } + + override var collectionViewContentSize: CGSize { + return contentSize + } + + override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { + guard let collectionView = collectionView else { + return false + } + return collectionView.width() != newBounds.size.width + } +} diff --git a/SignalMessaging/ViewControllers/Stickers/StickerKeyboard.swift b/SignalMessaging/ViewControllers/Stickers/StickerKeyboard.swift index 04ab2b297d..ae4f3ca9eb 100644 --- a/SignalMessaging/ViewControllers/Stickers/StickerKeyboard.swift +++ b/SignalMessaging/ViewControllers/Stickers/StickerKeyboard.swift @@ -43,8 +43,12 @@ public class StickerKeyboard: UIStackView { object: nil) } + required public init(coder: NSCoder) { + notImplemented() + } + // TODO: Tune this value. - private let keyboardHeight: CGFloat = 200 + private let keyboardHeight: CGFloat = 300 @objc public override var intrinsicContentSize: CGSize { @@ -59,31 +63,81 @@ public class StickerKeyboard: UIStackView { addBackgroundView(withBackgroundColor: Theme.offBackgroundColor) - headerView.axis = .horizontal addArrangedSubview(headerView) headerView.setContentHuggingVerticalHigh() headerView.setCompressionResistanceVerticalHigh() - headerView.autoSetDimension(.height, toSize: 44) stickerCollectionView.stickerDelegate = self addArrangedSubview(stickerCollectionView) stickerCollectionView.setContentHuggingVerticalLow() stickerCollectionView.setCompressionResistanceVerticalLow() + + populateHeaderView() } private func reloadStickers() { stickerPacks = StickerManager.installedStickerPacks() + packsCollectionView.collectionViewLayout.invalidateLayout() + packsCollectionView.reloadData() + guard stickerPacks.count > 0 else { - stickerPack = nil + stickerPack = nil return } if stickerPack == nil { stickerPack = stickerPacks.first } + } - // TODO: Reload header? + private let packsCollectionView = UICollectionView(frame: .zero, collectionViewLayout: buildCoverLayout()) + private let cellReuseIdentifier = "cellReuseIdentifier" + + private static let packCoverSize: CGFloat = 24 + private static let packCoverSpacing: CGFloat = 12 + + private class func buildCoverLayout() -> UICollectionViewLayout { + return LinearHorizontalLayout(itemSize: CGSize(width: packCoverSize, height: packCoverSize), inset: 0, spacing: packCoverSpacing) + } + + private func populateHeaderView() { + headerView.spacing = StickerKeyboard.packCoverSpacing + headerView.axis = .horizontal + headerView.alignment = .center + headerView.backgroundColor = Theme.offBackgroundColor + headerView.layoutMargins = UIEdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12) + headerView.isLayoutMarginsRelativeArrangement = true + + let searchButton = OWSButton(imageName: "search-24", tintColor: Theme.secondaryColor) { [weak self] in + self?.searchButtonWasTapped() + } + searchButton.setContentHuggingHigh() + searchButton.setCompressionResistanceHigh() + headerView.addArrangedSubview(searchButton) + + packsCollectionView.backgroundColor = Theme.offBackgroundColor + packsCollectionView.delegate = self + packsCollectionView.dataSource = self + packsCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellReuseIdentifier) + backgroundColor = Theme.offBackgroundColor + + packsCollectionView.setContentHuggingHorizontalLow() + packsCollectionView.setCompressionResistanceHorizontalLow() + packsCollectionView.autoSetDimension(.height, toSize: StickerKeyboard.packCoverSize) + headerView.addArrangedSubview(packsCollectionView) + + let manageButton = OWSButton(imageName: "plus-24", tintColor: Theme.secondaryColor) { [weak self] in + self?.manageButtonWasTapped() + } + manageButton.setContentHuggingHigh() + manageButton.setCompressionResistanceHigh() + headerView.addArrangedSubview(manageButton) + + updateHeaderView() + } + + private func updateHeaderView() { } // MARK: Events @@ -94,10 +148,23 @@ public class StickerKeyboard: UIStackView { Logger.verbose("") reloadStickers() + updateHeaderView() } - required public init(coder: NSCoder) { - notImplemented() + private func searchButtonWasTapped() { + AssertIsOnMainThread() + + Logger.verbose("") + + // TODO: + } + + private func manageButtonWasTapped() { + AssertIsOnMainThread() + + Logger.verbose("") + + // TODO: } } @@ -112,3 +179,51 @@ extension StickerKeyboard: StickerPackCollectionViewDelegate { delegate?.didSelectSticker(stickerInfo: stickerInfo) } } + +// MARK: - UICollectionViewDelegate + +extension StickerKeyboard: UICollectionViewDelegate { + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + Logger.debug("") + + guard let stickerPack = stickerPacks[safe: indexPath.row] else { + owsFailDebug("Invalid index path: \(indexPath)") + return + } + + self.stickerPack = stickerPack + } +} + +// MARK: - UICollectionViewDataSource + +extension StickerKeyboard: UICollectionViewDataSource { + + public func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } + + public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection sectionIdx: Int) -> Int { + return stickerPacks.count + } + + public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + // We could eventually use cells that lazy-load the sticker views + // when the cells becomes visible and eagerly unload them. + // But we probably won't need to do that. + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellReuseIdentifier, for: indexPath) + + guard let stickerPack = stickerPacks[safe: indexPath.row] else { + owsFailDebug("Invalid index path: \(indexPath)") + return cell + } + + // TODO: Actual size? + let iconView = StickerView(stickerInfo: stickerPack.coverInfo) + + cell.contentView.addSubview(iconView) + iconView.autoPinEdgesToSuperviewEdges() + + return cell + } +} diff --git a/SignalMessaging/ViewControllers/Stickers/StickerPackCollectionView.swift b/SignalMessaging/ViewControllers/Stickers/StickerPackCollectionView.swift index 2ab9174a9e..c65505b670 100644 --- a/SignalMessaging/ViewControllers/Stickers/StickerPackCollectionView.swift +++ b/SignalMessaging/ViewControllers/Stickers/StickerPackCollectionView.swift @@ -22,6 +22,8 @@ public class StickerPackCollectionView: UICollectionView { AssertIsOnMainThread() reloadStickers() + // Scroll to the top. + contentOffset = .zero } } @@ -151,6 +153,8 @@ extension StickerPackCollectionView { } layout.minimumInteritemSpacing = kSpacing layout.minimumLineSpacing = kSpacing + let inset = kSpacing + layout.sectionInset = UIEdgeInsets(top: inset, leading: inset, bottom: inset, trailing: inset) return layout } @@ -170,9 +174,11 @@ extension StickerPackCollectionView { } let spacing = StickerPackCollectionView.kSpacing - let preferredCellSize: CGFloat = 84 - let columnCount = UInt((containerWidth + spacing) / (preferredCellSize + spacing)) - let cellWidth = (containerWidth - spacing * (CGFloat(columnCount) - 1)) / CGFloat(columnCount) + let inset = spacing + let preferredCellSize: CGFloat = 80 + let contentWidth = containerWidth - 2 * inset + let columnCount = UInt((contentWidth + spacing) / (preferredCellSize + spacing)) + let cellWidth = (contentWidth - spacing * (CGFloat(columnCount) - 1)) / CGFloat(columnCount) let itemSize = CGSize(width: cellWidth, height: cellWidth) if (itemSize != flowLayout.itemSize) {