mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Video chat UI
This commit is contained in:
parent
1e56520356
commit
f203693f89
@ -134,10 +134,14 @@ final class VideoChatParticipantThumbnailComponent: Component {
|
||||
if transition.animation.isImmediate {
|
||||
speakingAlphaTransition = .immediate
|
||||
} else {
|
||||
if !wasSpeaking {
|
||||
speakingAlphaTransition = .easeInOut(duration: 0.1)
|
||||
if let previousComponent, previousComponent.isSelected == component.isSelected {
|
||||
if !wasSpeaking {
|
||||
speakingAlphaTransition = .easeInOut(duration: 0.1)
|
||||
} else {
|
||||
speakingAlphaTransition = .easeInOut(duration: 0.25)
|
||||
}
|
||||
} else {
|
||||
speakingAlphaTransition = .easeInOut(duration: 0.25)
|
||||
speakingAlphaTransition = .immediate
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,8 +172,7 @@ final class VideoChatParticipantThumbnailComponent: Component {
|
||||
transition: transition,
|
||||
component: AnyComponent(VideoChatMuteIconComponent(
|
||||
color: .white,
|
||||
isFilled: true,
|
||||
isMuted: component.participant.muteState != nil
|
||||
content: component.isPresentation ? .screenshare : .mute(isFilled: true, isMuted: component.participant.muteState != nil && !component.isSpeaking)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 36.0, height: 36.0)
|
||||
@ -182,8 +185,6 @@ final class VideoChatParticipantThumbnailComponent: Component {
|
||||
transition.setPosition(view: muteStatusView, position: muteStatusFrame.center)
|
||||
transition.setBounds(view: muteStatusView, bounds: CGRect(origin: CGPoint(), size: muteStatusFrame.size))
|
||||
transition.setScale(view: muteStatusView, scale: 0.65)
|
||||
|
||||
speakingAlphaTransition.setTintColor(layer: muteStatusView.iconView.layer, color: component.isSpeaking ? UIColor(rgb: 0x33C758) : .white)
|
||||
}
|
||||
|
||||
let titleSize = self.title.update(
|
||||
@ -203,8 +204,6 @@ final class VideoChatParticipantThumbnailComponent: Component {
|
||||
}
|
||||
transition.setPosition(view: titleView, position: titleFrame.origin)
|
||||
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||
|
||||
speakingAlphaTransition.setTintColor(layer: titleView.layer, color: component.isSpeaking ? UIColor(rgb: 0x33C758) : .white)
|
||||
}
|
||||
|
||||
if let videoDescription = component.isPresentation ? component.participant.presentationDescription : component.participant.videoDescription {
|
||||
@ -330,11 +329,13 @@ final class VideoChatParticipantThumbnailComponent: Component {
|
||||
} else {
|
||||
selectedBorderView = UIImageView()
|
||||
self.selectedBorderView = selectedBorderView
|
||||
selectedBorderView.alpha = 0.0
|
||||
self.addSubview(selectedBorderView)
|
||||
selectedBorderView.image = View.selectedBorderImage
|
||||
|
||||
selectedBorderView.frame = CGRect(origin: CGPoint(), size: availableSize)
|
||||
|
||||
speakingAlphaTransition.setAlpha(view: selectedBorderView, alpha: 1.0)
|
||||
ComponentTransition.immediate.setTintColor(layer: selectedBorderView.layer, color: component.isSpeaking ? UIColor(rgb: 0x33C758) : component.theme.list.itemAccentColor)
|
||||
}
|
||||
} else if let selectedBorderView = self.selectedBorderView {
|
||||
|
@ -6,50 +6,49 @@ import MultilineTextComponent
|
||||
import TelegramPresentationData
|
||||
import AppBundle
|
||||
import LottieComponent
|
||||
import BundleIconComponent
|
||||
|
||||
final class VideoChatMuteIconComponent: Component {
|
||||
enum Content: Equatable {
|
||||
case mute(isFilled: Bool, isMuted: Bool)
|
||||
case screenshare
|
||||
}
|
||||
|
||||
let color: UIColor
|
||||
let isFilled: Bool
|
||||
let isMuted: Bool
|
||||
let content: Content
|
||||
|
||||
init(
|
||||
color: UIColor,
|
||||
isFilled: Bool,
|
||||
isMuted: Bool
|
||||
content: Content
|
||||
) {
|
||||
self.color = color
|
||||
self.isFilled = isFilled
|
||||
self.isMuted = isMuted
|
||||
self.content = content
|
||||
}
|
||||
|
||||
static func ==(lhs: VideoChatMuteIconComponent, rhs: VideoChatMuteIconComponent) -> Bool {
|
||||
if lhs.color != rhs.color {
|
||||
return false
|
||||
}
|
||||
if lhs.isFilled != rhs.isFilled {
|
||||
return false
|
||||
}
|
||||
if lhs.isMuted != rhs.isMuted {
|
||||
if lhs.content != rhs.content {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: HighlightTrackingButton {
|
||||
private let icon: VoiceChatMicrophoneNode
|
||||
private var icon: VoiceChatMicrophoneNode?
|
||||
private var scheenshareIcon: ComponentView<Empty>?
|
||||
|
||||
private var component: VideoChatMuteIconComponent?
|
||||
private var isUpdating: Bool = false
|
||||
|
||||
private var contentImage: UIImage?
|
||||
|
||||
var iconView: UIView {
|
||||
return self.icon.view
|
||||
var iconView: UIView? {
|
||||
return self.icon?.view
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.icon = VoiceChatMicrophoneNode()
|
||||
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
@ -65,14 +64,59 @@ final class VideoChatMuteIconComponent: Component {
|
||||
|
||||
self.component = component
|
||||
|
||||
let animationSize = availableSize
|
||||
|
||||
let animationFrame = animationSize.centered(in: CGRect(origin: CGPoint(), size: availableSize))
|
||||
if self.icon.view.superview == nil {
|
||||
self.addSubview(self.icon.view)
|
||||
if case let .mute(isFilled, isMuted) = component.content {
|
||||
let icon: VoiceChatMicrophoneNode
|
||||
if let current = self.icon {
|
||||
icon = current
|
||||
} else {
|
||||
icon = VoiceChatMicrophoneNode()
|
||||
self.icon = icon
|
||||
self.addSubview(icon.view)
|
||||
}
|
||||
|
||||
let animationSize = availableSize
|
||||
let animationFrame = animationSize.centered(in: CGRect(origin: CGPoint(), size: availableSize))
|
||||
transition.setFrame(view: icon.view, frame: animationFrame)
|
||||
icon.update(state: VoiceChatMicrophoneNode.State(muted: isMuted, filled: isFilled, color: component.color), animated: !transition.animation.isImmediate)
|
||||
} else {
|
||||
if let icon = self.icon {
|
||||
self.icon = nil
|
||||
icon.view.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
if case .screenshare = component.content {
|
||||
let scheenshareIcon: ComponentView<Empty>
|
||||
if let current = self.scheenshareIcon {
|
||||
scheenshareIcon = current
|
||||
} else {
|
||||
scheenshareIcon = ComponentView()
|
||||
self.scheenshareIcon = scheenshareIcon
|
||||
}
|
||||
let scheenshareIconSize = scheenshareIcon.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(BundleIconComponent(
|
||||
name: "Call/StatusScreen",
|
||||
tintColor: component.color
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let scheenshareIconFrame = scheenshareIconSize.centered(in: CGRect(origin: CGPoint(), size: availableSize))
|
||||
if let scheenshareIconView = scheenshareIcon.view {
|
||||
if scheenshareIconView.superview == nil {
|
||||
self.addSubview(scheenshareIconView)
|
||||
}
|
||||
transition.setPosition(view: scheenshareIconView, position: scheenshareIconFrame.center)
|
||||
transition.setBounds(view: scheenshareIconView, bounds: CGRect(origin: CGPoint(), size: scheenshareIconFrame.size))
|
||||
transition.setScale(view: scheenshareIconView, scale: 1.5)
|
||||
}
|
||||
} else {
|
||||
if let scheenshareIcon = self.scheenshareIcon {
|
||||
self.scheenshareIcon = nil
|
||||
scheenshareIcon.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
transition.setFrame(view: self.icon.view, frame: animationFrame)
|
||||
self.icon.update(state: VoiceChatMicrophoneNode.State(muted: component.isMuted, filled: component.isFilled, color: component.color), animated: !transition.animation.isImmediate)
|
||||
|
||||
return availableSize
|
||||
}
|
||||
|
@ -61,8 +61,7 @@ final class VideoChatParticipantStatusComponent: Component {
|
||||
transition: transition,
|
||||
component: AnyComponent(VideoChatMuteIconComponent(
|
||||
color: .white,
|
||||
isFilled: false,
|
||||
isMuted: component.isMuted && !component.isSpeaking
|
||||
content: .mute(isFilled: false, isMuted: component.isMuted && !component.isSpeaking)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 36.0, height: 36.0)
|
||||
@ -80,7 +79,9 @@ final class VideoChatParticipantStatusComponent: Component {
|
||||
} else {
|
||||
tintTransition = .immediate
|
||||
}
|
||||
tintTransition.setTintColor(layer: muteStatusView.iconView.layer, color: component.isSpeaking ? UIColor(rgb: 0x33C758) : UIColor(white: 1.0, alpha: 0.4))
|
||||
if let iconView = muteStatusView.iconView {
|
||||
tintTransition.setTintColor(layer: iconView.layer, color: component.isSpeaking ? UIColor(rgb: 0x33C758) : UIColor(white: 1.0, alpha: 0.4))
|
||||
}
|
||||
}
|
||||
|
||||
return size
|
||||
|
@ -40,7 +40,9 @@ final class VideoChatParticipantVideoComponent: Component {
|
||||
let isPresentation: Bool
|
||||
let isSpeaking: Bool
|
||||
let isExpanded: Bool
|
||||
let bottomInset: CGFloat
|
||||
let isUIHidden: Bool
|
||||
let contentInsets: UIEdgeInsets
|
||||
let controlInsets: UIEdgeInsets
|
||||
weak var rootVideoLoadingEffectView: VideoChatVideoLoadingEffectView?
|
||||
let action: (() -> Void)?
|
||||
|
||||
@ -50,7 +52,9 @@ final class VideoChatParticipantVideoComponent: Component {
|
||||
isPresentation: Bool,
|
||||
isSpeaking: Bool,
|
||||
isExpanded: Bool,
|
||||
bottomInset: CGFloat,
|
||||
isUIHidden: Bool,
|
||||
contentInsets: UIEdgeInsets,
|
||||
controlInsets: UIEdgeInsets,
|
||||
rootVideoLoadingEffectView: VideoChatVideoLoadingEffectView?,
|
||||
action: (() -> Void)?
|
||||
) {
|
||||
@ -59,7 +63,9 @@ final class VideoChatParticipantVideoComponent: Component {
|
||||
self.isPresentation = isPresentation
|
||||
self.isSpeaking = isSpeaking
|
||||
self.isExpanded = isExpanded
|
||||
self.bottomInset = bottomInset
|
||||
self.isUIHidden = isUIHidden
|
||||
self.contentInsets = contentInsets
|
||||
self.controlInsets = controlInsets
|
||||
self.rootVideoLoadingEffectView = rootVideoLoadingEffectView
|
||||
self.action = action
|
||||
}
|
||||
@ -77,7 +83,13 @@ final class VideoChatParticipantVideoComponent: Component {
|
||||
if lhs.isExpanded != rhs.isExpanded {
|
||||
return false
|
||||
}
|
||||
if lhs.bottomInset != rhs.bottomInset {
|
||||
if lhs.isUIHidden != rhs.isUIHidden {
|
||||
return false
|
||||
}
|
||||
if lhs.contentInsets != rhs.contentInsets {
|
||||
return false
|
||||
}
|
||||
if lhs.controlInsets != rhs.controlInsets {
|
||||
return false
|
||||
}
|
||||
if (lhs.action == nil) != (rhs.action == nil) {
|
||||
@ -153,6 +165,15 @@ final class VideoChatParticipantVideoComponent: Component {
|
||||
self.component = component
|
||||
self.componentState = state
|
||||
|
||||
let alphaTransition: ComponentTransition
|
||||
if !transition.animation.isImmediate {
|
||||
alphaTransition = .easeInOut(duration: 0.2)
|
||||
} else {
|
||||
alphaTransition = .immediate
|
||||
}
|
||||
|
||||
let controlsAlpha: CGFloat = component.isUIHidden ? 0.0 : 1.0
|
||||
|
||||
let nameColor = component.participant.peer.nameColor ?? .blue
|
||||
let nameColors = component.call.accountContext.peerNameColors.get(nameColor, dark: true)
|
||||
self.backgroundColor = nameColors.main.withMultiplied(hue: 1.0, saturation: 1.0, brightness: 0.4)
|
||||
@ -210,25 +231,26 @@ final class VideoChatParticipantVideoComponent: Component {
|
||||
transition: transition,
|
||||
component: AnyComponent(VideoChatMuteIconComponent(
|
||||
color: .white,
|
||||
isFilled: true,
|
||||
isMuted: component.participant.muteState != nil
|
||||
content: component.isPresentation ? .screenshare : .mute(isFilled: true, isMuted: component.participant.muteState != nil && !component.isSpeaking)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 36.0, height: 36.0)
|
||||
)
|
||||
let muteStatusFrame: CGRect
|
||||
if component.isExpanded {
|
||||
muteStatusFrame = CGRect(origin: CGPoint(x: 5.0, y: availableSize.height - component.bottomInset + 1.0 - muteStatusSize.height), size: muteStatusSize)
|
||||
muteStatusFrame = CGRect(origin: CGPoint(x: 5.0, y: availableSize.height - component.controlInsets.bottom + 1.0 - muteStatusSize.height), size: muteStatusSize)
|
||||
} else {
|
||||
muteStatusFrame = CGRect(origin: CGPoint(x: 1.0, y: availableSize.height - component.bottomInset + 3.0 - muteStatusSize.height), size: muteStatusSize)
|
||||
muteStatusFrame = CGRect(origin: CGPoint(x: 1.0, y: availableSize.height - component.controlInsets.bottom + 3.0 - muteStatusSize.height), size: muteStatusSize)
|
||||
}
|
||||
if let muteStatusView = self.muteStatus.view {
|
||||
if muteStatusView.superview == nil {
|
||||
self.addSubview(muteStatusView)
|
||||
muteStatusView.alpha = controlsAlpha
|
||||
}
|
||||
transition.setPosition(view: muteStatusView, position: muteStatusFrame.center)
|
||||
transition.setBounds(view: muteStatusView, bounds: CGRect(origin: CGPoint(), size: muteStatusFrame.size))
|
||||
transition.setScale(view: muteStatusView, scale: component.isExpanded ? 1.0 : 0.7)
|
||||
alphaTransition.setAlpha(view: muteStatusView, alpha: controlsAlpha)
|
||||
}
|
||||
|
||||
let titleSize = self.title.update(
|
||||
@ -241,18 +263,20 @@ final class VideoChatParticipantVideoComponent: Component {
|
||||
)
|
||||
let titleFrame: CGRect
|
||||
if component.isExpanded {
|
||||
titleFrame = CGRect(origin: CGPoint(x: 36.0, y: availableSize.height - component.bottomInset - 8.0 - titleSize.height), size: titleSize)
|
||||
titleFrame = CGRect(origin: CGPoint(x: 36.0, y: availableSize.height - component.controlInsets.bottom - 8.0 - titleSize.height), size: titleSize)
|
||||
} else {
|
||||
titleFrame = CGRect(origin: CGPoint(x: 29.0, y: availableSize.height - component.bottomInset - 4.0 - titleSize.height), size: titleSize)
|
||||
titleFrame = CGRect(origin: CGPoint(x: 29.0, y: availableSize.height - component.controlInsets.bottom - 4.0 - titleSize.height), size: titleSize)
|
||||
}
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
titleView.layer.anchorPoint = CGPoint()
|
||||
self.addSubview(titleView)
|
||||
titleView.alpha = controlsAlpha
|
||||
}
|
||||
transition.setPosition(view: titleView, position: titleFrame.origin)
|
||||
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||
transition.setScale(view: titleView, scale: component.isExpanded ? 1.0 : 0.825)
|
||||
alphaTransition.setAlpha(view: titleView, alpha: controlsAlpha)
|
||||
}
|
||||
|
||||
if let videoDescription = component.isPresentation ? component.participant.presentationDescription : component.participant.videoDescription {
|
||||
@ -397,7 +421,7 @@ final class VideoChatParticipantVideoComponent: Component {
|
||||
let blurredVideoSize = rotatedResolution.aspectFilled(availableSize)
|
||||
let blurredVideoFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - blurredVideoSize.width) * 0.5), y: floorToScreenPixels((availableSize.height - blurredVideoSize.height) * 0.5)), size: blurredVideoSize)
|
||||
|
||||
let videoResolution = rotatedResolution.aspectFitted(CGSize(width: availableSize.width * 3.0, height: availableSize.height * 3.0))
|
||||
let videoResolution = rotatedResolution
|
||||
|
||||
var rotatedVideoResolution = videoResolution
|
||||
var rotatedVideoFrame = videoFrame
|
||||
@ -408,6 +432,7 @@ final class VideoChatParticipantVideoComponent: Component {
|
||||
rotatedVideoFrame = rotatedVideoFrame.size.centered(around: rotatedVideoFrame.center)
|
||||
rotatedBlurredVideoFrame = rotatedBlurredVideoFrame.size.centered(around: rotatedBlurredVideoFrame.center)
|
||||
}
|
||||
rotatedVideoResolution = rotatedVideoResolution.aspectFittedOrSmaller(CGSize(width: rotatedVideoFrame.width * UIScreenScale, height: rotatedVideoFrame.height * UIScreenScale))
|
||||
|
||||
transition.setPosition(layer: videoLayer, position: rotatedVideoFrame.center)
|
||||
transition.setBounds(layer: videoLayer, bounds: CGRect(origin: CGPoint(), size: rotatedVideoFrame.size))
|
||||
|
@ -12,21 +12,26 @@ import TelegramPresentationData
|
||||
import PeerListItemComponent
|
||||
|
||||
final class VideoChatParticipantsComponent: Component {
|
||||
enum LayoutType: Equatable {
|
||||
struct Horizontal: Equatable {
|
||||
var rightColumnWidth: CGFloat
|
||||
var columnSpacing: CGFloat
|
||||
var isCentered: Bool
|
||||
struct Layout: Equatable {
|
||||
struct Column: Equatable {
|
||||
var width: CGFloat
|
||||
var insets: UIEdgeInsets
|
||||
|
||||
init(rightColumnWidth: CGFloat, columnSpacing: CGFloat, isCentered: Bool) {
|
||||
self.rightColumnWidth = rightColumnWidth
|
||||
self.columnSpacing = columnSpacing
|
||||
self.isCentered = isCentered
|
||||
init(width: CGFloat, insets: UIEdgeInsets) {
|
||||
self.width = width
|
||||
self.insets = insets
|
||||
}
|
||||
}
|
||||
|
||||
case vertical
|
||||
case horizontal(Horizontal)
|
||||
var videoColumn: Column?
|
||||
var mainColumn: Column
|
||||
var columnSpacing: CGFloat
|
||||
|
||||
init(videoColumn: Column?, mainColumn: Column, columnSpacing: CGFloat) {
|
||||
self.videoColumn = videoColumn
|
||||
self.mainColumn = mainColumn
|
||||
self.columnSpacing = columnSpacing
|
||||
}
|
||||
}
|
||||
|
||||
final class Participants: Equatable {
|
||||
@ -75,10 +80,12 @@ final class VideoChatParticipantsComponent: Component {
|
||||
final class ExpandedVideoState: Equatable {
|
||||
let mainParticipant: VideoParticipantKey
|
||||
let isMainParticipantPinned: Bool
|
||||
let isUIHidden: Bool
|
||||
|
||||
init(mainParticipant: VideoParticipantKey, isMainParticipantPinned: Bool) {
|
||||
init(mainParticipant: VideoParticipantKey, isMainParticipantPinned: Bool, isUIHidden: Bool) {
|
||||
self.mainParticipant = mainParticipant
|
||||
self.isMainParticipantPinned = isMainParticipantPinned
|
||||
self.isUIHidden = isUIHidden
|
||||
}
|
||||
|
||||
static func ==(lhs: ExpandedVideoState, rhs: ExpandedVideoState) -> Bool {
|
||||
@ -91,6 +98,9 @@ final class VideoChatParticipantsComponent: Component {
|
||||
if lhs.isMainParticipantPinned != rhs.isMainParticipantPinned {
|
||||
return false
|
||||
}
|
||||
if lhs.isUIHidden != rhs.isUIHidden {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -101,12 +111,12 @@ final class VideoChatParticipantsComponent: Component {
|
||||
let expandedVideoState: ExpandedVideoState?
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let layoutType: LayoutType
|
||||
let collapsedContainerInsets: UIEdgeInsets
|
||||
let expandedContainerInsets: UIEdgeInsets
|
||||
let sideInset: CGFloat
|
||||
let layout: Layout
|
||||
let expandedInsets: UIEdgeInsets
|
||||
let safeInsets: UIEdgeInsets
|
||||
let updateMainParticipant: (VideoParticipantKey?) -> Void
|
||||
let updateIsMainParticipantPinned: (Bool) -> Void
|
||||
let updateIsExpandedUIHidden: (Bool) -> Void
|
||||
|
||||
init(
|
||||
call: PresentationGroupCall,
|
||||
@ -115,12 +125,12 @@ final class VideoChatParticipantsComponent: Component {
|
||||
expandedVideoState: ExpandedVideoState?,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
layoutType: LayoutType,
|
||||
collapsedContainerInsets: UIEdgeInsets,
|
||||
expandedContainerInsets: UIEdgeInsets,
|
||||
sideInset: CGFloat,
|
||||
layout: Layout,
|
||||
expandedInsets: UIEdgeInsets,
|
||||
safeInsets: UIEdgeInsets,
|
||||
updateMainParticipant: @escaping (VideoParticipantKey?) -> Void,
|
||||
updateIsMainParticipantPinned: @escaping (Bool) -> Void
|
||||
updateIsMainParticipantPinned: @escaping (Bool) -> Void,
|
||||
updateIsExpandedUIHidden: @escaping (Bool) -> Void
|
||||
) {
|
||||
self.call = call
|
||||
self.participants = participants
|
||||
@ -128,12 +138,12 @@ final class VideoChatParticipantsComponent: Component {
|
||||
self.expandedVideoState = expandedVideoState
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.layoutType = layoutType
|
||||
self.collapsedContainerInsets = collapsedContainerInsets
|
||||
self.expandedContainerInsets = expandedContainerInsets
|
||||
self.sideInset = sideInset
|
||||
self.layout = layout
|
||||
self.expandedInsets = expandedInsets
|
||||
self.safeInsets = safeInsets
|
||||
self.updateMainParticipant = updateMainParticipant
|
||||
self.updateIsMainParticipantPinned = updateIsMainParticipantPinned
|
||||
self.updateIsExpandedUIHidden = updateIsExpandedUIHidden
|
||||
}
|
||||
|
||||
static func ==(lhs: VideoChatParticipantsComponent, rhs: VideoChatParticipantsComponent) -> Bool {
|
||||
@ -152,16 +162,13 @@ final class VideoChatParticipantsComponent: Component {
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.layoutType != rhs.layoutType {
|
||||
if lhs.layout != rhs.layout {
|
||||
return false
|
||||
}
|
||||
if lhs.collapsedContainerInsets != rhs.collapsedContainerInsets {
|
||||
if lhs.expandedInsets != rhs.expandedInsets {
|
||||
return false
|
||||
}
|
||||
if lhs.expandedContainerInsets != rhs.expandedContainerInsets {
|
||||
return false
|
||||
}
|
||||
if lhs.sideInset != rhs.sideInset {
|
||||
if lhs.safeInsets != rhs.safeInsets {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@ -178,40 +185,84 @@ final class VideoChatParticipantsComponent: Component {
|
||||
let containerSize: CGSize
|
||||
let sideInset: CGFloat
|
||||
let itemCount: Int
|
||||
let isDedicatedColumn: Bool
|
||||
let itemSize: CGSize
|
||||
let itemSpacing: CGFloat
|
||||
let lastItemSize: CGFloat
|
||||
let lastRowItemCount: Int
|
||||
let lastRowItemSize: CGFloat
|
||||
let itemsPerRow: Int
|
||||
let rowCount: Int
|
||||
|
||||
init(containerSize: CGSize, sideInset: CGFloat, itemCount: Int) {
|
||||
init(containerSize: CGSize, sideInset: CGFloat, itemCount: Int, isDedicatedColumn: Bool) {
|
||||
self.containerSize = containerSize
|
||||
self.sideInset = sideInset
|
||||
self.itemCount = itemCount
|
||||
self.isDedicatedColumn = isDedicatedColumn
|
||||
|
||||
let width: CGFloat = containerSize.width - sideInset * 2.0
|
||||
|
||||
self.itemSpacing = 4.0
|
||||
|
||||
let itemsPerRow: Int
|
||||
if itemCount == 1 {
|
||||
itemsPerRow = 1
|
||||
if isDedicatedColumn {
|
||||
if itemCount <= 2 {
|
||||
itemsPerRow = 1
|
||||
} else {
|
||||
itemsPerRow = 2
|
||||
}
|
||||
} else {
|
||||
itemsPerRow = 2
|
||||
if itemCount == 1 {
|
||||
itemsPerRow = 1
|
||||
} else {
|
||||
itemsPerRow = 2
|
||||
}
|
||||
}
|
||||
self.itemsPerRow = Int(itemsPerRow)
|
||||
|
||||
let itemWidth = floorToScreenPixels((width - (self.itemSpacing * CGFloat(self.itemsPerRow - 1))) / CGFloat(itemsPerRow))
|
||||
let itemHeight = min(180.0, itemWidth)
|
||||
self.itemSize = CGSize(width: itemWidth, height: itemHeight)
|
||||
var itemSize = CGSize(width: itemWidth, height: itemHeight)
|
||||
|
||||
self.rowCount = itemCount / self.itemsPerRow + ((itemCount % self.itemsPerRow) != 0 ? 1 : 0)
|
||||
|
||||
if isDedicatedColumn && itemCount != 0 {
|
||||
let contentHeight = itemSize.height * CGFloat(self.rowCount) + self.itemSpacing * CGFloat(max(0, self.rowCount - 1))
|
||||
if contentHeight < containerSize.height {
|
||||
itemSize.height = (containerSize.height - self.itemSpacing * CGFloat(max(0, self.rowCount - 1))) / CGFloat(self.rowCount)
|
||||
itemSize.height = floor(itemSize.height)
|
||||
}
|
||||
}
|
||||
|
||||
self.itemSize = itemSize
|
||||
|
||||
self.lastItemSize = width - (self.itemSize.width + self.itemSpacing) * CGFloat(self.itemsPerRow - 1)
|
||||
var lastRowItemCount = itemCount % self.itemsPerRow
|
||||
if lastRowItemCount == 0 {
|
||||
lastRowItemCount = self.itemsPerRow
|
||||
}
|
||||
self.lastRowItemCount = lastRowItemCount
|
||||
self.lastRowItemSize = width - (self.itemSize.width + self.itemSpacing) * CGFloat(lastRowItemCount - 1)
|
||||
}
|
||||
|
||||
func frame(at index: Int) -> CGRect {
|
||||
let row = index / self.itemsPerRow
|
||||
let column = index % self.itemsPerRow
|
||||
|
||||
let frame = CGRect(origin: CGPoint(x: self.sideInset + CGFloat(column) * (self.itemSize.width + self.itemSpacing), y: CGFloat(row) * (self.itemSize.height + self.itemSpacing)), size: CGSize(width: column == (self.itemsPerRow - 1) ? self.lastItemSize : itemSize.width, height: itemSize.height))
|
||||
let itemWidth: CGFloat
|
||||
if row == self.rowCount - 1 && column == self.lastRowItemCount - 1 {
|
||||
itemWidth = self.lastRowItemSize
|
||||
} else if column == self.itemsPerRow - 1 {
|
||||
if row == self.rowCount - 1 {
|
||||
itemWidth = self.lastRowItemSize
|
||||
} else {
|
||||
itemWidth = self.lastItemSize
|
||||
}
|
||||
} else {
|
||||
itemWidth = self.itemSize.width
|
||||
}
|
||||
|
||||
let frame = CGRect(origin: CGPoint(x: self.sideInset + CGFloat(column) * (self.itemSize.width + self.itemSpacing), y: CGFloat(row) * (self.itemSize.height + self.itemSpacing)), size: CGSize(width: itemWidth, height: itemSize.height))
|
||||
return frame
|
||||
}
|
||||
|
||||
@ -237,21 +288,37 @@ final class VideoChatParticipantsComponent: Component {
|
||||
|
||||
struct ExpandedGrid {
|
||||
let containerSize: CGSize
|
||||
let layoutType: LayoutType
|
||||
let containerInsets: UIEdgeInsets
|
||||
let layout: Layout
|
||||
let expandedInsets: UIEdgeInsets
|
||||
let isUIHidden: Bool
|
||||
|
||||
init(containerSize: CGSize, layoutType: LayoutType, containerInsets: UIEdgeInsets) {
|
||||
init(containerSize: CGSize, layout: Layout, expandedInsets: UIEdgeInsets, isUIHidden: Bool) {
|
||||
self.containerSize = containerSize
|
||||
self.layoutType = layoutType
|
||||
self.containerInsets = containerInsets
|
||||
self.layout = layout
|
||||
self.expandedInsets = expandedInsets
|
||||
self.isUIHidden = isUIHidden
|
||||
}
|
||||
|
||||
func itemContainerFrame() -> CGRect {
|
||||
switch self.layoutType {
|
||||
case .vertical:
|
||||
return CGRect(origin: CGPoint(x: self.containerInsets.left, y: self.containerInsets.top), size: CGSize(width: self.containerSize.width - self.containerInsets.left - self.containerInsets.right, height: self.containerSize.height - self.containerInsets.top - containerInsets.bottom))
|
||||
case .horizontal:
|
||||
return CGRect(origin: CGPoint(x: self.containerInsets.left, y: self.containerInsets.top), size: CGSize(width: self.containerSize.width - self.containerInsets.left - self.containerInsets.right, height: self.containerSize.height - self.containerInsets.top))
|
||||
let containerInsets: UIEdgeInsets
|
||||
if self.isUIHidden {
|
||||
containerInsets = UIEdgeInsets()
|
||||
} else {
|
||||
containerInsets = self.expandedInsets
|
||||
}
|
||||
|
||||
if self.layout.videoColumn != nil {
|
||||
return CGRect(origin: CGPoint(x: containerInsets.left, y: containerInsets.top), size: CGSize(width: self.containerSize.width - containerInsets.left - containerInsets.right, height: self.containerSize.height - containerInsets.top - containerInsets.bottom))
|
||||
} else {
|
||||
return CGRect(origin: CGPoint(x: containerInsets.left, y: containerInsets.top), size: CGSize(width: self.containerSize.width - containerInsets.left - containerInsets.right, height: self.containerSize.height - containerInsets.top - containerInsets.bottom))
|
||||
}
|
||||
}
|
||||
|
||||
func itemContainerInsets() -> UIEdgeInsets {
|
||||
if self.isUIHidden {
|
||||
return self.expandedInsets
|
||||
} else {
|
||||
return UIEdgeInsets()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -306,9 +373,10 @@ final class VideoChatParticipantsComponent: Component {
|
||||
}
|
||||
|
||||
let containerSize: CGSize
|
||||
let layoutType: LayoutType
|
||||
let collapsedContainerInsets: UIEdgeInsets
|
||||
let sideInset: CGFloat
|
||||
let layout: Layout
|
||||
let isUIHidden: Bool
|
||||
let expandedInsets: UIEdgeInsets
|
||||
let safeInsets: UIEdgeInsets
|
||||
let grid: Grid
|
||||
let expandedGrid: ExpandedGrid
|
||||
let list: List
|
||||
@ -320,32 +388,41 @@ final class VideoChatParticipantsComponent: Component {
|
||||
let scrollClippingFrame: CGRect
|
||||
let separateVideoScrollClippingFrame: CGRect
|
||||
|
||||
init(containerSize: CGSize, layoutType: LayoutType, sideInset: CGFloat, collapsedContainerInsets: UIEdgeInsets, expandedContainerInsets: UIEdgeInsets, gridItemCount: Int, listItemCount: Int, listItemHeight: CGFloat, listTrailingItemHeight: CGFloat) {
|
||||
init(containerSize: CGSize, layout: Layout, isUIHidden: Bool, expandedInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, gridItemCount: Int, listItemCount: Int, listItemHeight: CGFloat, listTrailingItemHeight: CGFloat) {
|
||||
self.containerSize = containerSize
|
||||
self.layoutType = layoutType
|
||||
self.collapsedContainerInsets = collapsedContainerInsets
|
||||
self.sideInset = sideInset
|
||||
self.layout = layout
|
||||
self.isUIHidden = isUIHidden
|
||||
self.expandedInsets = expandedInsets
|
||||
self.safeInsets = safeInsets
|
||||
|
||||
let listWidth: CGFloat = layout.mainColumn.width
|
||||
let gridWidth: CGFloat
|
||||
let listWidth: CGFloat
|
||||
switch layoutType {
|
||||
case .vertical:
|
||||
listWidth = containerSize.width - sideInset * 2.0
|
||||
let gridSideInset: CGFloat
|
||||
let gridContainerHeight: CGFloat
|
||||
if let videoColumn = layout.videoColumn {
|
||||
gridWidth = videoColumn.width
|
||||
gridSideInset = videoColumn.insets.left
|
||||
gridContainerHeight = containerSize.height - videoColumn.insets.top - videoColumn.insets.bottom
|
||||
} else {
|
||||
gridWidth = listWidth
|
||||
case let .horizontal(horizontal):
|
||||
listWidth = horizontal.rightColumnWidth
|
||||
gridWidth = max(10.0, containerSize.width - sideInset * 2.0 - horizontal.rightColumnWidth - horizontal.columnSpacing)
|
||||
gridSideInset = layout.mainColumn.insets.left
|
||||
gridContainerHeight = containerSize.height
|
||||
}
|
||||
|
||||
self.grid = Grid(containerSize: CGSize(width: gridWidth, height: containerSize.height), sideInset: 0.0, itemCount: gridItemCount)
|
||||
self.expandedGrid = ExpandedGrid(containerSize: containerSize, layoutType: layoutType, containerInsets: expandedContainerInsets)
|
||||
self.list = List(containerSize: CGSize(width: listWidth, height: containerSize.height), sideInset: 0.0, itemCount: listItemCount, itemHeight: listItemHeight, trailingItemHeight: listTrailingItemHeight)
|
||||
self.grid = Grid(containerSize: CGSize(width: gridWidth, height: gridContainerHeight), sideInset: gridSideInset, itemCount: gridItemCount, isDedicatedColumn: layout.videoColumn != nil)
|
||||
self.list = List(containerSize: CGSize(width: listWidth, height: containerSize.height), sideInset: layout.mainColumn.insets.left, itemCount: listItemCount, itemHeight: listItemHeight, trailingItemHeight: listTrailingItemHeight)
|
||||
self.spacing = 4.0
|
||||
|
||||
self.gridOffsetY = collapsedContainerInsets.top
|
||||
if let videoColumn = layout.videoColumn, !isUIHidden {
|
||||
self.expandedGrid = ExpandedGrid(containerSize: CGSize(width: videoColumn.width + expandedInsets.left, height: containerSize.height), layout: layout, expandedInsets: UIEdgeInsets(top: expandedInsets.top, left: expandedInsets.left, bottom: expandedInsets.bottom, right: 0.0), isUIHidden: isUIHidden)
|
||||
} else {
|
||||
self.expandedGrid = ExpandedGrid(containerSize: containerSize, layout: layout, expandedInsets: expandedInsets, isUIHidden: isUIHidden)
|
||||
}
|
||||
|
||||
self.gridOffsetY = layout.mainColumn.insets.top
|
||||
|
||||
var listOffsetY: CGFloat = self.gridOffsetY
|
||||
if case .vertical = layoutType {
|
||||
if layout.videoColumn == nil {
|
||||
if self.grid.itemCount != 0 {
|
||||
listOffsetY += self.grid.contentHeight()
|
||||
listOffsetY += self.spacing
|
||||
@ -353,55 +430,54 @@ final class VideoChatParticipantsComponent: Component {
|
||||
}
|
||||
self.listOffsetY = listOffsetY
|
||||
|
||||
switch layoutType {
|
||||
case .vertical:
|
||||
self.scrollClippingFrame = CGRect(origin: CGPoint(x: self.sideInset, y: collapsedContainerInsets.top), size: CGSize(width: containerSize.width - self.sideInset * 2.0, height: containerSize.height - collapsedContainerInsets.top - collapsedContainerInsets.bottom))
|
||||
self.listFrame = CGRect(origin: CGPoint(), size: containerSize)
|
||||
if let videoColumn = layout.videoColumn {
|
||||
let columnsWidth: CGFloat = videoColumn.width + layout.columnSpacing + layout.mainColumn.width
|
||||
let columnsSideInset: CGFloat = floorToScreenPixels((containerSize.width - columnsWidth) * 0.5)
|
||||
|
||||
self.separateVideoGridFrame = CGRect(origin: CGPoint(x: -containerSize.width, y: 0.0), size: containerSize)
|
||||
self.separateVideoScrollClippingFrame = CGRect(origin: CGPoint(x: self.separateVideoGridFrame.minX, y: collapsedContainerInsets.top), size: CGSize(width: self.separateVideoGridFrame.width, height: containerSize.height - collapsedContainerInsets.top))
|
||||
case let .horizontal(horizontal):
|
||||
if horizontal.isCentered {
|
||||
self.listFrame = CGRect(origin: CGPoint(x: floor((containerSize.width - horizontal.rightColumnWidth) * 0.5), y: 0.0), size: CGSize(width: horizontal.rightColumnWidth, height: containerSize.height))
|
||||
} else {
|
||||
self.listFrame = CGRect(origin: CGPoint(x: containerSize.width - self.sideInset - horizontal.rightColumnWidth, y: 0.0), size: CGSize(width: horizontal.rightColumnWidth, height: containerSize.height))
|
||||
var separateVideoGridFrame = CGRect(origin: CGPoint(x: floor((containerSize.width - columnsWidth) * 0.5), y: 0.0), size: CGSize(width: gridWidth, height: containerSize.height))
|
||||
|
||||
var listFrame = CGRect(origin: CGPoint(x: separateVideoGridFrame.maxX + layout.columnSpacing, y: 0.0), size: CGSize(width: listWidth, height: containerSize.height))
|
||||
if isUIHidden {
|
||||
listFrame.origin.x += columnsSideInset + layout.mainColumn.width
|
||||
separateVideoGridFrame = CGRect(origin: CGPoint(x: floor((containerSize.width - columnsWidth) * 0.5), y: 0.0), size: CGSize(width: columnsWidth, height: containerSize.height))
|
||||
}
|
||||
self.scrollClippingFrame = CGRect(origin: CGPoint(x: self.listFrame.minX, y: collapsedContainerInsets.top), size: CGSize(width: self.listFrame.width, height: containerSize.height - collapsedContainerInsets.top))
|
||||
|
||||
self.separateVideoGridFrame = CGRect(origin: CGPoint(x: min(self.sideInset, self.scrollClippingFrame.minX - horizontal.columnSpacing - gridWidth), y: 0.0), size: CGSize(width: gridWidth, height: containerSize.height))
|
||||
self.separateVideoScrollClippingFrame = CGRect(origin: CGPoint(x: self.separateVideoGridFrame.minX, y: collapsedContainerInsets.top), size: CGSize(width: self.separateVideoGridFrame.width, height: containerSize.height - collapsedContainerInsets.top))
|
||||
self.separateVideoGridFrame = separateVideoGridFrame
|
||||
self.listFrame = listFrame
|
||||
|
||||
self.separateVideoScrollClippingFrame = CGRect(origin: CGPoint(x: self.separateVideoGridFrame.minX, y: videoColumn.insets.top), size: CGSize(width: self.separateVideoGridFrame.width, height: containerSize.height - videoColumn.insets.top))
|
||||
self.scrollClippingFrame = CGRect(origin: CGPoint(x: self.listFrame.minX, y: layout.mainColumn.insets.top), size: CGSize(width: self.listFrame.width, height: containerSize.height - layout.mainColumn.insets.top))
|
||||
} else {
|
||||
self.listFrame = CGRect(origin: CGPoint(x: floor((containerSize.width - listWidth) * 0.5), y: 0.0), size: CGSize(width: listWidth, height: containerSize.height))
|
||||
self.scrollClippingFrame = CGRect(origin: CGPoint(x: self.listFrame.minX, y: layout.mainColumn.insets.top), size: CGSize(width: listWidth, height: containerSize.height - layout.mainColumn.insets.top - layout.mainColumn.insets.bottom))
|
||||
|
||||
self.separateVideoGridFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 0.0, height: containerSize.height))
|
||||
self.separateVideoScrollClippingFrame = CGRect(origin: CGPoint(x: self.separateVideoGridFrame.minX, y: layout.mainColumn.insets.top), size: CGSize(width: self.separateVideoGridFrame.width, height: containerSize.height - layout.mainColumn.insets.top))
|
||||
}
|
||||
}
|
||||
|
||||
func contentHeight() -> CGFloat {
|
||||
var result: CGFloat = self.gridOffsetY
|
||||
switch self.layoutType {
|
||||
case .vertical:
|
||||
if self.layout.videoColumn == nil {
|
||||
if self.grid.itemCount != 0 {
|
||||
result += self.grid.contentHeight()
|
||||
result += self.spacing
|
||||
}
|
||||
case .horizontal:
|
||||
break
|
||||
}
|
||||
result += self.list.contentHeight()
|
||||
result += self.collapsedContainerInsets.bottom
|
||||
result += self.layout.mainColumn.insets.bottom
|
||||
result += 24.0
|
||||
return result
|
||||
}
|
||||
|
||||
func separateVideoGridContentHeight() -> CGFloat {
|
||||
var result: CGFloat = self.gridOffsetY
|
||||
switch self.layoutType {
|
||||
case .vertical:
|
||||
break
|
||||
case .horizontal:
|
||||
if let videoColumn = self.layout.videoColumn {
|
||||
if self.grid.itemCount != 0 {
|
||||
result += self.grid.contentHeight()
|
||||
}
|
||||
result += videoColumn.insets.bottom
|
||||
}
|
||||
result += self.collapsedContainerInsets.bottom
|
||||
result += 24.0
|
||||
return result
|
||||
}
|
||||
|
||||
@ -414,11 +490,10 @@ final class VideoChatParticipantsComponent: Component {
|
||||
}
|
||||
|
||||
func gridItemContainerFrame() -> CGRect {
|
||||
switch self.layoutType {
|
||||
case .vertical:
|
||||
return CGRect(origin: CGPoint(x: self.sideInset, y: self.gridOffsetY), size: CGSize(width: self.containerSize.width - self.sideInset * 2.0, height: self.grid.contentHeight()))
|
||||
case .horizontal:
|
||||
if let _ = self.layout.videoColumn {
|
||||
return CGRect(origin: CGPoint(x: 0.0, y: self.gridOffsetY), size: CGSize(width: self.separateVideoGridFrame.width, height: self.grid.contentHeight()))
|
||||
} else {
|
||||
return CGRect(origin: CGPoint(x: 0.0, y: self.gridOffsetY), size: CGSize(width: self.containerSize.width, height: self.grid.contentHeight()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -431,11 +506,10 @@ final class VideoChatParticipantsComponent: Component {
|
||||
}
|
||||
|
||||
func listItemContainerFrame() -> CGRect {
|
||||
switch self.layoutType {
|
||||
case .vertical:
|
||||
return CGRect(origin: CGPoint(x: self.sideInset, y: self.listOffsetY), size: CGSize(width: self.containerSize.width - self.sideInset * 2.0, height: self.list.contentHeight()))
|
||||
case .horizontal:
|
||||
return CGRect(origin: CGPoint(x: 0.0, y: self.listOffsetY), size: CGSize(width: self.listFrame.width, height: self.list.contentHeight()))
|
||||
if let _ = self.layout.videoColumn {
|
||||
return CGRect(origin: CGPoint(x: 0.0, y: self.listOffsetY), size: CGSize(width: self.separateVideoGridFrame.width, height: self.list.contentHeight()))
|
||||
} else {
|
||||
return CGRect(origin: CGPoint(x: 0.0, y: self.listOffsetY), size: CGSize(width: self.containerSize.width, height: self.list.contentHeight()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -620,6 +694,13 @@ final class VideoChatParticipantsComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
let alphaTransition: ComponentTransition
|
||||
if !transition.animation.isImmediate {
|
||||
alphaTransition = .easeInOut(duration: 0.2)
|
||||
} else {
|
||||
alphaTransition = .immediate
|
||||
}
|
||||
|
||||
let gridWasEmpty = self.appliedGridIsEmpty
|
||||
let gridIsEmpty = self.gridParticipants.isEmpty
|
||||
self.appliedGridIsEmpty = gridIsEmpty
|
||||
@ -637,27 +718,26 @@ final class VideoChatParticipantsComponent: Component {
|
||||
if component.expandedVideoState != nil {
|
||||
expandedGridItemContainerFrame = itemLayout.expandedGrid.itemContainerFrame()
|
||||
} else {
|
||||
switch itemLayout.layoutType {
|
||||
case .vertical:
|
||||
expandedGridItemContainerFrame = itemLayout.gridItemContainerFrame().offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)
|
||||
|
||||
if expandedGridItemContainerFrame.origin.y < component.collapsedContainerInsets.top {
|
||||
expandedGridItemContainerFrame.size.height -= component.collapsedContainerInsets.top - expandedGridItemContainerFrame.origin.y
|
||||
expandedGridItemContainerFrame.origin.y = component.collapsedContainerInsets.top
|
||||
}
|
||||
if expandedGridItemContainerFrame.origin.y + expandedGridItemContainerFrame.height > itemLayout.containerSize.height - component.collapsedContainerInsets.bottom {
|
||||
expandedGridItemContainerFrame.size.height -= (expandedGridItemContainerFrame.origin.y + expandedGridItemContainerFrame.height) - (itemLayout.containerSize.height - component.collapsedContainerInsets.bottom)
|
||||
}
|
||||
case .horizontal:
|
||||
if let videoColumn = itemLayout.layout.videoColumn {
|
||||
expandedGridItemContainerFrame = itemLayout.gridItemContainerFrame().offsetBy(dx: itemLayout.separateVideoScrollClippingFrame.minX, dy: 0.0).offsetBy(dx: 0.0, dy: -self.separateVideoScrollView.bounds.minY)
|
||||
|
||||
if expandedGridItemContainerFrame.origin.y < component.collapsedContainerInsets.top {
|
||||
expandedGridItemContainerFrame.size.height -= component.collapsedContainerInsets.top - expandedGridItemContainerFrame.origin.y
|
||||
expandedGridItemContainerFrame.origin.y = component.collapsedContainerInsets.top
|
||||
if expandedGridItemContainerFrame.origin.y < videoColumn.insets.top {
|
||||
expandedGridItemContainerFrame.size.height -= videoColumn.insets.top - expandedGridItemContainerFrame.origin.y
|
||||
expandedGridItemContainerFrame.origin.y = videoColumn.insets.top
|
||||
}
|
||||
if expandedGridItemContainerFrame.origin.y + expandedGridItemContainerFrame.height > itemLayout.containerSize.height {
|
||||
expandedGridItemContainerFrame.size.height -= (expandedGridItemContainerFrame.origin.y + expandedGridItemContainerFrame.height) - (itemLayout.containerSize.height)
|
||||
}
|
||||
} else {
|
||||
expandedGridItemContainerFrame = itemLayout.gridItemContainerFrame().offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)
|
||||
|
||||
if expandedGridItemContainerFrame.origin.y < itemLayout.layout.mainColumn.insets.top {
|
||||
expandedGridItemContainerFrame.size.height -= itemLayout.layout.mainColumn.insets.top - expandedGridItemContainerFrame.origin.y
|
||||
expandedGridItemContainerFrame.origin.y = itemLayout.layout.mainColumn.insets.top
|
||||
}
|
||||
if expandedGridItemContainerFrame.origin.y + expandedGridItemContainerFrame.height > itemLayout.containerSize.height - itemLayout.layout.mainColumn.insets.bottom {
|
||||
expandedGridItemContainerFrame.size.height -= (expandedGridItemContainerFrame.origin.y + expandedGridItemContainerFrame.height) - (itemLayout.containerSize.height - itemLayout.layout.mainColumn.insets.bottom)
|
||||
}
|
||||
}
|
||||
if expandedGridItemContainerFrame.size.height < 0.0 {
|
||||
expandedGridItemContainerFrame.size.height = 0.0
|
||||
@ -670,10 +750,9 @@ final class VideoChatParticipantsComponent: Component {
|
||||
var validGridItemIndices: [Int] = []
|
||||
|
||||
let visibleGridItemRange: (minIndex: Int, maxIndex: Int)
|
||||
switch itemLayout.layoutType {
|
||||
case .vertical:
|
||||
if itemLayout.layout.videoColumn == nil {
|
||||
visibleGridItemRange = itemLayout.visibleGridItemRange(for: self.scrollView.bounds)
|
||||
case .horizontal:
|
||||
} else {
|
||||
visibleGridItemRange = itemLayout.visibleGridItemRange(for: self.separateVideoScrollView.bounds)
|
||||
}
|
||||
if visibleGridItemRange.maxIndex >= visibleGridItemRange.minIndex {
|
||||
@ -707,8 +786,14 @@ final class VideoChatParticipantsComponent: Component {
|
||||
}
|
||||
|
||||
var isItemExpanded = false
|
||||
if let expandedVideoState = component.expandedVideoState, expandedVideoState.mainParticipant == videoParticipantKey {
|
||||
isItemExpanded = true
|
||||
var isItemUIHidden = false
|
||||
if let expandedVideoState = component.expandedVideoState {
|
||||
if expandedVideoState.mainParticipant == videoParticipantKey {
|
||||
isItemExpanded = true
|
||||
}
|
||||
if expandedVideoState.isUIHidden {
|
||||
isItemUIHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
var suppressItemExpansionCollapseAnimation = false
|
||||
@ -734,14 +819,28 @@ final class VideoChatParticipantsComponent: Component {
|
||||
itemFrame = itemLayout.gridItemFrame(at: index)
|
||||
}
|
||||
|
||||
var itemBottomInset: CGFloat = isItemExpanded ? 96.0 : 0.0
|
||||
switch itemLayout.layoutType {
|
||||
case .vertical:
|
||||
break
|
||||
case .horizontal:
|
||||
if isItemExpanded {
|
||||
itemBottomInset += itemLayout.expandedGrid.containerInsets.bottom
|
||||
}
|
||||
let itemContentInsets: UIEdgeInsets
|
||||
if isItemExpanded {
|
||||
itemContentInsets = itemLayout.expandedGrid.itemContainerInsets()
|
||||
} else {
|
||||
itemContentInsets = UIEdgeInsets()
|
||||
}
|
||||
|
||||
var itemControlInsets: UIEdgeInsets
|
||||
if isItemExpanded {
|
||||
itemControlInsets = itemContentInsets
|
||||
itemControlInsets.bottom = max(itemControlInsets.bottom, 96.0)
|
||||
} else {
|
||||
itemControlInsets = itemContentInsets
|
||||
}
|
||||
|
||||
let itemAlpha: CGFloat
|
||||
if isItemExpanded {
|
||||
itemAlpha = 1.0
|
||||
} else if component.expandedVideoState != nil && itemLayout.layout.videoColumn != nil {
|
||||
itemAlpha = 0.0
|
||||
} else {
|
||||
itemAlpha = 1.0
|
||||
}
|
||||
|
||||
let _ = itemView.view.update(
|
||||
@ -752,14 +851,16 @@ final class VideoChatParticipantsComponent: Component {
|
||||
isPresentation: videoParticipant.isPresentation,
|
||||
isSpeaking: component.speakingParticipants.contains(videoParticipant.participant.peer.id),
|
||||
isExpanded: isItemExpanded,
|
||||
bottomInset: itemBottomInset,
|
||||
isUIHidden: isItemUIHidden,
|
||||
contentInsets: itemContentInsets,
|
||||
controlInsets: itemControlInsets,
|
||||
rootVideoLoadingEffectView: self.rootVideoLoadingEffectView,
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
if component.expandedVideoState?.mainParticipant == videoParticipantKey {
|
||||
component.updateMainParticipant(nil)
|
||||
if let expandedVideoState = component.expandedVideoState, expandedVideoState.mainParticipant == videoParticipantKey {
|
||||
component.updateIsExpandedUIHidden(!expandedVideoState.isUIHidden)
|
||||
} else {
|
||||
component.updateMainParticipant(videoParticipantKey)
|
||||
}
|
||||
@ -770,6 +871,8 @@ final class VideoChatParticipantsComponent: Component {
|
||||
)
|
||||
if let itemComponentView = itemView.view.view {
|
||||
if itemComponentView.superview == nil {
|
||||
itemComponentView.layer.allowsGroupOpacity = true
|
||||
|
||||
if isItemExpanded {
|
||||
if let expandedThumbnailsView = self.expandedThumbnailsView?.view {
|
||||
self.expandedGridItemContainer.insertSubview(itemComponentView, belowSubview: expandedThumbnailsView)
|
||||
@ -781,12 +884,13 @@ final class VideoChatParticipantsComponent: Component {
|
||||
}
|
||||
|
||||
itemComponentView.frame = itemFrame
|
||||
itemComponentView.alpha = itemAlpha
|
||||
|
||||
if !resultingItemTransition.animation.isImmediate {
|
||||
resultingItemTransition.animateScale(view: itemComponentView, from: 0.001, to: 1.0)
|
||||
}
|
||||
if !resultingItemTransition.animation.isImmediate {
|
||||
itemComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||
if !resultingItemTransition.animation.isImmediate && itemAlpha != 0.0 {
|
||||
itemComponentView.layer.animateAlpha(from: 0.0, to: itemAlpha, duration: 0.1)
|
||||
}
|
||||
} else if isItemExpanded && itemComponentView.superview != self.expandedGridItemContainer {
|
||||
let fromFrame = itemComponentView.convert(itemComponentView.bounds, to: self.expandedGridItemContainer)
|
||||
@ -820,6 +924,14 @@ final class VideoChatParticipantsComponent: Component {
|
||||
if !itemView.isCollapsing {
|
||||
resultingItemTransition.setPosition(view: itemComponentView, position: itemFrame.center)
|
||||
resultingItemTransition.setBounds(view: itemComponentView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
|
||||
|
||||
let resultingItemAlphaTransition: ComponentTransition
|
||||
if !resultingItemTransition.animation.isImmediate {
|
||||
resultingItemAlphaTransition = alphaTransition
|
||||
} else {
|
||||
resultingItemAlphaTransition = .immediate
|
||||
}
|
||||
resultingItemAlphaTransition.setAlpha(view: itemComponentView, alpha: itemAlpha)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1017,6 +1129,18 @@ final class VideoChatParticipantsComponent: Component {
|
||||
))
|
||||
}*/
|
||||
|
||||
let expandedControlsAlpha: CGFloat = expandedVideoState.isUIHidden ? 0.0 : 1.0
|
||||
let expandedThumbnailsAlpha: CGFloat = expandedControlsAlpha
|
||||
/*if itemLayout.layout.videoColumn == nil {
|
||||
if expandedVideoState.isUIHidden {
|
||||
expandedThumbnailsAlpha = 0.0
|
||||
} else {
|
||||
expandedThumbnailsAlpha = 1.0
|
||||
}
|
||||
} else {
|
||||
expandedThumbnailsAlpha = 0.0
|
||||
}*/
|
||||
|
||||
var expandedThumbnailsTransition = transition
|
||||
let expandedThumbnailsView: ComponentView<Empty>
|
||||
if let current = self.expandedThumbnailsView {
|
||||
@ -1046,16 +1170,11 @@ final class VideoChatParticipantsComponent: Component {
|
||||
environment: {},
|
||||
containerSize: itemLayout.expandedGrid.itemContainerFrame().size
|
||||
)
|
||||
var expandedThumbnailsFrame = CGRect(origin: CGPoint(x: 0.0, y: expandedGridItemContainerFrame.height - expandedThumbnailsSize.height), size: expandedThumbnailsSize)
|
||||
switch itemLayout.layoutType {
|
||||
case .vertical:
|
||||
break
|
||||
case .horizontal:
|
||||
expandedThumbnailsFrame.origin.y -= itemLayout.expandedGrid.containerInsets.bottom
|
||||
}
|
||||
let expandedThumbnailsFrame = CGRect(origin: CGPoint(x: 0.0, y: expandedGridItemContainerFrame.height - expandedThumbnailsSize.height), size: expandedThumbnailsSize)
|
||||
if let expandedThumbnailsComponentView = expandedThumbnailsView.view {
|
||||
if expandedThumbnailsComponentView.superview == nil {
|
||||
self.expandedGridItemContainer.addSubview(expandedThumbnailsComponentView)
|
||||
expandedThumbnailsComponentView.alpha = expandedThumbnailsAlpha
|
||||
|
||||
let fromReferenceFrame: CGRect
|
||||
if let index = self.gridParticipants.firstIndex(where: { $0.participant.peer.id == expandedVideoState.mainParticipant.id && $0.isPresentation == expandedVideoState.mainParticipant.isPresentation }) {
|
||||
@ -1066,11 +1185,12 @@ final class VideoChatParticipantsComponent: Component {
|
||||
|
||||
expandedThumbnailsComponentView.frame = CGRect(origin: CGPoint(x: fromReferenceFrame.minX - previousExpandedGridItemContainerFrame.minX, y: fromReferenceFrame.maxY - expandedThumbnailsSize.height), size: expandedThumbnailsFrame.size)
|
||||
|
||||
if !transition.animation.isImmediate {
|
||||
if !transition.animation.isImmediate && expandedThumbnailsAlpha != 0.0 {
|
||||
expandedThumbnailsComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
}
|
||||
}
|
||||
transition.setFrame(view: expandedThumbnailsComponentView, frame: expandedThumbnailsFrame)
|
||||
alphaTransition.setAlpha(view: expandedThumbnailsComponentView, alpha: expandedThumbnailsAlpha)
|
||||
}
|
||||
|
||||
var expandedControlsTransition = transition
|
||||
@ -1113,6 +1233,8 @@ final class VideoChatParticipantsComponent: Component {
|
||||
if expandedControlsComponentView.superview == nil {
|
||||
self.expandedGridItemContainer.addSubview(expandedControlsComponentView)
|
||||
|
||||
expandedControlsComponentView.alpha = expandedControlsAlpha
|
||||
|
||||
let fromReferenceFrame: CGRect
|
||||
if let index = self.gridParticipants.firstIndex(where: { $0.participant.peer.id == expandedVideoState.mainParticipant.id && $0.isPresentation == expandedVideoState.mainParticipant.isPresentation }) {
|
||||
fromReferenceFrame = self.gridItemViewContainer.convert(itemLayout.gridItemFrame(at: index), to: self.expandedGridItemContainer)
|
||||
@ -1122,11 +1244,12 @@ final class VideoChatParticipantsComponent: Component {
|
||||
|
||||
expandedControlsComponentView.frame = CGRect(origin: CGPoint(x: fromReferenceFrame.minX - previousExpandedGridItemContainerFrame.minX, y: fromReferenceFrame.minY - previousExpandedGridItemContainerFrame.minY), size: expandedControlsFrame.size)
|
||||
|
||||
if !transition.animation.isImmediate {
|
||||
if !transition.animation.isImmediate && expandedControlsAlpha != 0.0 {
|
||||
expandedControlsComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
}
|
||||
}
|
||||
transition.setFrame(view: expandedControlsComponentView, frame: expandedControlsFrame)
|
||||
alphaTransition.setAlpha(view: expandedControlsComponentView, alpha: expandedControlsAlpha)
|
||||
}
|
||||
} else {
|
||||
if let expandedThumbnailsView = self.expandedThumbnailsView {
|
||||
@ -1142,7 +1265,7 @@ final class VideoChatParticipantsComponent: Component {
|
||||
let targetThumbnailsFrame = CGRect(origin: CGPoint(x: targetItemFrame.minX, y: targetItemFrame.maxY - expandedThumbnailsComponentView.bounds.height), size: expandedThumbnailsComponentView.bounds.size)
|
||||
transition.setFrame(view: expandedThumbnailsComponentView, frame: targetThumbnailsFrame)
|
||||
}
|
||||
expandedThumbnailsComponentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak expandedThumbnailsComponentView] _ in
|
||||
expandedThumbnailsComponentView.layer.animateAlpha(from: expandedThumbnailsComponentView.alpha, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak expandedThumbnailsComponentView] _ in
|
||||
expandedThumbnailsComponentView?.removeFromSuperview()
|
||||
})
|
||||
} else {
|
||||
@ -1163,7 +1286,7 @@ final class VideoChatParticipantsComponent: Component {
|
||||
let targetThumbnailsFrame = CGRect(origin: CGPoint(x: targetItemFrame.minX, y: targetItemFrame.minY), size: expandedControlsComponentView.bounds.size)
|
||||
transition.setFrame(view: expandedControlsComponentView, frame: targetThumbnailsFrame)
|
||||
}
|
||||
expandedControlsComponentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak expandedControlsComponentView] _ in
|
||||
expandedControlsComponentView.layer.animateAlpha(from: expandedControlsComponentView.alpha, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak expandedControlsComponentView] _ in
|
||||
expandedControlsComponentView?.removeFromSuperview()
|
||||
})
|
||||
} else {
|
||||
@ -1272,10 +1395,10 @@ final class VideoChatParticipantsComponent: Component {
|
||||
|
||||
let itemLayout = ItemLayout(
|
||||
containerSize: availableSize,
|
||||
layoutType: component.layoutType,
|
||||
sideInset: component.sideInset,
|
||||
collapsedContainerInsets: component.collapsedContainerInsets,
|
||||
expandedContainerInsets: component.expandedContainerInsets,
|
||||
layout: component.layout,
|
||||
isUIHidden: component.expandedVideoState?.isUIHidden ?? false,
|
||||
expandedInsets: component.expandedInsets,
|
||||
safeInsets: component.safeInsets,
|
||||
gridItemCount: gridParticipants.count,
|
||||
listItemCount: listParticipants.count,
|
||||
listItemHeight: measureListItemSize.height,
|
||||
@ -1290,9 +1413,9 @@ final class VideoChatParticipantsComponent: Component {
|
||||
cornerRadius: 10.0
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: itemLayout.list.containerSize.width, height: itemLayout.list.contentHeight())
|
||||
containerSize: CGSize(width: itemLayout.listFrame.width - itemLayout.layout.mainColumn.insets.left - itemLayout.layout.mainColumn.insets.right, height: itemLayout.list.contentHeight())
|
||||
)
|
||||
let listItemsBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: listItemsBackgroundSize)
|
||||
let listItemsBackgroundFrame = CGRect(origin: CGPoint(x: itemLayout.layout.mainColumn.insets.left, y: 0.0), size: listItemsBackgroundSize)
|
||||
if let listItemsBackgroundView = self.listItemsBackground.view {
|
||||
if listItemsBackgroundView.superview == nil {
|
||||
self.listItemViewContainer.addSubview(listItemsBackgroundView)
|
||||
@ -1375,12 +1498,11 @@ final class VideoChatParticipantsComponent: Component {
|
||||
|
||||
self.ignoreScrolling = false
|
||||
|
||||
switch component.layoutType {
|
||||
case .vertical:
|
||||
if itemLayout.layout.videoColumn == nil {
|
||||
if self.gridItemViewContainer.superview !== self.scrollView {
|
||||
self.scrollView.addSubview(self.gridItemViewContainer)
|
||||
}
|
||||
case .horizontal:
|
||||
} else {
|
||||
if self.gridItemViewContainer.superview !== self.separateVideoScrollView {
|
||||
self.separateVideoScrollView.addSubview(self.gridItemViewContainer)
|
||||
}
|
||||
|
@ -1041,9 +1041,9 @@ private final class VideoChatScreenComponent: Component {
|
||||
}) {
|
||||
if participant.peer.id != expandedParticipantsVideoState.mainParticipant.id {
|
||||
if participant.presentationDescription != nil {
|
||||
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: true), isMainParticipantPinned: false)
|
||||
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: true), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden)
|
||||
} else {
|
||||
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false)
|
||||
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1073,9 +1073,9 @@ private final class VideoChatScreenComponent: Component {
|
||||
return false
|
||||
}) {
|
||||
if participant.presentationDescription != nil {
|
||||
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: true), isMainParticipantPinned: false)
|
||||
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: true), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden)
|
||||
} else {
|
||||
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false)
|
||||
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden)
|
||||
}
|
||||
} else {
|
||||
self.expandedParticipantsVideoState = nil
|
||||
@ -1227,7 +1227,7 @@ private final class VideoChatScreenComponent: Component {
|
||||
}
|
||||
})
|
||||
|
||||
let sideInset: CGFloat = environment.safeInsets.left + 14.0
|
||||
let sideInset: CGFloat = max(environment.safeInsets.left, 14.0)
|
||||
|
||||
let topInset: CGFloat = environment.statusBarHeight + 2.0
|
||||
let navigationBarHeight: CGFloat = 61.0
|
||||
@ -1333,35 +1333,60 @@ private final class VideoChatScreenComponent: Component {
|
||||
)
|
||||
}
|
||||
|
||||
let participantsLayoutType: VideoChatParticipantsComponent.LayoutType
|
||||
if availableSize.width > 620.0 {
|
||||
let maxSingleColumnWidth: CGFloat = 620.0
|
||||
let isTwoColumnLayout: Bool
|
||||
if availableSize.width > maxSingleColumnWidth {
|
||||
if let mappedParticipants, mappedParticipants.participants.contains(where: { $0.videoDescription != nil || $0.presentationDescription != nil }) {
|
||||
participantsLayoutType = .horizontal(VideoChatParticipantsComponent.LayoutType.Horizontal(
|
||||
rightColumnWidth: 300.0,
|
||||
columnSpacing: 8.0,
|
||||
isCentered: false
|
||||
))
|
||||
isTwoColumnLayout = true
|
||||
} else {
|
||||
participantsLayoutType = .horizontal(VideoChatParticipantsComponent.LayoutType.Horizontal(
|
||||
rightColumnWidth: 380.0,
|
||||
columnSpacing: 0.0,
|
||||
isCentered: true
|
||||
))
|
||||
isTwoColumnLayout = false
|
||||
}
|
||||
} else {
|
||||
participantsLayoutType = .vertical
|
||||
isTwoColumnLayout = false
|
||||
}
|
||||
|
||||
let areButtonsCollapsed: Bool
|
||||
let mainColumnWidth: CGFloat
|
||||
let mainColumnSideInset: CGFloat
|
||||
|
||||
if isTwoColumnLayout {
|
||||
areButtonsCollapsed = false
|
||||
|
||||
mainColumnWidth = 320.0
|
||||
mainColumnSideInset = 0.0
|
||||
} else {
|
||||
areButtonsCollapsed = self.expandedParticipantsVideoState != nil
|
||||
|
||||
if availableSize.width > maxSingleColumnWidth {
|
||||
mainColumnWidth = 420.0
|
||||
mainColumnSideInset = 0.0
|
||||
} else {
|
||||
mainColumnWidth = availableSize.width
|
||||
mainColumnSideInset = sideInset
|
||||
}
|
||||
}
|
||||
|
||||
let actionButtonDiameter: CGFloat = 56.0
|
||||
let expandedMicrophoneButtonDiameter: CGFloat = actionButtonDiameter
|
||||
let collapsedMicrophoneButtonDiameter: CGFloat = 116.0
|
||||
var collapsedMicrophoneButtonDiameter: CGFloat = 116.0
|
||||
|
||||
let maxActionMicrophoneButtonSpacing: CGFloat = 38.0
|
||||
let minActionMicrophoneButtonSpacing: CGFloat = 20.0
|
||||
|
||||
if actionButtonDiameter * 2.0 + collapsedMicrophoneButtonDiameter + maxActionMicrophoneButtonSpacing * 2.0 > mainColumnWidth {
|
||||
collapsedMicrophoneButtonDiameter = mainColumnWidth - (actionButtonDiameter * 2.0 + minActionMicrophoneButtonSpacing * 2.0)
|
||||
collapsedMicrophoneButtonDiameter = max(actionButtonDiameter, collapsedMicrophoneButtonDiameter)
|
||||
}
|
||||
|
||||
let microphoneButtonDiameter: CGFloat
|
||||
if case .horizontal = participantsLayoutType {
|
||||
microphoneButtonDiameter = expandedMicrophoneButtonDiameter
|
||||
if isTwoColumnLayout {
|
||||
microphoneButtonDiameter = collapsedMicrophoneButtonDiameter
|
||||
} else {
|
||||
microphoneButtonDiameter = self.expandedParticipantsVideoState == nil ? collapsedMicrophoneButtonDiameter : expandedMicrophoneButtonDiameter
|
||||
if areButtonsCollapsed {
|
||||
microphoneButtonDiameter = expandedMicrophoneButtonDiameter
|
||||
} else {
|
||||
microphoneButtonDiameter = self.expandedParticipantsVideoState == nil ? collapsedMicrophoneButtonDiameter : expandedMicrophoneButtonDiameter
|
||||
}
|
||||
}
|
||||
|
||||
let buttonsSideInset: CGFloat = 42.0
|
||||
@ -1370,36 +1395,96 @@ private final class VideoChatScreenComponent: Component {
|
||||
let remainingButtonsSpace: CGFloat = availableSize.width - buttonsSideInset * 2.0 - buttonsWidth
|
||||
let actionMicrophoneButtonSpacing = min(maxActionMicrophoneButtonSpacing, floor(remainingButtonsSpace * 0.5))
|
||||
|
||||
let collapsedMicrophoneButtonFrame: CGRect = CGRect(origin: CGPoint(x: floor((availableSize.width - collapsedMicrophoneButtonDiameter) * 0.5), y: availableSize.height - 48.0 - environment.safeInsets.bottom - collapsedMicrophoneButtonDiameter), size: CGSize(width: collapsedMicrophoneButtonDiameter, height: collapsedMicrophoneButtonDiameter))
|
||||
let expandedMicrophoneButtonFrame: CGRect = CGRect(origin: CGPoint(x: floor((availableSize.width - expandedMicrophoneButtonDiameter) * 0.5), y: availableSize.height - environment.safeInsets.bottom - expandedMicrophoneButtonDiameter - 12.0), size: CGSize(width: expandedMicrophoneButtonDiameter, height: expandedMicrophoneButtonDiameter))
|
||||
|
||||
let microphoneButtonFrame: CGRect
|
||||
if case .horizontal = participantsLayoutType {
|
||||
microphoneButtonFrame = expandedMicrophoneButtonFrame
|
||||
} else {
|
||||
if self.expandedParticipantsVideoState == nil {
|
||||
microphoneButtonFrame = collapsedMicrophoneButtonFrame
|
||||
var collapsedMicrophoneButtonFrame: CGRect = CGRect(origin: CGPoint(x: floor((availableSize.width - collapsedMicrophoneButtonDiameter) * 0.5), y: availableSize.height - 48.0 - environment.safeInsets.bottom - collapsedMicrophoneButtonDiameter), size: CGSize(width: collapsedMicrophoneButtonDiameter, height: collapsedMicrophoneButtonDiameter))
|
||||
var expandedMicrophoneButtonFrame: CGRect = CGRect(origin: CGPoint(x: floor((availableSize.width - expandedMicrophoneButtonDiameter) * 0.5), y: availableSize.height - environment.safeInsets.bottom - expandedMicrophoneButtonDiameter - 12.0), size: CGSize(width: expandedMicrophoneButtonDiameter, height: expandedMicrophoneButtonDiameter))
|
||||
if isTwoColumnLayout {
|
||||
if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, expandedParticipantsVideoState.isUIHidden {
|
||||
collapsedMicrophoneButtonFrame.origin.x = availableSize.width - sideInset - mainColumnWidth + floor((mainColumnWidth - collapsedMicrophoneButtonDiameter) * 0.5) + sideInset + mainColumnWidth
|
||||
} else {
|
||||
microphoneButtonFrame = expandedMicrophoneButtonFrame
|
||||
collapsedMicrophoneButtonFrame.origin.x = availableSize.width - sideInset - mainColumnWidth + floor((mainColumnWidth - collapsedMicrophoneButtonDiameter) * 0.5)
|
||||
}
|
||||
expandedMicrophoneButtonFrame = collapsedMicrophoneButtonFrame
|
||||
} else {
|
||||
if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, expandedParticipantsVideoState.isUIHidden {
|
||||
expandedMicrophoneButtonFrame.origin.y = availableSize.height + expandedMicrophoneButtonDiameter + 12.0
|
||||
}
|
||||
}
|
||||
|
||||
let collapsedParticipantsClippingY: CGFloat = collapsedMicrophoneButtonFrame.minY - 16.0
|
||||
let expandedParticipantsClippingY: CGFloat = expandedMicrophoneButtonFrame.minY - 24.0
|
||||
let microphoneButtonFrame: CGRect
|
||||
if areButtonsCollapsed {
|
||||
microphoneButtonFrame = expandedMicrophoneButtonFrame
|
||||
} else {
|
||||
microphoneButtonFrame = collapsedMicrophoneButtonFrame
|
||||
}
|
||||
|
||||
let collapsedParticipantsClippingY: CGFloat
|
||||
collapsedParticipantsClippingY = collapsedMicrophoneButtonFrame.minY - 16.0
|
||||
|
||||
let expandedParticipantsClippingY: CGFloat
|
||||
if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, expandedParticipantsVideoState.isUIHidden {
|
||||
if isTwoColumnLayout {
|
||||
expandedParticipantsClippingY = expandedMicrophoneButtonFrame.minY - 24.0
|
||||
} else {
|
||||
expandedParticipantsClippingY = availableSize.height - max(14.0, environment.safeInsets.bottom)
|
||||
}
|
||||
} else {
|
||||
expandedParticipantsClippingY = expandedMicrophoneButtonFrame.minY - 24.0
|
||||
}
|
||||
|
||||
let leftActionButtonFrame = CGRect(origin: CGPoint(x: microphoneButtonFrame.minX - actionMicrophoneButtonSpacing - actionButtonDiameter, y: microphoneButtonFrame.minY + floor((microphoneButtonFrame.height - actionButtonDiameter) * 0.5)), size: CGSize(width: actionButtonDiameter, height: actionButtonDiameter))
|
||||
let rightActionButtonFrame = CGRect(origin: CGPoint(x: microphoneButtonFrame.maxX + actionMicrophoneButtonSpacing, y: microphoneButtonFrame.minY + floor((microphoneButtonFrame.height - actionButtonDiameter) * 0.5)), size: CGSize(width: actionButtonDiameter, height: actionButtonDiameter))
|
||||
|
||||
let participantsSize = availableSize
|
||||
let participantsCollapsedInsets: UIEdgeInsets
|
||||
let participantsExpandedInsets: UIEdgeInsets
|
||||
|
||||
if case .horizontal = participantsLayoutType {
|
||||
participantsCollapsedInsets = UIEdgeInsets(top: navigationHeight, left: environment.safeInsets.left, bottom: availableSize.height - (expandedMicrophoneButtonFrame.minY - 16.0), right: environment.safeInsets.right)
|
||||
participantsExpandedInsets = participantsCollapsedInsets
|
||||
let columnSpacing: CGFloat = 14.0
|
||||
let participantsLayout: VideoChatParticipantsComponent.Layout
|
||||
if isTwoColumnLayout {
|
||||
let mainColumnInsets: UIEdgeInsets = UIEdgeInsets(top: navigationHeight, left: mainColumnSideInset, bottom: availableSize.height - collapsedParticipantsClippingY, right: mainColumnSideInset)
|
||||
let videoColumnWidth: CGFloat = max(10.0, availableSize.width - sideInset * 2.0 - mainColumnWidth - columnSpacing)
|
||||
participantsLayout = VideoChatParticipantsComponent.Layout(
|
||||
videoColumn: VideoChatParticipantsComponent.Layout.Column(
|
||||
width: videoColumnWidth,
|
||||
insets: UIEdgeInsets(top: navigationHeight, left: 0.0, bottom: max(14.0, environment.safeInsets.bottom), right: 0.0)
|
||||
),
|
||||
mainColumn: VideoChatParticipantsComponent.Layout.Column(
|
||||
width: mainColumnWidth,
|
||||
insets: mainColumnInsets
|
||||
),
|
||||
columnSpacing: columnSpacing
|
||||
)
|
||||
} else {
|
||||
participantsCollapsedInsets = UIEdgeInsets(top: navigationHeight, left: environment.safeInsets.left, bottom: availableSize.height - collapsedParticipantsClippingY, right: environment.safeInsets.right)
|
||||
participantsExpandedInsets = UIEdgeInsets(top: environment.statusBarHeight, left: environment.safeInsets.left, bottom: availableSize.height - expandedParticipantsClippingY, right: environment.safeInsets.right)
|
||||
let mainColumnInsets: UIEdgeInsets = UIEdgeInsets(top: navigationHeight, left: mainColumnSideInset, bottom: availableSize.height - collapsedParticipantsClippingY, right: mainColumnSideInset)
|
||||
participantsLayout = VideoChatParticipantsComponent.Layout(
|
||||
videoColumn: nil,
|
||||
mainColumn: VideoChatParticipantsComponent.Layout.Column(
|
||||
width: mainColumnWidth,
|
||||
insets: mainColumnInsets
|
||||
),
|
||||
columnSpacing: columnSpacing
|
||||
)
|
||||
}
|
||||
|
||||
let participantsSafeInsets = UIEdgeInsets(
|
||||
top: environment.statusBarHeight,
|
||||
left: environment.safeInsets.left,
|
||||
bottom: max(14.0, environment.safeInsets.bottom),
|
||||
right: environment.safeInsets.right
|
||||
)
|
||||
let participantsExpandedInsets: UIEdgeInsets
|
||||
if isTwoColumnLayout {
|
||||
participantsExpandedInsets = UIEdgeInsets(
|
||||
top: navigationHeight,
|
||||
left: max(14.0, participantsSafeInsets.left),
|
||||
bottom: participantsSafeInsets.bottom,
|
||||
right: max(14.0, participantsSafeInsets.right)
|
||||
)
|
||||
} else {
|
||||
participantsExpandedInsets = UIEdgeInsets(
|
||||
top: participantsSafeInsets.top,
|
||||
left: participantsSafeInsets.left,
|
||||
bottom: availableSize.height - expandedParticipantsClippingY,
|
||||
right: participantsSafeInsets.right
|
||||
)
|
||||
}
|
||||
|
||||
let _ = self.participants.update(
|
||||
@ -1411,10 +1496,9 @@ private final class VideoChatScreenComponent: Component {
|
||||
expandedVideoState: self.expandedParticipantsVideoState,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
layoutType: participantsLayoutType,
|
||||
collapsedContainerInsets: participantsCollapsedInsets,
|
||||
expandedContainerInsets: participantsExpandedInsets,
|
||||
sideInset: sideInset,
|
||||
layout: participantsLayout,
|
||||
expandedInsets: participantsExpandedInsets,
|
||||
safeInsets: participantsSafeInsets,
|
||||
updateMainParticipant: { [weak self] key in
|
||||
guard let self else {
|
||||
return
|
||||
@ -1423,7 +1507,7 @@ private final class VideoChatScreenComponent: Component {
|
||||
if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, expandedParticipantsVideoState.mainParticipant == key {
|
||||
return
|
||||
}
|
||||
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: key, isMainParticipantPinned: false)
|
||||
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: key, isMainParticipantPinned: false, isUIHidden: self.expandedParticipantsVideoState?.isUIHidden ?? false)
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
} else if self.expandedParticipantsVideoState != nil {
|
||||
self.expandedParticipantsVideoState = nil
|
||||
@ -1439,7 +1523,25 @@ private final class VideoChatScreenComponent: Component {
|
||||
}
|
||||
let updatedExpandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(
|
||||
mainParticipant: expandedParticipantsVideoState.mainParticipant,
|
||||
isMainParticipantPinned: isPinned
|
||||
isMainParticipantPinned: isPinned,
|
||||
isUIHidden: expandedParticipantsVideoState.isUIHidden
|
||||
)
|
||||
if self.expandedParticipantsVideoState != updatedExpandedParticipantsVideoState {
|
||||
self.expandedParticipantsVideoState = updatedExpandedParticipantsVideoState
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
},
|
||||
updateIsExpandedUIHidden: { [weak self] isUIHidden in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
guard let expandedParticipantsVideoState = self.expandedParticipantsVideoState else {
|
||||
return
|
||||
}
|
||||
let updatedExpandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(
|
||||
mainParticipant: expandedParticipantsVideoState.mainParticipant,
|
||||
isMainParticipantPinned: expandedParticipantsVideoState.isMainParticipantPinned,
|
||||
isUIHidden: isUIHidden
|
||||
)
|
||||
if self.expandedParticipantsVideoState != updatedExpandedParticipantsVideoState {
|
||||
self.expandedParticipantsVideoState = updatedExpandedParticipantsVideoState
|
||||
@ -1484,13 +1586,6 @@ private final class VideoChatScreenComponent: Component {
|
||||
actionButtonMicrophoneState = .connecting
|
||||
}
|
||||
|
||||
let areButtonsCollapsed: Bool
|
||||
if case .horizontal = participantsLayoutType {
|
||||
areButtonsCollapsed = true
|
||||
} else {
|
||||
areButtonsCollapsed = self.expandedParticipantsVideoState != nil
|
||||
}
|
||||
|
||||
let _ = self.microphoneButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(VideoChatMicButtonComponent(
|
||||
|
Loading…
x
Reference in New Issue
Block a user