2018-11-05 22:43:46 +01:00
|
|
|
//
|
|
|
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
|
|
|
|
@objc(OWSMediaGalleryCellView)
|
2018-11-06 15:20:22 +01:00
|
|
|
public class MediaGalleryCellView: UIStackView {
|
2018-11-05 22:43:46 +01:00
|
|
|
private let items: [ConversationMediaGalleryItem]
|
2018-11-06 15:20:22 +01:00
|
|
|
private let itemViews: [ConversationMediaView]
|
2018-11-05 22:43:46 +01:00
|
|
|
|
|
|
|
private static let kSpacingPts: CGFloat = 2
|
|
|
|
private static let kMaxItems = 5
|
|
|
|
|
|
|
|
@objc
|
2018-11-06 15:20:22 +01:00
|
|
|
public required init(mediaCache: NSCache<NSString, AnyObject>,
|
2018-11-05 22:43:46 +01:00
|
|
|
items: [ConversationMediaGalleryItem],
|
|
|
|
maxMessageWidth: CGFloat) {
|
|
|
|
self.items = items
|
|
|
|
self.itemViews = MediaGalleryCellView.itemsToDisplay(forItems: items).map {
|
2018-11-06 15:20:22 +01:00
|
|
|
ConversationMediaView(mediaCache: mediaCache,
|
|
|
|
attachment: $0.attachment)
|
2018-11-05 22:43:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
super.init(frame: .zero)
|
|
|
|
|
2018-11-06 19:06:11 +01:00
|
|
|
backgroundColor = Theme.backgroundColor
|
2018-11-05 22:43:46 +01:00
|
|
|
|
|
|
|
createContents(maxMessageWidth: maxMessageWidth)
|
|
|
|
}
|
|
|
|
|
|
|
|
private func createContents(maxMessageWidth: CGFloat) {
|
|
|
|
switch itemViews.count {
|
|
|
|
case 0:
|
|
|
|
return
|
|
|
|
case 1:
|
|
|
|
guard let itemView = itemViews.first else {
|
|
|
|
owsFailDebug("Missing item view.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
addSubview(itemView)
|
|
|
|
itemView.autoPinEdgesToSuperviewEdges()
|
|
|
|
case 4:
|
|
|
|
// Square
|
|
|
|
let imageSize = (maxMessageWidth - MediaGalleryCellView.kSpacingPts) / 2
|
|
|
|
|
|
|
|
let topViews = Array(itemViews[0..<2])
|
2018-11-06 15:20:22 +01:00
|
|
|
addArrangedSubview(newRow(rowViews: topViews,
|
|
|
|
axis: .horizontal,
|
|
|
|
viewSize: imageSize))
|
2018-11-05 22:43:46 +01:00
|
|
|
|
|
|
|
let bottomViews = Array(itemViews[2..<4])
|
2018-11-06 15:20:22 +01:00
|
|
|
addArrangedSubview(newRow(rowViews: bottomViews,
|
|
|
|
axis: .horizontal,
|
|
|
|
viewSize: imageSize))
|
2018-11-05 22:43:46 +01:00
|
|
|
|
2018-11-06 15:20:22 +01:00
|
|
|
self.axis = .vertical
|
|
|
|
self.spacing = MediaGalleryCellView.kSpacingPts
|
2018-11-05 22:43:46 +01:00
|
|
|
case 2:
|
|
|
|
// X X
|
|
|
|
// side-by-side.
|
|
|
|
let imageSize = (maxMessageWidth - MediaGalleryCellView.kSpacingPts) / 2
|
2018-11-06 15:20:22 +01:00
|
|
|
autoSet(viewSize: imageSize, ofViews: itemViews)
|
2018-11-05 22:43:46 +01:00
|
|
|
for itemView in itemViews {
|
2018-11-06 15:20:22 +01:00
|
|
|
addArrangedSubview(itemView)
|
2018-11-05 22:43:46 +01:00
|
|
|
}
|
2018-11-06 15:20:22 +01:00
|
|
|
self.axis = .horizontal
|
|
|
|
self.spacing = MediaGalleryCellView.kSpacingPts
|
2018-11-05 22:43:46 +01:00
|
|
|
case 3:
|
|
|
|
// x
|
|
|
|
// X
|
|
|
|
// x
|
|
|
|
// Big on left, 2 small on right.
|
|
|
|
let smallImageSize = (maxMessageWidth - MediaGalleryCellView.kSpacingPts * 2) / 3
|
|
|
|
let bigImageSize = smallImageSize * 2 + MediaGalleryCellView.kSpacingPts
|
|
|
|
|
|
|
|
guard let leftItemView = itemViews.first else {
|
|
|
|
owsFailDebug("Missing view")
|
|
|
|
return
|
|
|
|
}
|
2018-11-06 15:20:22 +01:00
|
|
|
autoSet(viewSize: bigImageSize, ofViews: [leftItemView])
|
|
|
|
addArrangedSubview(leftItemView)
|
2018-11-05 22:43:46 +01:00
|
|
|
|
|
|
|
let rightViews = Array(itemViews[1..<3])
|
2018-11-06 15:20:22 +01:00
|
|
|
addArrangedSubview(newRow(rowViews: rightViews,
|
|
|
|
axis: .vertical,
|
|
|
|
viewSize: smallImageSize))
|
|
|
|
self.axis = .horizontal
|
|
|
|
self.spacing = MediaGalleryCellView.kSpacingPts
|
2018-11-05 22:43:46 +01:00
|
|
|
default:
|
|
|
|
// X X
|
|
|
|
// xxx
|
|
|
|
// 2 big on top, 3 small on bottom.
|
|
|
|
let bigImageSize = (maxMessageWidth - MediaGalleryCellView.kSpacingPts) / 2
|
|
|
|
let smallImageSize = (maxMessageWidth - MediaGalleryCellView.kSpacingPts * 2) / 3
|
|
|
|
|
|
|
|
let topViews = Array(itemViews[0..<2])
|
2018-11-06 15:20:22 +01:00
|
|
|
addArrangedSubview(newRow(rowViews: topViews,
|
|
|
|
axis: .horizontal,
|
|
|
|
viewSize: bigImageSize))
|
2018-11-05 22:43:46 +01:00
|
|
|
|
|
|
|
let bottomViews = Array(itemViews[2..<5])
|
2018-11-06 15:20:22 +01:00
|
|
|
addArrangedSubview(newRow(rowViews: bottomViews,
|
|
|
|
axis: .horizontal,
|
|
|
|
viewSize: smallImageSize))
|
|
|
|
|
|
|
|
self.axis = .vertical
|
|
|
|
self.spacing = MediaGalleryCellView.kSpacingPts
|
2018-11-06 19:06:11 +01:00
|
|
|
|
|
|
|
if items.count > MediaGalleryCellView.kMaxItems {
|
|
|
|
guard let lastView = bottomViews.last else {
|
|
|
|
owsFailDebug("Missing lastView")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let tintView = UIView()
|
|
|
|
tintView.backgroundColor = UIColor(white: 0, alpha: 0.4)
|
|
|
|
lastView.addSubview(tintView)
|
|
|
|
tintView.autoPinEdgesToSuperviewEdges()
|
|
|
|
|
|
|
|
let moreCount = max(1, items.count - MediaGalleryCellView.kMaxItems)
|
|
|
|
let moreCountText = OWSFormat.formatInt(Int32(moreCount))
|
|
|
|
let moreText = String(format: NSLocalizedString("MEDIA_GALLERY_MORE_ITEMS_FORMAT",
|
|
|
|
comment: "Format for the 'more items' indicator for media galleries. Embeds {{the number of additional items}}."), moreCountText)
|
|
|
|
let moreLabel = UILabel()
|
|
|
|
moreLabel.text = moreText
|
|
|
|
moreLabel.textColor = UIColor.ows_white
|
|
|
|
// We don't want to use dynamic text here.
|
|
|
|
moreLabel.font = UIFont.systemFont(ofSize: 24)
|
|
|
|
lastView.addSubview(moreLabel)
|
|
|
|
moreLabel.autoCenterInSuperview()
|
|
|
|
}
|
2018-11-06 15:20:22 +01:00
|
|
|
}
|
|
|
|
}
|
2018-11-05 22:43:46 +01:00
|
|
|
|
2018-11-06 15:20:22 +01:00
|
|
|
private func autoSet(viewSize: CGFloat,
|
|
|
|
ofViews views: [ConversationMediaView]
|
|
|
|
) {
|
|
|
|
for itemView in views {
|
|
|
|
itemView.autoSetDimensions(to: CGSize(width: viewSize, height: viewSize))
|
2018-11-05 22:43:46 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-06 15:20:22 +01:00
|
|
|
private func newRow(rowViews: [ConversationMediaView],
|
|
|
|
axis: NSLayoutConstraint.Axis,
|
|
|
|
viewSize: CGFloat) -> UIStackView {
|
|
|
|
autoSet(viewSize: viewSize, ofViews: rowViews)
|
|
|
|
return newRow(rowViews: rowViews, axis: axis)
|
|
|
|
}
|
|
|
|
|
|
|
|
private func newRow(rowViews: [ConversationMediaView],
|
|
|
|
axis: NSLayoutConstraint.Axis) -> UIStackView {
|
|
|
|
let stackView = UIStackView(arrangedSubviews: rowViews)
|
|
|
|
stackView.axis = axis
|
|
|
|
stackView.spacing = MediaGalleryCellView.kSpacingPts
|
|
|
|
return stackView
|
|
|
|
}
|
|
|
|
|
2018-11-05 22:43:46 +01:00
|
|
|
@objc
|
|
|
|
public func loadMedia() {
|
|
|
|
for itemView in itemViews {
|
|
|
|
itemView.loadMedia()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc
|
|
|
|
public func unloadMedia() {
|
|
|
|
for itemView in itemViews {
|
|
|
|
itemView.unloadMedia()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@available(*, unavailable, message: "use other init() instead.")
|
2018-11-06 15:20:22 +01:00
|
|
|
required public init(coder aDecoder: NSCoder) {
|
2018-11-05 22:43:46 +01:00
|
|
|
notImplemented()
|
|
|
|
}
|
|
|
|
|
|
|
|
private class func itemsToDisplay(forItems items: [ConversationMediaGalleryItem]) -> [ConversationMediaGalleryItem] {
|
2018-11-06 19:06:11 +01:00
|
|
|
// TODO: Unless design changes, we want to display
|
|
|
|
// items which are still downloading and invalid
|
|
|
|
// items.
|
|
|
|
let validItems = items
|
2018-11-05 22:43:46 +01:00
|
|
|
guard validItems.count < kMaxItems else {
|
|
|
|
return Array(validItems[0..<kMaxItems])
|
|
|
|
}
|
|
|
|
return validItems
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc
|
|
|
|
public class func layoutSize(forMaxMessageWidth maxMessageWidth: CGFloat,
|
|
|
|
items: [ConversationMediaGalleryItem]) -> CGSize {
|
|
|
|
let itemCount = itemsToDisplay(forItems: items).count
|
|
|
|
switch itemCount {
|
|
|
|
case 0, 1, 4:
|
|
|
|
// Square
|
|
|
|
return CGSize(width: maxMessageWidth, height: maxMessageWidth)
|
|
|
|
case 2:
|
|
|
|
// X X
|
|
|
|
// side-by-side.
|
|
|
|
let imageSize = (maxMessageWidth - kSpacingPts) / 2
|
|
|
|
return CGSize(width: maxMessageWidth, height: imageSize)
|
|
|
|
case 3:
|
|
|
|
// x
|
|
|
|
// X
|
|
|
|
// x
|
|
|
|
// Big on left, 2 small on right.
|
|
|
|
let smallImageSize = (maxMessageWidth - kSpacingPts * 2) / 3
|
|
|
|
let bigImageSize = smallImageSize * 2 + kSpacingPts
|
|
|
|
return CGSize(width: maxMessageWidth, height: bigImageSize)
|
|
|
|
default:
|
|
|
|
// X X
|
|
|
|
// xxx
|
|
|
|
// 2 big on top, 3 small on bottom.
|
|
|
|
let bigImageSize = (maxMessageWidth - kSpacingPts) / 2
|
|
|
|
let smallImageSize = (maxMessageWidth - kSpacingPts * 2) / 3
|
|
|
|
return CGSize(width: maxMessageWidth, height: bigImageSize + smallImageSize + kSpacingPts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|