mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
159 lines
5.6 KiB
Swift
159 lines
5.6 KiB
Swift
//
|
|
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import SignalRingRTC
|
|
|
|
protocol GroupCallVideoGridLayoutDelegate: AnyObject {
|
|
var maxColumns: Int { get }
|
|
var maxRows: Int { get }
|
|
var maxItems: Int { get }
|
|
|
|
func deviceState(for index: Int) -> RemoteDeviceState?
|
|
}
|
|
|
|
class GroupCallVideoGridLayout: UICollectionViewLayout {
|
|
|
|
public weak var delegate: GroupCallVideoGridLayoutDelegate?
|
|
|
|
private var itemAttributesMap = [Int: UICollectionViewLayoutAttributes]()
|
|
|
|
private var contentSize = CGSize.zero
|
|
|
|
// MARK: Initializers and Factory Methods
|
|
|
|
@available(*, unavailable, message: "use other constructor instead.")
|
|
required init?(coder aDecoder: NSCoder) {
|
|
notImplemented()
|
|
}
|
|
|
|
override init() {
|
|
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 let delegate = delegate else { return }
|
|
|
|
let vInset: CGFloat = 6
|
|
let hInset: CGFloat = 6
|
|
let vSpacing: CGFloat = 6
|
|
let hSpacing: CGFloat = 6
|
|
|
|
let maxColumns = delegate.maxColumns
|
|
let maxRows = delegate.maxRows
|
|
|
|
let numberOfItems = min(collectionView.numberOfItems(inSection: 0), delegate.maxItems)
|
|
|
|
guard numberOfItems > 0 else { return }
|
|
|
|
// We evenly distribute items across rows, up to the max
|
|
// column count. If an item is alone on a row, it should
|
|
// expand across all columns.
|
|
|
|
let possibleGrids = (1...maxColumns).reduce(
|
|
into: [(rows: Int, columns: Int)]()
|
|
) { result, columns in
|
|
let rows = Int(ceil(CGFloat(numberOfItems) / CGFloat(columns)))
|
|
if let previousRows = result.last?.rows, previousRows == rows { return }
|
|
result.append((rows, columns))
|
|
}.filter { $0.columns <= maxColumns && $0.rows <= maxRows }
|
|
.sorted { lhs, rhs in
|
|
// We prefer to render square grids (e.g. 2x2, 3x3, etc.) but it's
|
|
// not always possible depending on how many items we have available.
|
|
// If a square aspect ratio is not possible, we'll defer to having more
|
|
// rows than columns.
|
|
let lhsDistanceFromSquare = CGFloat(lhs.rows) / CGFloat(lhs.columns) - 1
|
|
let rhsDistanceFromSquare = CGFloat(rhs.rows) / CGFloat(rhs.columns) - 1
|
|
|
|
if lhsDistanceFromSquare >= 0 && rhsDistanceFromSquare >= 0 {
|
|
return lhsDistanceFromSquare < rhsDistanceFromSquare
|
|
} else {
|
|
return lhsDistanceFromSquare > rhsDistanceFromSquare
|
|
}
|
|
}
|
|
|
|
guard let (numberOfRows, numberOfColumns) = possibleGrids.first else { return owsFailDebug("missing grid") }
|
|
|
|
let totalViewWidth = collectionView.width()
|
|
let totalViewHeight = collectionView.height()
|
|
|
|
let verticalSpacersWidth = (2 * vInset) + (vSpacing * (CGFloat(numberOfRows) - 1))
|
|
let verticalCellSpace = totalViewHeight - verticalSpacersWidth
|
|
|
|
let rowHeight = verticalCellSpace / CGFloat(numberOfRows)
|
|
|
|
// The last row may have less columns than the previous rows,
|
|
// if there is an odd number of videos. Each row should always
|
|
// expand the full width of the collection view.
|
|
var columnWidthPerRow = [CGFloat]()
|
|
for row in 1...numberOfRows {
|
|
let numberOfColumnsForRow: Int
|
|
if row == numberOfRows {
|
|
numberOfColumnsForRow = numberOfItems - ((row - 1) * numberOfColumns)
|
|
} else {
|
|
numberOfColumnsForRow = numberOfColumns
|
|
}
|
|
|
|
let horizontalSpacersWidth = (2 * hInset) + (hSpacing * (CGFloat(numberOfColumnsForRow) - 1))
|
|
let horizontalCellSpace = totalViewWidth - horizontalSpacersWidth
|
|
let columnWidth = horizontalCellSpace / CGFloat(numberOfColumnsForRow)
|
|
|
|
columnWidthPerRow.append(columnWidth)
|
|
}
|
|
|
|
for index in 0..<numberOfItems {
|
|
let indexPath = NSIndexPath(item: index, section: 0)
|
|
let itemAttributes = UICollectionViewLayoutAttributes(forCellWith: indexPath as IndexPath)
|
|
|
|
let row = ceil(CGFloat(index + 1) / CGFloat(numberOfColumns)) - 1
|
|
let yPosition = (row * rowHeight) + vInset + (CGFloat(row) * vSpacing)
|
|
|
|
let columnWidth = columnWidthPerRow[Int(row)]
|
|
|
|
let column = CGFloat(index % numberOfColumns)
|
|
let xPosition = (column * columnWidth) + vInset + (CGFloat(column) * vSpacing)
|
|
|
|
itemAttributes.frame = CGRect(x: xPosition, y: yPosition, width: columnWidth, height: rowHeight)
|
|
itemAttributesMap[index] = itemAttributes
|
|
}
|
|
|
|
contentSize = collectionView.frame.size
|
|
}
|
|
|
|
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? {
|
|
return itemAttributesMap[indexPath.row]
|
|
}
|
|
|
|
override var collectionViewContentSize: CGSize {
|
|
return contentSize
|
|
}
|
|
|
|
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
|
|
return true
|
|
}
|
|
}
|