Fix sticker keyboard layout.
This commit is contained in:
parent
4708963ebd
commit
48df37519f
@ -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 = "<group>"; };
|
||||
34C2EEB12270CC8D00BCA1D0 /* StickerPackCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerPackCollectionView.swift; sourceTree = "<group>"; };
|
||||
34C2EEB32270D1CE00BCA1D0 /* StickerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerView.swift; sourceTree = "<group>"; };
|
||||
34C2EEB52270FF7B00BCA1D0 /* LinearHorizontalLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinearHorizontalLayout.swift; sourceTree = "<group>"; };
|
||||
34C3C78C20409F320000134C /* Opening.m4r */ = {isa = PBXFileReference; lastKnownFileType = file; path = Opening.m4r; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
@ -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 */,
|
||||
|
||||
23
Signal/Images.xcassets/plus-24.imageset/Contents.json
vendored
Normal file
23
Signal/Images.xcassets/plus-24.imageset/Contents.json
vendored
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
BIN
Signal/Images.xcassets/plus-24.imageset/plus-24@1x.png
vendored
Normal file
BIN
Signal/Images.xcassets/plus-24.imageset/plus-24@1x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 168 B |
BIN
Signal/Images.xcassets/plus-24.imageset/plus-24@2x.png
vendored
Normal file
BIN
Signal/Images.xcassets/plus-24.imageset/plus-24@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 248 B |
BIN
Signal/Images.xcassets/plus-24.imageset/plus-24@3x.png
vendored
Normal file
BIN
Signal/Images.xcassets/plus-24.imageset/plus-24@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 348 B |
23
Signal/Images.xcassets/search-24.imageset/Contents.json
vendored
Normal file
23
Signal/Images.xcassets/search-24.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "search-24@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "search-24@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "search-24@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
Signal/Images.xcassets/search-24.imageset/search-24@1x.png
vendored
Normal file
BIN
Signal/Images.xcassets/search-24.imageset/search-24@1x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 321 B |
BIN
Signal/Images.xcassets/search-24.imageset/search-24@2x.png
vendored
Normal file
BIN
Signal/Images.xcassets/search-24.imageset/search-24@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 654 B |
BIN
Signal/Images.xcassets/search-24.imageset/search-24@3x.png
vendored
Normal file
BIN
Signal/Images.xcassets/search-24.imageset/search-24@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 999 B |
@ -0,0 +1,104 @@
|
||||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
// A trivial layout that places each item in a horizontal line.
|
||||
// Each item has uniform size.
|
||||
class LinearHorizontalLayout: UICollectionViewLayout {
|
||||
|
||||
private let itemSize: CGSize
|
||||
private let inset: CGFloat
|
||||
private let spacing: CGFloat
|
||||
|
||||
private var itemAttributesMap = [UInt: UICollectionViewLayoutAttributes]()
|
||||
|
||||
private var contentSize = CGSize.zero
|
||||
|
||||
// MARK: Initializers and Factory Methods
|
||||
|
||||
@available(*, unavailable, message:"use other constructor instead.")
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
required init(itemSize: CGSize, inset: CGFloat = 0, spacing: CGFloat = 0) {
|
||||
self.itemSize = itemSize
|
||||
self.inset = inset
|
||||
self.spacing = spacing
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
override func invalidateLayout() {
|
||||
super.invalidateLayout()
|
||||
|
||||
itemAttributesMap.removeAll()
|
||||
}
|
||||
|
||||
override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
|
||||
super.invalidateLayout(with: context)
|
||||
|
||||
itemAttributesMap.removeAll()
|
||||
}
|
||||
|
||||
override func prepare() {
|
||||
super.prepare()
|
||||
|
||||
guard let collectionView = collectionView else {
|
||||
return
|
||||
}
|
||||
guard collectionView.numberOfSections == 1 else {
|
||||
owsFailDebug("This layout only support a single section.")
|
||||
return
|
||||
}
|
||||
let itemCount = collectionView.numberOfItems(inSection: 0)
|
||||
|
||||
let vInset: CGFloat = inset
|
||||
let hInset: CGFloat = inset
|
||||
|
||||
guard itemCount > 0 else {
|
||||
contentSize = .zero
|
||||
return
|
||||
}
|
||||
|
||||
for row in 0..<itemCount {
|
||||
let itemX: CGFloat = hInset + CGFloat(row) * (itemSize.width + spacing)
|
||||
let itemY: CGFloat = vInset
|
||||
let itemFrame = CGRect(x: itemX, y: itemY, width: itemSize.width, height: itemSize.height)
|
||||
|
||||
let indexPath = NSIndexPath(row: row, section: 0)
|
||||
let itemAttributes = UICollectionViewLayoutAttributes(forCellWith: indexPath as IndexPath)
|
||||
itemAttributes.frame = itemFrame
|
||||
itemAttributesMap[UInt(row)] = itemAttributes
|
||||
}
|
||||
|
||||
contentSize = CGSize(width: hInset * 2 + CGFloat(itemCount) * itemSize.width + CGFloat(itemCount - 1) * spacing,
|
||||
height: vInset * 2 + itemSize.height)
|
||||
}
|
||||
|
||||
override func layoutAttributesForElements(in rect: CGRect) -> [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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user