243 lines
8.3 KiB
Swift
243 lines
8.3 KiB
Swift
//
|
|
// GroupCallVideoLayout.swift
|
|
// TUICallKit
|
|
//
|
|
// Created by vincepzhang on 2023/2/15.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
class GroupCallVideoLayout: UIView, UICollectionViewDelegate, UICollectionViewDataSource {
|
|
|
|
let selfCallStatusObserver = Observer()
|
|
let isCameraOpenObserver = Observer()
|
|
let selfUser = TUICallState.instance.selfUser.value
|
|
|
|
var allUserList = [User]()
|
|
|
|
lazy var calleeCollectionView = {
|
|
let flowLayout = GroupCallVideoFlowLayout()
|
|
let calleeCollectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: flowLayout)
|
|
calleeCollectionView.delegate = self
|
|
calleeCollectionView.dataSource = self
|
|
calleeCollectionView.showsVerticalScrollIndicator = false
|
|
calleeCollectionView.showsHorizontalScrollIndicator = false
|
|
calleeCollectionView.backgroundColor = UIColor.clear
|
|
return calleeCollectionView
|
|
}()
|
|
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
backgroundColor = UIColor.clear
|
|
processUserList(remoteUserList: TUICallState.instance.remoteUserList.value)
|
|
registerObserveState()
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
deinit {
|
|
TUICallState.instance.isCameraOpen.removeObserver(isCameraOpenObserver)
|
|
TUICallState.instance.remoteUserList.removeObserver(selfCallStatusObserver)
|
|
|
|
showLargeViewIndex = -1
|
|
|
|
for view in subviews {
|
|
view.removeFromSuperview()
|
|
}
|
|
}
|
|
|
|
// MARK: UI Specification Processing
|
|
private var isViewReady: Bool = false
|
|
override func didMoveToWindow() {
|
|
super.didMoveToWindow()
|
|
if isViewReady { return }
|
|
constructViewHierarchy()
|
|
activateConstraints()
|
|
bindInteraction()
|
|
showHistoryLargeView()
|
|
isViewReady = true
|
|
}
|
|
|
|
func constructViewHierarchy() {
|
|
addSubview(calleeCollectionView)
|
|
}
|
|
|
|
func activateConstraints() {
|
|
calleeCollectionView.snp.makeConstraints { make in
|
|
make.edges.equalTo(self)
|
|
}
|
|
}
|
|
|
|
func bindInteraction() {
|
|
for i in 0..<9 {
|
|
calleeCollectionView.register(GroupCallVideoCell.self, forCellWithReuseIdentifier: "GroupCallVideoCell_\(i)")
|
|
}
|
|
}
|
|
|
|
// MARK: Set TUICallState showLargeViewUserId
|
|
func setShowLargeViewUserId(userId: String) {
|
|
TUICallState.instance.showLargeViewUserId.value = userId
|
|
}
|
|
|
|
// MARK: Register TUICallState Observer && Update UI
|
|
func registerObserveState() {
|
|
remoteUserChanged()
|
|
isCameraOpenChanged()
|
|
}
|
|
|
|
func remoteUserChanged() {
|
|
TUICallState.instance.remoteUserList.addObserver(selfCallStatusObserver, closure: { [weak self] newValue, _ in
|
|
guard let self = self else { return }
|
|
self.processUserList(remoteUserList: newValue)
|
|
self.calleeCollectionView.reloadData()
|
|
})
|
|
}
|
|
|
|
func processUserList(remoteUserList: [User]) {
|
|
allUserList.removeAll()
|
|
selfUser.index = 0
|
|
allUserList.append(selfUser)
|
|
|
|
for (index, value) in remoteUserList.enumerated() {
|
|
value.index = index + 1
|
|
allUserList.append(value)
|
|
}
|
|
}
|
|
|
|
func isCameraOpenChanged() {
|
|
TUICallState.instance.isCameraOpen.addObserver(isCameraOpenObserver, closure: { [weak self] newValue, _ in
|
|
guard let self = self else { return }
|
|
if newValue {
|
|
self.showMySelfAsLargeView()
|
|
}
|
|
})
|
|
}
|
|
|
|
func showMySelfAsLargeView() {
|
|
var row = -1
|
|
for (index, element) in allUserList.enumerated() where element.id.value == selfUser.id.value {
|
|
row = index
|
|
}
|
|
if row >= 0 && selfUser.id.value != TUICallState.instance.showLargeViewUserId.value {
|
|
let indexPath = IndexPath(row: row, section: 0)
|
|
performUpdates(indexPath: indexPath)
|
|
}
|
|
}
|
|
|
|
func showHistoryLargeView() {
|
|
var row = -1
|
|
for (index, element) in allUserList.enumerated() where element.id.value == TUICallState.instance.showLargeViewUserId.value {
|
|
row = index
|
|
}
|
|
if row >= 0 && row < allUserList.count {
|
|
let indexPath = IndexPath(row: row, section: 0)
|
|
performUpdates(indexPath: indexPath)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: UICollectionViewDelegate, UICollectionViewDataSource
|
|
extension GroupCallVideoLayout {
|
|
|
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
|
return allUserList.count
|
|
}
|
|
|
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "GroupCallVideoCell_\(indexPath.row)",
|
|
for: indexPath) as! GroupCallVideoCell
|
|
cell.initCell(user: allUserList[indexPath.row])
|
|
return cell
|
|
}
|
|
|
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
|
performUpdates(indexPath: indexPath)
|
|
}
|
|
|
|
func performUpdates(indexPath: IndexPath) {
|
|
let count = allUserList.count
|
|
let remoteUpdates = getRemoteUpdates(indexPath: indexPath)
|
|
|
|
var firstBigFlag = false
|
|
if count >= 2 && count <= 4 && indexPath.row != showLargeViewIndex {
|
|
firstBigFlag = true
|
|
}
|
|
|
|
showLargeViewIndex = (showLargeViewIndex == indexPath.row) ? -1 : indexPath.row
|
|
if firstBigFlag {
|
|
showLargeViewIndex = 0
|
|
}
|
|
|
|
setShowLargeViewUserId(userId: (showLargeViewIndex >= 0) ? allUserList[indexPath.row].id.value : " ")
|
|
|
|
// Animate all other update types together.
|
|
calleeCollectionView.cancelInteractiveMovement()
|
|
calleeCollectionView.performBatchUpdates({
|
|
var deletes = [Int]()
|
|
var inserts = [(user:User, index:Int)]()
|
|
|
|
for update in remoteUpdates {
|
|
switch update {
|
|
case let .delete(index):
|
|
calleeCollectionView.deleteItems(at: [IndexPath(item: index, section: 0)])
|
|
deletes.append(index)
|
|
|
|
case let .insert(user, index):
|
|
calleeCollectionView.insertItems(at: [IndexPath(item: index, section: 0)])
|
|
inserts.append((user, index))
|
|
|
|
case let .move(fromIndex, toIndex):
|
|
calleeCollectionView.moveItem(at: IndexPath(item: fromIndex, section: 0),
|
|
to: IndexPath(item: toIndex, section: 0))
|
|
deletes.append(fromIndex)
|
|
inserts.append((allUserList[fromIndex], toIndex))
|
|
}
|
|
}
|
|
|
|
for deletedIndex in deletes.sorted().reversed() {
|
|
allUserList.remove(at: deletedIndex)
|
|
}
|
|
|
|
let sortedInserts = inserts.sorted(by: { (userA, userB) -> Bool in
|
|
return userA.index <= userB.index
|
|
})
|
|
|
|
for insertion in sortedInserts {
|
|
if insertion.index >= allUserList.startIndex && insertion.index <= allUserList.endIndex {
|
|
allUserList.insert(insertion.user, at: insertion.index)
|
|
}
|
|
}
|
|
}) { [weak self] _ in
|
|
guard let self = self else { return }
|
|
self.calleeCollectionView.endInteractiveMovement()
|
|
}
|
|
}
|
|
|
|
func getRemoteUpdates(indexPath: IndexPath) -> [UserUpdate] {
|
|
let count = allUserList.count
|
|
|
|
if count < 2 || count > 4 || indexPath.row >= count {
|
|
return [UserUpdate]()
|
|
}
|
|
|
|
if indexPath.row == showLargeViewIndex {
|
|
return [
|
|
UserUpdate.move(0, allUserList[indexPath.row].index)
|
|
]
|
|
}
|
|
|
|
if count == 2 || allUserList[0].index == 0 {
|
|
return [
|
|
UserUpdate.move(indexPath.row, 0)
|
|
]
|
|
}
|
|
|
|
return [
|
|
UserUpdate.move(0, allUserList[0].index),
|
|
UserUpdate.move(indexPath.row, 0)
|
|
]
|
|
}
|
|
}
|