session-ios/Session/Open Groups/OpenGroupSuggestionGrid.swift

188 lines
7.2 KiB
Swift

import PromiseKit
import NVActivityIndicatorView
final class OpenGroupSuggestionGrid : UIView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private let maxWidth: CGFloat
private var rooms: [OpenGroupAPIV2.Info] = [] { didSet { update() } }
private var heightConstraint: NSLayoutConstraint!
var delegate: OpenGroupSuggestionGridDelegate?
// MARK: UI Components
private lazy var layout: UICollectionViewFlowLayout = {
let result = UICollectionViewFlowLayout()
result.minimumLineSpacing = 0
result.minimumInteritemSpacing = 0
return result
}()
private lazy var collectionView: UICollectionView = {
let result = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
result.register(Cell.self, forCellWithReuseIdentifier: Cell.identifier)
result.backgroundColor = .clear
result.isScrollEnabled = false
result.dataSource = self
result.delegate = self
return result
}()
private lazy var spinner: NVActivityIndicatorView = {
let result = NVActivityIndicatorView(frame: CGRect.zero, type: .circleStrokeSpin, color: Colors.text, padding: nil)
result.set(.width, to: OpenGroupSuggestionGrid.cellHeight)
result.set(.height, to: OpenGroupSuggestionGrid.cellHeight)
return result
}()
// MARK: Settings
private static let cellHeight: CGFloat = 40
private static let separatorWidth = 1 / UIScreen.main.scale
// MARK: Initialization
init(maxWidth: CGFloat) {
self.maxWidth = maxWidth
super.init(frame: CGRect.zero)
initialize()
}
override init(frame: CGRect) {
preconditionFailure("Use init(maxWidth:) instead.")
}
required init?(coder: NSCoder) {
preconditionFailure("Use init(maxWidth:) instead.")
}
private func initialize() {
addSubview(collectionView)
collectionView.pin(to: self)
addSubview(spinner)
spinner.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.top ], to: self)
spinner.startAnimating()
heightConstraint = set(.height, to: OpenGroupSuggestionGrid.cellHeight)
widthAnchor.constraint(greaterThanOrEqualToConstant: OpenGroupSuggestionGrid.cellHeight).isActive = true
if OpenGroupAPIV2.defaultRoomsPromise == nil {
OpenGroupAPIV2.getDefaultRoomsIfNeeded()
}
let _ = OpenGroupAPIV2.defaultRoomsPromise?.done { [weak self] rooms in
self?.rooms = rooms
}
}
// MARK: Updating
private func update() {
spinner.stopAnimating()
spinner.isHidden = true
let roomCount = min(rooms.count, 8) // Cap to a maximum of 8 (4 rows of 2)
let height = OpenGroupSuggestionGrid.cellHeight * ceil(CGFloat(roomCount) / 2)
heightConstraint.constant = height
collectionView.reloadData()
}
// MARK: Layout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: maxWidth / 2, height: OpenGroupSuggestionGrid.cellHeight)
}
// MARK: Data Source
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return min(rooms.count, 8) // Cap to a maximum of 8 (4 rows of 2)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Cell.identifier, for: indexPath) as! Cell
cell.room = rooms[indexPath.item]
return cell
}
// MARK: Interaction
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let room = rooms[indexPath.item]
delegate?.join(room)
}
}
// MARK: Cell
extension OpenGroupSuggestionGrid {
fileprivate final class Cell : UICollectionViewCell {
var room: OpenGroupAPIV2.Info? { didSet { update() } }
static let identifier = "OpenGroupSuggestionGridCell"
private lazy var snContentView: UIView = {
let result = UIView()
result.backgroundColor = Colors.navigationBarBackground
result.set(.height, to: Cell.contentViewHeight)
result.layer.cornerRadius = Cell.contentViewCornerRadius
return result
}()
private lazy var imageView: UIImageView = {
let result = UIImageView()
let size: CGFloat = 24
result.set(.width, to: size)
result.set(.height, to: size)
result.layer.cornerRadius = size / 2
result.clipsToBounds = true
return result
}()
private lazy var label: UILabel = {
let result = UILabel()
result.textColor = Colors.text
result.font = .systemFont(ofSize: Values.smallFontSize)
result.lineBreakMode = .byTruncatingTail
return result
}()
private static let contentViewInset: CGFloat = 4
private static var contentViewHeight: CGFloat { OpenGroupSuggestionGrid.cellHeight - 2 * contentViewInset }
private static var contentViewCornerRadius: CGFloat { contentViewHeight / 2 }
override init(frame: CGRect) {
super.init(frame: frame)
setUpViewHierarchy()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setUpViewHierarchy()
}
private func setUpViewHierarchy() {
addSubview(snContentView)
let stackView = UIStackView(arrangedSubviews: [ imageView, label ])
stackView.axis = .horizontal
stackView.spacing = Values.smallSpacing
snContentView.addSubview(stackView)
stackView.center(.vertical, in: snContentView)
stackView.pin(.leading, to: .leading, of: snContentView, withInset: 4)
snContentView.trailingAnchor.constraint(greaterThanOrEqualTo: stackView.trailingAnchor, constant: Values.smallSpacing).isActive = true
snContentView.pin(to: self, withInset: Cell.contentViewInset)
}
override func layoutSubviews() {
super.layoutSubviews()
let newPath = UIBezierPath(roundedRect: snContentView.bounds, cornerRadius: Cell.contentViewCornerRadius).cgPath
snContentView.layer.shadowPath = newPath
snContentView.layer.shadowColor = UIColor.black.cgColor
snContentView.layer.shadowOffset = CGSize.zero
snContentView.layer.shadowOpacity = isLightMode ? 0.2 : 0.6
snContentView.layer.shadowRadius = 2
}
private func update() {
guard let room = room else { return }
let promise = OpenGroupAPIV2.getGroupImage(for: room.id, on: OpenGroupAPIV2.defaultServer)
imageView.image = given(promise.value) { UIImage(data: $0)! }
imageView.isHidden = (imageView.image == nil)
label.text = room.name
}
}
}
// MARK: Delegate
protocol OpenGroupSuggestionGridDelegate {
func join(_ room: OpenGroupAPIV2.Info)
}