mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
3a9cacfa68
@ -1370,7 +1370,7 @@ public class BrowserScreen: ViewController, MinimizableController {
|
||||
inputHeight: layout.inputHeight ?? 0.0,
|
||||
metrics: layout.metrics,
|
||||
deviceMetrics: layout.deviceMetrics,
|
||||
orientation: nil,
|
||||
orientation: layout.metrics.orientation,
|
||||
isVisible: true,
|
||||
theme: self.presentationData.theme,
|
||||
strings: self.presentationData.strings,
|
||||
|
@ -65,7 +65,7 @@ open class ViewControllerComponentContainer: ViewController {
|
||||
inputHeight: CGFloat,
|
||||
metrics: LayoutMetrics,
|
||||
deviceMetrics: DeviceMetrics,
|
||||
orientation: UIInterfaceOrientation? = nil,
|
||||
orientation: UIInterfaceOrientation?,
|
||||
isVisible: Bool,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
@ -177,6 +177,7 @@ open class ViewControllerComponentContainer: ViewController {
|
||||
inputHeight: layout.inputHeight ?? 0.0,
|
||||
metrics: layout.metrics,
|
||||
deviceMetrics: layout.deviceMetrics,
|
||||
orientation: layout.metrics.orientation,
|
||||
isVisible: self.currentIsVisible,
|
||||
theme: self.resolvedTheme,
|
||||
strings: self.presentationData.strings,
|
||||
|
@ -102,7 +102,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
case playlistPlayback(Bool)
|
||||
case enableQuickReactionSwitch(Bool)
|
||||
case disableReloginTokens(Bool)
|
||||
case callV2(Bool)
|
||||
case disableCallV2(Bool)
|
||||
case experimentalCallMute(Bool)
|
||||
case liveStreamV2(Bool)
|
||||
case preferredVideoCodec(Int, String, String?, Bool)
|
||||
@ -129,7 +129,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return DebugControllerSection.web.rawValue
|
||||
case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .callV2, .experimentalCallMute, .liveStreamV2:
|
||||
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .disableCallV2, .experimentalCallMute, .liveStreamV2:
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
case .logTranslationRecognition, .resetTranslationStates:
|
||||
return DebugControllerSection.translation.rawValue
|
||||
@ -242,7 +242,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return 49
|
||||
case .enableQuickReactionSwitch:
|
||||
return 50
|
||||
case .callV2:
|
||||
case .disableCallV2:
|
||||
return 51
|
||||
case .experimentalCallMute:
|
||||
return 52
|
||||
@ -1318,12 +1318,12 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
})
|
||||
}).start()
|
||||
})
|
||||
case let .callV2(value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "[WIP] Video Chat V2", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
case let .disableCallV2(value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Disable Video Chat V2", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
|
||||
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
|
||||
settings.callV2 = value
|
||||
settings.disableCallV2 = value
|
||||
return PreferencesEntry(settings)
|
||||
})
|
||||
}).start()
|
||||
@ -1502,7 +1502,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
||||
}
|
||||
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
||||
entries.append(.enableQuickReactionSwitch(!experimentalSettings.disableQuickReaction))
|
||||
entries.append(.callV2(experimentalSettings.callV2))
|
||||
entries.append(.disableCallV2(experimentalSettings.disableCallV2))
|
||||
entries.append(.experimentalCallMute(experimentalSettings.experimentalCallMute))
|
||||
entries.append(.liveStreamV2(experimentalSettings.liveStreamV2))
|
||||
}
|
||||
|
@ -481,6 +481,7 @@ public class ReplaceBoostScreen: ViewController {
|
||||
inputHeight: layout.inputHeight ?? 0.0,
|
||||
metrics: layout.metrics,
|
||||
deviceMetrics: layout.deviceMetrics,
|
||||
orientation: layout.metrics.orientation,
|
||||
isVisible: self.currentIsVisible,
|
||||
theme: self.presentationData.theme,
|
||||
strings: self.presentationData.strings,
|
||||
|
@ -335,7 +335,7 @@ final class MediaStreamVideoComponent: Component {
|
||||
stallTimer = _stallTimer
|
||||
self.clipsToBounds = component.isFullscreen // or just true
|
||||
|
||||
if let videoView = self.videoRenderingContext.makeView(input: input, forceSampleBufferDisplayLayer: true) {
|
||||
if let videoView = self.videoRenderingContext.makeView(input: input, blur: false, forceSampleBufferDisplayLayer: true) {
|
||||
self.videoView = videoView
|
||||
self.addSubview(videoView)
|
||||
videoView.alpha = 0
|
||||
|
@ -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 {
|
||||
@ -289,20 +288,26 @@ final class VideoChatParticipantThumbnailComponent: Component {
|
||||
var rotatedVideoResolution = videoResolution
|
||||
var rotatedVideoFrame = videoFrame
|
||||
var rotatedBlurredVideoFrame = blurredVideoFrame
|
||||
var rotatedVideoBoundsSize = videoFrame.size
|
||||
var rotatedBlurredVideoBoundsSize = blurredVideoFrame.size
|
||||
|
||||
if videoIsRotated {
|
||||
rotatedVideoResolution = CGSize(width: rotatedVideoResolution.height, height: rotatedVideoResolution.width)
|
||||
rotatedVideoBoundsSize = CGSize(width: rotatedVideoBoundsSize.height, height: rotatedVideoBoundsSize.width)
|
||||
rotatedVideoFrame = rotatedVideoFrame.size.centered(around: rotatedVideoFrame.center)
|
||||
|
||||
rotatedBlurredVideoBoundsSize = CGSize(width: rotatedBlurredVideoBoundsSize.height, height: rotatedBlurredVideoBoundsSize.width)
|
||||
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))
|
||||
transition.setBounds(layer: videoLayer, bounds: CGRect(origin: CGPoint(), size: rotatedVideoBoundsSize))
|
||||
transition.setTransform(layer: videoLayer, transform: CATransform3DMakeRotation(CGFloat(videoSpec.rotationAngle), 0.0, 0.0, 1.0))
|
||||
videoLayer.renderSpec = RenderLayerSpec(size: RenderSize(width: Int(rotatedVideoResolution.width), height: Int(rotatedVideoResolution.height)), edgeInset: 2)
|
||||
|
||||
transition.setPosition(layer: videoLayer.blurredLayer, position: rotatedBlurredVideoFrame.center)
|
||||
transition.setBounds(layer: videoLayer.blurredLayer, bounds: CGRect(origin: CGPoint(), size: rotatedBlurredVideoFrame.size))
|
||||
transition.setBounds(layer: videoLayer.blurredLayer, bounds: CGRect(origin: CGPoint(), size: rotatedBlurredVideoBoundsSize))
|
||||
transition.setTransform(layer: videoLayer.blurredLayer, transform: CATransform3DMakeRotation(CGFloat(videoSpec.rotationAngle), 0.0, 0.0, 1.0))
|
||||
}
|
||||
} else {
|
||||
@ -330,11 +335,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 {
|
||||
|
@ -9,13 +9,16 @@ import BundleIconComponent
|
||||
final class VideoChatListInviteComponent: Component {
|
||||
let title: String
|
||||
let theme: PresentationTheme
|
||||
let action: () -> Void
|
||||
|
||||
init(
|
||||
title: String,
|
||||
theme: PresentationTheme
|
||||
theme: PresentationTheme,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.title = title
|
||||
self.theme = theme
|
||||
self.action = action
|
||||
}
|
||||
|
||||
static func ==(lhs: VideoChatListInviteComponent, rhs: VideoChatListInviteComponent) -> Bool {
|
||||
@ -28,21 +31,61 @@ final class VideoChatListInviteComponent: Component {
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
final class View: HighlightTrackingButton {
|
||||
private let icon = ComponentView<Empty>()
|
||||
private let title = ComponentView<Empty>()
|
||||
|
||||
private var component: VideoChatListInviteComponent?
|
||||
private var isUpdating: Bool = false
|
||||
|
||||
private var highlightBackgroundLayer: SimpleLayer?
|
||||
private var highlightBackgroundFrame: CGRect?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.highligthedChanged = { [weak self] isHighlighted in
|
||||
guard let self, let component = self.component, let highlightBackgroundFrame = self.highlightBackgroundFrame else {
|
||||
return
|
||||
}
|
||||
|
||||
if isHighlighted {
|
||||
self.superview?.bringSubviewToFront(self)
|
||||
|
||||
let highlightBackgroundLayer: SimpleLayer
|
||||
if let current = self.highlightBackgroundLayer {
|
||||
highlightBackgroundLayer = current
|
||||
} else {
|
||||
highlightBackgroundLayer = SimpleLayer()
|
||||
self.highlightBackgroundLayer = highlightBackgroundLayer
|
||||
self.layer.insertSublayer(highlightBackgroundLayer, at: 0)
|
||||
highlightBackgroundLayer.backgroundColor = component.theme.list.itemHighlightedBackgroundColor.cgColor
|
||||
}
|
||||
highlightBackgroundLayer.frame = highlightBackgroundFrame
|
||||
highlightBackgroundLayer.opacity = 1.0
|
||||
} else {
|
||||
if let highlightBackgroundLayer = self.highlightBackgroundLayer {
|
||||
self.highlightBackgroundLayer = nil
|
||||
highlightBackgroundLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak highlightBackgroundLayer] _ in
|
||||
highlightBackgroundLayer?.removeFromSuperlayer()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.action()
|
||||
}
|
||||
|
||||
func update(component: VideoChatListInviteComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
@ -65,6 +108,7 @@ final class VideoChatListInviteComponent: Component {
|
||||
let titleFrame = CGRect(origin: CGPoint(x: 62.0, y: floor((size.height - titleSize.height) * 0.5)), size: titleSize)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
titleView.isUserInteractionEnabled = false
|
||||
titleView.layer.anchorPoint = CGPoint()
|
||||
self.addSubview(titleView)
|
||||
}
|
||||
@ -84,11 +128,14 @@ final class VideoChatListInviteComponent: Component {
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((62.0 - iconSize.width) * 0.5), y: floor((size.height - iconSize.height) * 0.5)), size: iconSize)
|
||||
if let iconView = self.icon.view {
|
||||
if iconView.superview == nil {
|
||||
iconView.isUserInteractionEnabled = false
|
||||
self.addSubview(iconView)
|
||||
}
|
||||
transition.setFrame(view: iconView, frame: iconFrame)
|
||||
}
|
||||
|
||||
//self.highlightBackgroundFrame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -3,24 +3,25 @@ import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import TelegramPresentationData
|
||||
import TelegramCore
|
||||
|
||||
final class VideoChatParticipantStatusComponent: Component {
|
||||
let isMuted: Bool
|
||||
let muteState: GroupCallParticipantsContext.Participant.MuteState?
|
||||
let isSpeaking: Bool
|
||||
let theme: PresentationTheme
|
||||
|
||||
init(
|
||||
isMuted: Bool,
|
||||
muteState: GroupCallParticipantsContext.Participant.MuteState?,
|
||||
isSpeaking: Bool,
|
||||
theme: PresentationTheme
|
||||
) {
|
||||
self.isMuted = isMuted
|
||||
self.muteState = muteState
|
||||
self.isSpeaking = isSpeaking
|
||||
self.theme = theme
|
||||
}
|
||||
|
||||
static func ==(lhs: VideoChatParticipantStatusComponent, rhs: VideoChatParticipantStatusComponent) -> Bool {
|
||||
if lhs.isMuted != rhs.isMuted {
|
||||
if lhs.muteState != rhs.muteState {
|
||||
return false
|
||||
}
|
||||
if lhs.isSpeaking != rhs.isSpeaking {
|
||||
@ -61,8 +62,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.muteState != nil && !component.isSpeaking)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 36.0, height: 36.0)
|
||||
@ -80,7 +80,24 @@ 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 {
|
||||
let iconTintColor: UIColor
|
||||
if component.isSpeaking {
|
||||
iconTintColor = UIColor(rgb: 0x33C758)
|
||||
} else {
|
||||
if let muteState = component.muteState {
|
||||
if muteState.canUnmute {
|
||||
iconTintColor = UIColor(white: 1.0, alpha: 0.4)
|
||||
} else {
|
||||
iconTintColor = UIColor(rgb: 0xFF3B30)
|
||||
}
|
||||
} else {
|
||||
iconTintColor = UIColor(white: 1.0, alpha: 0.4)
|
||||
}
|
||||
}
|
||||
|
||||
tintTransition.setTintColor(layer: iconView.layer, color: iconTintColor)
|
||||
}
|
||||
}
|
||||
|
||||
return size
|
||||
|
@ -40,7 +40,10 @@ 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
|
||||
let interfaceOrientation: UIInterfaceOrientation
|
||||
weak var rootVideoLoadingEffectView: VideoChatVideoLoadingEffectView?
|
||||
let action: (() -> Void)?
|
||||
|
||||
@ -50,7 +53,10 @@ final class VideoChatParticipantVideoComponent: Component {
|
||||
isPresentation: Bool,
|
||||
isSpeaking: Bool,
|
||||
isExpanded: Bool,
|
||||
bottomInset: CGFloat,
|
||||
isUIHidden: Bool,
|
||||
contentInsets: UIEdgeInsets,
|
||||
controlInsets: UIEdgeInsets,
|
||||
interfaceOrientation: UIInterfaceOrientation,
|
||||
rootVideoLoadingEffectView: VideoChatVideoLoadingEffectView?,
|
||||
action: (() -> Void)?
|
||||
) {
|
||||
@ -59,7 +65,10 @@ 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.interfaceOrientation = interfaceOrientation
|
||||
self.rootVideoLoadingEffectView = rootVideoLoadingEffectView
|
||||
self.action = action
|
||||
}
|
||||
@ -77,7 +86,16 @@ 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.interfaceOrientation != rhs.interfaceOrientation {
|
||||
return false
|
||||
}
|
||||
if (lhs.action == nil) != (rhs.action == nil) {
|
||||
@ -89,10 +107,12 @@ final class VideoChatParticipantVideoComponent: Component {
|
||||
private struct VideoSpec: Equatable {
|
||||
var resolution: CGSize
|
||||
var rotationAngle: Float
|
||||
var followsDeviceOrientation: Bool
|
||||
|
||||
init(resolution: CGSize, rotationAngle: Float) {
|
||||
init(resolution: CGSize, rotationAngle: Float, followsDeviceOrientation: Bool) {
|
||||
self.resolution = resolution
|
||||
self.rotationAngle = rotationAngle
|
||||
self.followsDeviceOrientation = followsDeviceOrientation
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,6 +173,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 +239,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 +271,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 {
|
||||
@ -296,7 +328,7 @@ final class VideoChatParticipantVideoComponent: Component {
|
||||
videoLayer.video = videoOutput
|
||||
|
||||
if let videoOutput {
|
||||
let videoSpec = VideoSpec(resolution: videoOutput.resolution, rotationAngle: videoOutput.rotationAngle)
|
||||
let videoSpec = VideoSpec(resolution: videoOutput.resolution, rotationAngle: videoOutput.rotationAngle, followsDeviceOrientation: videoOutput.followsDeviceOrientation)
|
||||
if self.videoSpec != videoSpec {
|
||||
self.videoSpec = videoSpec
|
||||
if !self.isUpdating {
|
||||
@ -311,69 +343,6 @@ final class VideoChatParticipantVideoComponent: Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*var notifyOrientationUpdated = false
|
||||
var notifyIsMirroredUpdated = false
|
||||
|
||||
if !self.didReportFirstFrame {
|
||||
notifyOrientationUpdated = true
|
||||
notifyIsMirroredUpdated = true
|
||||
}
|
||||
|
||||
if let currentOutput = videoOutput {
|
||||
let currentAspect: CGFloat
|
||||
if currentOutput.resolution.height > 0.0 {
|
||||
currentAspect = currentOutput.resolution.width / currentOutput.resolution.height
|
||||
} else {
|
||||
currentAspect = 1.0
|
||||
}
|
||||
if self.currentAspect != currentAspect {
|
||||
self.currentAspect = currentAspect
|
||||
notifyOrientationUpdated = true
|
||||
}
|
||||
|
||||
let currentOrientation: PresentationCallVideoView.Orientation
|
||||
if currentOutput.followsDeviceOrientation {
|
||||
currentOrientation = .rotation0
|
||||
} else {
|
||||
if abs(currentOutput.rotationAngle - 0.0) < .ulpOfOne {
|
||||
currentOrientation = .rotation0
|
||||
} else if abs(currentOutput.rotationAngle - Float.pi * 0.5) < .ulpOfOne {
|
||||
currentOrientation = .rotation90
|
||||
} else if abs(currentOutput.rotationAngle - Float.pi) < .ulpOfOne {
|
||||
currentOrientation = .rotation180
|
||||
} else if abs(currentOutput.rotationAngle - Float.pi * 3.0 / 2.0) < .ulpOfOne {
|
||||
currentOrientation = .rotation270
|
||||
} else {
|
||||
currentOrientation = .rotation0
|
||||
}
|
||||
}
|
||||
if self.currentOrientation != currentOrientation {
|
||||
self.currentOrientation = currentOrientation
|
||||
notifyOrientationUpdated = true
|
||||
}
|
||||
|
||||
let currentIsMirrored = !currentOutput.mirrorDirection.isEmpty
|
||||
if self.currentIsMirrored != currentIsMirrored {
|
||||
self.currentIsMirrored = currentIsMirrored
|
||||
notifyIsMirroredUpdated = true
|
||||
}
|
||||
}
|
||||
|
||||
if !self.didReportFirstFrame {
|
||||
self.didReportFirstFrame = true
|
||||
self.onFirstFrameReceived?(Float(self.currentAspect))
|
||||
}
|
||||
|
||||
if notifyOrientationUpdated {
|
||||
self.onOrientationUpdated?(self.currentOrientation, self.currentAspect)
|
||||
}
|
||||
|
||||
if notifyIsMirroredUpdated {
|
||||
self.onIsMirroredUpdated?(self.currentIsMirrored)
|
||||
}*/
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -383,9 +352,11 @@ final class VideoChatParticipantVideoComponent: Component {
|
||||
if let videoSpec = self.videoSpec {
|
||||
videoBackgroundLayer.isHidden = false
|
||||
|
||||
let rotationAngle = resolveCallVideoRotationAngle(angle: videoSpec.rotationAngle, followsDeviceOrientation: videoSpec.followsDeviceOrientation, interfaceOrientation: component.interfaceOrientation)
|
||||
|
||||
var rotatedResolution = videoSpec.resolution
|
||||
var videoIsRotated = false
|
||||
if abs(videoSpec.rotationAngle - Float.pi * 0.5) < .ulpOfOne || abs(videoSpec.rotationAngle - Float.pi * 3.0 / 2.0) < .ulpOfOne {
|
||||
if abs(rotationAngle - Float.pi * 0.5) < .ulpOfOne || abs(rotationAngle - Float.pi * 3.0 / 2.0) < .ulpOfOne {
|
||||
videoIsRotated = true
|
||||
}
|
||||
if videoIsRotated {
|
||||
@ -397,26 +368,31 @@ 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
|
||||
var rotatedBlurredVideoFrame = blurredVideoFrame
|
||||
var rotatedVideoBoundsSize = videoFrame.size
|
||||
var rotatedBlurredVideoBoundsSize = blurredVideoFrame.size
|
||||
|
||||
if videoIsRotated {
|
||||
rotatedVideoResolution = CGSize(width: rotatedVideoResolution.height, height: rotatedVideoResolution.width)
|
||||
rotatedVideoBoundsSize = CGSize(width: rotatedVideoBoundsSize.height, height: rotatedVideoBoundsSize.width)
|
||||
rotatedVideoFrame = rotatedVideoFrame.size.centered(around: rotatedVideoFrame.center)
|
||||
|
||||
rotatedBlurredVideoBoundsSize = CGSize(width: rotatedBlurredVideoBoundsSize.height, height: rotatedBlurredVideoBoundsSize.width)
|
||||
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))
|
||||
transition.setTransform(layer: videoLayer, transform: CATransform3DMakeRotation(CGFloat(videoSpec.rotationAngle), 0.0, 0.0, 1.0))
|
||||
transition.setBounds(layer: videoLayer, bounds: CGRect(origin: CGPoint(), size: rotatedVideoBoundsSize))
|
||||
transition.setTransform(layer: videoLayer, transform: CATransform3DMakeRotation(CGFloat(rotationAngle), 0.0, 0.0, 1.0))
|
||||
videoLayer.renderSpec = RenderLayerSpec(size: RenderSize(width: Int(rotatedVideoResolution.width), height: Int(rotatedVideoResolution.height)), edgeInset: 2)
|
||||
|
||||
transition.setPosition(layer: videoLayer.blurredLayer, position: rotatedBlurredVideoFrame.center)
|
||||
transition.setBounds(layer: videoLayer.blurredLayer, bounds: CGRect(origin: CGPoint(), size: rotatedBlurredVideoFrame.size))
|
||||
transition.setTransform(layer: videoLayer.blurredLayer, transform: CATransform3DMakeRotation(CGFloat(videoSpec.rotationAngle), 0.0, 0.0, 1.0))
|
||||
transition.setBounds(layer: videoLayer.blurredLayer, bounds: CGRect(origin: CGPoint(), size: rotatedBlurredVideoBoundsSize))
|
||||
transition.setTransform(layer: videoLayer.blurredLayer, transform: CATransform3DMakeRotation(CGFloat(rotationAngle), 0.0, 0.0, 1.0))
|
||||
}
|
||||
} else {
|
||||
if let videoBackgroundLayer = self.videoBackgroundLayer {
|
||||
@ -439,6 +415,7 @@ final class VideoChatParticipantVideoComponent: Component {
|
||||
self.loadingEffectView = loadingEffectView
|
||||
self.addSubview(loadingEffectView.view)
|
||||
rootVideoLoadingEffectView.portalSource.addPortal(view: loadingEffectView)
|
||||
loadingEffectView.view.isUserInteractionEnabled = false
|
||||
loadingEffectView.view.frame = CGRect(origin: CGPoint(), size: availableSize)
|
||||
}
|
||||
}
|
||||
|
@ -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,15 @@ 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 interfaceOrientation: UIInterfaceOrientation
|
||||
let openParticipantContextMenu: (EnginePeer.Id, ContextExtractedContentContainingView, ContextGesture?) -> Void
|
||||
let updateMainParticipant: (VideoParticipantKey?) -> Void
|
||||
let updateIsMainParticipantPinned: (Bool) -> Void
|
||||
let updateIsExpandedUIHidden: (Bool) -> Void
|
||||
let openInviteMembers: () -> Void
|
||||
|
||||
init(
|
||||
call: PresentationGroupCall,
|
||||
@ -115,12 +128,15 @@ final class VideoChatParticipantsComponent: Component {
|
||||
expandedVideoState: ExpandedVideoState?,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
layoutType: LayoutType,
|
||||
collapsedContainerInsets: UIEdgeInsets,
|
||||
expandedContainerInsets: UIEdgeInsets,
|
||||
sideInset: CGFloat,
|
||||
layout: Layout,
|
||||
expandedInsets: UIEdgeInsets,
|
||||
safeInsets: UIEdgeInsets,
|
||||
interfaceOrientation: UIInterfaceOrientation,
|
||||
openParticipantContextMenu: @escaping (EnginePeer.Id, ContextExtractedContentContainingView, ContextGesture?) -> Void,
|
||||
updateMainParticipant: @escaping (VideoParticipantKey?) -> Void,
|
||||
updateIsMainParticipantPinned: @escaping (Bool) -> Void
|
||||
updateIsMainParticipantPinned: @escaping (Bool) -> Void,
|
||||
updateIsExpandedUIHidden: @escaping (Bool) -> Void,
|
||||
openInviteMembers: @escaping () -> Void
|
||||
) {
|
||||
self.call = call
|
||||
self.participants = participants
|
||||
@ -128,12 +144,15 @@ 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.interfaceOrientation = interfaceOrientation
|
||||
self.openParticipantContextMenu = openParticipantContextMenu
|
||||
self.updateMainParticipant = updateMainParticipant
|
||||
self.updateIsMainParticipantPinned = updateIsMainParticipantPinned
|
||||
self.updateIsExpandedUIHidden = updateIsExpandedUIHidden
|
||||
self.openInviteMembers = openInviteMembers
|
||||
}
|
||||
|
||||
static func ==(lhs: VideoChatParticipantsComponent, rhs: VideoChatParticipantsComponent) -> Bool {
|
||||
@ -152,16 +171,16 @@ 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 {
|
||||
if lhs.safeInsets != rhs.safeInsets {
|
||||
return false
|
||||
}
|
||||
if lhs.sideInset != rhs.sideInset {
|
||||
if lhs.interfaceOrientation != rhs.interfaceOrientation {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@ -178,40 +197,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 +300,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 +385,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 +400,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 +442,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 +502,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 +518,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()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -444,6 +530,14 @@ final class VideoChatParticipantsComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
private struct ExpandedGridSwipeState {
|
||||
var fraction: CGFloat
|
||||
|
||||
init(fraction: CGFloat) {
|
||||
self.fraction = fraction
|
||||
}
|
||||
}
|
||||
|
||||
private final class VideoParticipant: Equatable {
|
||||
let participant: GroupCallParticipantsContext.Participant
|
||||
let isPresentation: Bool
|
||||
@ -496,6 +590,7 @@ final class VideoChatParticipantsComponent: Component {
|
||||
private let separateVideoScrollView: ScrollView
|
||||
|
||||
private var component: VideoChatParticipantsComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
private var isUpdating: Bool = false
|
||||
|
||||
private var ignoreScrolling: Bool = false
|
||||
@ -519,6 +614,7 @@ final class VideoChatParticipantsComponent: Component {
|
||||
private let listItemsBackground = ComponentView<Empty>()
|
||||
|
||||
private var itemLayout: ItemLayout?
|
||||
private var expandedGridSwipeState: ExpandedGridSwipeState?
|
||||
|
||||
private var appliedGridIsEmpty: Bool = true
|
||||
|
||||
@ -581,6 +677,8 @@ final class VideoChatParticipantsComponent: Component {
|
||||
|
||||
self.scrollView.addSubview(self.listItemViewContainer)
|
||||
self.addSubview(self.expandedGridItemContainer)
|
||||
|
||||
self.expandedGridItemContainer.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.expandedGridPanGesture(_:))))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@ -609,6 +707,35 @@ final class VideoChatParticipantsComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func expandedGridPanGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
if self.bounds.height == 0.0 {
|
||||
return
|
||||
}
|
||||
switch recognizer.state {
|
||||
case .began, .changed:
|
||||
let translation = recognizer.translation(in: self)
|
||||
let fraction = translation.y / self.bounds.height
|
||||
self.expandedGridSwipeState = ExpandedGridSwipeState(fraction: fraction)
|
||||
self.state?.updated(transition: .immediate)
|
||||
case .ended, .cancelled:
|
||||
let translation = recognizer.translation(in: self)
|
||||
let fraction = translation.y / self.bounds.height
|
||||
self.expandedGridSwipeState = nil
|
||||
|
||||
let velocity = recognizer.velocity(in: self)
|
||||
if abs(velocity.y) > 100.0 || abs(fraction) >= 0.5 {
|
||||
component.updateMainParticipant(nil)
|
||||
} else {
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if !self.ignoreScrolling {
|
||||
self.updateScrolling(transition: .immediate)
|
||||
@ -620,6 +747,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
|
||||
@ -636,28 +770,30 @@ final class VideoChatParticipantsComponent: Component {
|
||||
var expandedGridItemContainerFrame: CGRect
|
||||
if component.expandedVideoState != nil {
|
||||
expandedGridItemContainerFrame = itemLayout.expandedGrid.itemContainerFrame()
|
||||
if let expandedGridSwipeState = self.expandedGridSwipeState {
|
||||
expandedGridItemContainerFrame.origin.y += expandedGridSwipeState.fraction * itemLayout.containerSize.height
|
||||
}
|
||||
} 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 +806,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 +842,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 +875,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 +907,17 @@ 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,
|
||||
interfaceOrientation: component.interfaceOrientation,
|
||||
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 +928,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 +941,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 +981,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -876,7 +1045,7 @@ final class VideoChatParticipantsComponent: Component {
|
||||
}
|
||||
|
||||
let rightAccessoryComponent: AnyComponent<Empty> = AnyComponent(VideoChatParticipantStatusComponent(
|
||||
isMuted: participant.muteState != nil,
|
||||
muteState: participant.muteState,
|
||||
isSpeaking: component.speakingParticipants.contains(participant.peer.id),
|
||||
theme: component.theme
|
||||
))
|
||||
@ -903,12 +1072,21 @@ final class VideoChatParticipantsComponent: Component {
|
||||
rightAccessoryComponent: rightAccessoryComponent,
|
||||
selectionState: .none,
|
||||
hasNext: false,
|
||||
action: { [weak self] peer, _, _ in
|
||||
guard let self else {
|
||||
extractedTheme: PeerListItemComponent.ExtractedTheme(
|
||||
inset: 2.0,
|
||||
background: UIColor(white: 0.1, alpha: 1.0)
|
||||
),
|
||||
action: { [weak self] peer, _, itemView in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
let _ = self
|
||||
let _ = peer
|
||||
component.openParticipantContextMenu(peer.id, itemView.extractedContainerView, nil)
|
||||
},
|
||||
contextAction: { [weak self] peer, sourceView, gesture in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.openParticipantContextMenu(peer.id, sourceView, gesture)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
@ -1017,6 +1195,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 +1236,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 +1251,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 +1299,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 +1310,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 +1331,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 +1352,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 {
|
||||
@ -1180,28 +1369,7 @@ final class VideoChatParticipantsComponent: Component {
|
||||
}
|
||||
|
||||
self.component = component
|
||||
|
||||
if !"".isEmpty {
|
||||
let rootVideoLoadingEffectView: VideoChatVideoLoadingEffectView
|
||||
if let current = self.rootVideoLoadingEffectView {
|
||||
rootVideoLoadingEffectView = current
|
||||
} else {
|
||||
rootVideoLoadingEffectView = VideoChatVideoLoadingEffectView(
|
||||
effectAlpha: 0.1,
|
||||
borderAlpha: 0.0,
|
||||
gradientWidth: 260.0,
|
||||
duration: 1.0,
|
||||
hasCustomBorder: false,
|
||||
playOnce: false
|
||||
)
|
||||
self.rootVideoLoadingEffectView = rootVideoLoadingEffectView
|
||||
self.insertSubview(rootVideoLoadingEffectView, at: 0)
|
||||
rootVideoLoadingEffectView.alpha = 0.0
|
||||
rootVideoLoadingEffectView.isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
rootVideoLoadingEffectView.update(size: availableSize, transition: transition)
|
||||
}
|
||||
self.state = state
|
||||
|
||||
let measureListItemSize = self.measureListItemView.update(
|
||||
transition: .immediate,
|
||||
@ -1229,7 +1397,13 @@ final class VideoChatParticipantsComponent: Component {
|
||||
transition: transition,
|
||||
component: AnyComponent(VideoChatListInviteComponent(
|
||||
title: "Invite Members",
|
||||
theme: component.theme
|
||||
theme: component.theme,
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.openInviteMembers()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: 1000.0)
|
||||
@ -1258,7 +1432,7 @@ final class VideoChatParticipantsComponent: Component {
|
||||
gridParticipants.append(videoParticipant)
|
||||
}
|
||||
}
|
||||
if !hasVideo {
|
||||
if !hasVideo || component.layout.videoColumn != nil {
|
||||
if participant.peer.id == component.call.accountContext.account.peerId {
|
||||
listParticipants.insert(participant, at: 0)
|
||||
} else {
|
||||
@ -1272,10 +1446,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 +1464,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)
|
||||
@ -1322,6 +1496,11 @@ final class VideoChatParticipantsComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
if component.layout.videoColumn != nil && gridParticipants.count == 1 {
|
||||
maxVideoQuality = .full
|
||||
maxPresentationQuality = .full
|
||||
}
|
||||
|
||||
if let videoChannel = participant.requestedVideoChannel(minQuality: .thumbnail, maxQuality: maxVideoQuality) {
|
||||
if !requestedVideo.contains(videoChannel) {
|
||||
requestedVideo.append(videoChannel)
|
||||
@ -1375,12 +1554,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)
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -45,6 +45,8 @@ final class VideoChatVideoLoadingEffectView: UIView {
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
self.portalSource.backgroundColor = .red
|
||||
|
||||
self.portalSource.layer.addSublayer(self.hierarchyTrackingLayer)
|
||||
self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in
|
||||
guard let self, self.bounds.width != 0.0 else {
|
||||
|
@ -6,8 +6,6 @@ import SwiftSignalKit
|
||||
import AccountContext
|
||||
import TelegramVoip
|
||||
import AVFoundation
|
||||
import CallScreen
|
||||
import MetalEngine
|
||||
|
||||
protocol VideoRenderingView: UIView {
|
||||
func setOnFirstFrameReceived(_ f: @escaping (Float) -> Void)
|
||||
@ -36,40 +34,30 @@ class VideoRenderingContext {
|
||||
}
|
||||
#endif
|
||||
|
||||
func makeView(input: Signal<OngoingGroupCallContext.VideoFrameData, NoError>, forceSampleBufferDisplayLayer: Bool = false) -> VideoRenderingView? {
|
||||
if !forceSampleBufferDisplayLayer {
|
||||
return CallScreenVideoView(input: input)
|
||||
}
|
||||
|
||||
func makeView(input: Signal<OngoingGroupCallContext.VideoFrameData, NoError>, blur: Bool, forceSampleBufferDisplayLayer: Bool = false) -> VideoRenderingView? {
|
||||
#if targetEnvironment(simulator)
|
||||
if blur {
|
||||
#if DEBUG
|
||||
return SampleBufferVideoRenderingView(input: input)
|
||||
#else
|
||||
return nil
|
||||
#endif
|
||||
}
|
||||
return SampleBufferVideoRenderingView(input: input)
|
||||
#else
|
||||
if #available(iOS 13.0, *), !forceSampleBufferDisplayLayer {
|
||||
return MetalVideoRenderingView(renderingContext: self.metalContext, input: input, blur: false)
|
||||
return MetalVideoRenderingView(renderingContext: self.metalContext, input: input, blur: blur)
|
||||
} else {
|
||||
if blur {
|
||||
return nil
|
||||
}
|
||||
return SampleBufferVideoRenderingView(input: input)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
func makeBlurView(input: Signal<OngoingGroupCallContext.VideoFrameData, NoError>, mainView: VideoRenderingView?, forceSampleBufferDisplayLayer: Bool = false) -> VideoRenderingView? {
|
||||
if let mainView = mainView as? CallScreenVideoView {
|
||||
return CallScreenVideoBlurView(mainView: mainView)
|
||||
}
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
#if DEBUG
|
||||
return SampleBufferVideoRenderingView(input: input)
|
||||
#else
|
||||
return nil
|
||||
#endif
|
||||
#else
|
||||
if #available(iOS 13.0, *), !forceSampleBufferDisplayLayer {
|
||||
return MetalVideoRenderingView(renderingContext: self.metalContext, input: input, blur: true)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
#endif
|
||||
return self.makeView(input: input, blur: true, forceSampleBufferDisplayLayer: forceSampleBufferDisplayLayer)
|
||||
}
|
||||
|
||||
func updateVisibility(isVisible: Bool) {
|
||||
@ -96,194 +84,3 @@ extension PresentationCallVideoView.Orientation {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class CallScreenVideoView: UIView, VideoRenderingView {
|
||||
private var isEnabled: Bool = false
|
||||
|
||||
private var onFirstFrameReceived: ((Float) -> Void)?
|
||||
private var onOrientationUpdated: ((PresentationCallVideoView.Orientation, CGFloat) -> Void)?
|
||||
private var onIsMirroredUpdated: ((Bool) -> Void)?
|
||||
|
||||
private var didReportFirstFrame: Bool = false
|
||||
private var currentIsMirrored: Bool = false
|
||||
private var currentOrientation: PresentationCallVideoView.Orientation = .rotation0
|
||||
private var currentAspect: CGFloat = 1.0
|
||||
|
||||
fileprivate let videoSource: AdaptedCallVideoSource
|
||||
private var disposable: Disposable?
|
||||
|
||||
fileprivate let videoLayer: PrivateCallVideoLayer
|
||||
|
||||
init(input: Signal<OngoingGroupCallContext.VideoFrameData, NoError>) {
|
||||
self.videoLayer = PrivateCallVideoLayer()
|
||||
self.videoLayer.masksToBounds = true
|
||||
|
||||
self.videoSource = AdaptedCallVideoSource(videoStreamSignal: input)
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.layer.addSublayer(self.videoLayer)
|
||||
|
||||
self.disposable = self.videoSource.addOnUpdated { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let videoOutput = self.videoSource.currentOutput
|
||||
self.videoLayer.video = videoOutput
|
||||
|
||||
var notifyOrientationUpdated = false
|
||||
var notifyIsMirroredUpdated = false
|
||||
|
||||
if !self.didReportFirstFrame {
|
||||
notifyOrientationUpdated = true
|
||||
notifyIsMirroredUpdated = true
|
||||
}
|
||||
|
||||
if let currentOutput = videoOutput {
|
||||
let currentAspect: CGFloat
|
||||
if currentOutput.resolution.height > 0.0 {
|
||||
currentAspect = currentOutput.resolution.width / currentOutput.resolution.height
|
||||
} else {
|
||||
currentAspect = 1.0
|
||||
}
|
||||
if self.currentAspect != currentAspect {
|
||||
self.currentAspect = currentAspect
|
||||
notifyOrientationUpdated = true
|
||||
}
|
||||
|
||||
let currentOrientation: PresentationCallVideoView.Orientation
|
||||
if currentOutput.followsDeviceOrientation {
|
||||
currentOrientation = .rotation0
|
||||
} else {
|
||||
if abs(currentOutput.rotationAngle - 0.0) < .ulpOfOne {
|
||||
currentOrientation = .rotation0
|
||||
} else if abs(currentOutput.rotationAngle - Float.pi * 0.5) < .ulpOfOne {
|
||||
currentOrientation = .rotation90
|
||||
} else if abs(currentOutput.rotationAngle - Float.pi) < .ulpOfOne {
|
||||
currentOrientation = .rotation180
|
||||
} else if abs(currentOutput.rotationAngle - Float.pi * 3.0 / 2.0) < .ulpOfOne {
|
||||
currentOrientation = .rotation270
|
||||
} else {
|
||||
currentOrientation = .rotation0
|
||||
}
|
||||
}
|
||||
if self.currentOrientation != currentOrientation {
|
||||
self.currentOrientation = currentOrientation
|
||||
notifyOrientationUpdated = true
|
||||
}
|
||||
|
||||
let currentIsMirrored = !currentOutput.mirrorDirection.isEmpty
|
||||
if self.currentIsMirrored != currentIsMirrored {
|
||||
self.currentIsMirrored = currentIsMirrored
|
||||
notifyIsMirroredUpdated = true
|
||||
}
|
||||
}
|
||||
|
||||
if !self.didReportFirstFrame {
|
||||
self.didReportFirstFrame = true
|
||||
self.onFirstFrameReceived?(Float(self.currentAspect))
|
||||
}
|
||||
|
||||
if notifyOrientationUpdated {
|
||||
self.onOrientationUpdated?(self.currentOrientation, self.currentAspect)
|
||||
}
|
||||
|
||||
if notifyIsMirroredUpdated {
|
||||
self.onIsMirroredUpdated?(self.currentIsMirrored)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable?.dispose()
|
||||
}
|
||||
|
||||
func setOnFirstFrameReceived(_ f: @escaping (Float) -> Void) {
|
||||
self.onFirstFrameReceived = f
|
||||
self.didReportFirstFrame = false
|
||||
}
|
||||
|
||||
func setOnOrientationUpdated(_ f: @escaping (PresentationCallVideoView.Orientation, CGFloat) -> Void) {
|
||||
self.onOrientationUpdated = f
|
||||
}
|
||||
|
||||
func getOrientation() -> PresentationCallVideoView.Orientation {
|
||||
return self.currentOrientation
|
||||
}
|
||||
|
||||
func getAspect() -> CGFloat {
|
||||
return self.currentAspect
|
||||
}
|
||||
|
||||
func setOnIsMirroredUpdated(_ f: @escaping (Bool) -> Void) {
|
||||
self.onIsMirroredUpdated = f
|
||||
}
|
||||
|
||||
func updateIsEnabled(_ isEnabled: Bool) {
|
||||
self.isEnabled = isEnabled
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
if let currentOutput = self.videoSource.currentOutput {
|
||||
let rotatedResolution = currentOutput.resolution
|
||||
let videoSize = size
|
||||
|
||||
let videoResolution = rotatedResolution.aspectFittedOrSmaller(CGSize(width: 1280, height: 1280)).aspectFittedOrSmaller(CGSize(width: videoSize.width * 3.0, height: videoSize.height * 3.0))
|
||||
let rotatedVideoResolution = videoResolution
|
||||
|
||||
transition.updateFrame(layer: self.videoLayer, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.videoLayer.renderSpec = RenderLayerSpec(size: RenderSize(width: Int(rotatedVideoResolution.width), height: Int(rotatedVideoResolution.height)), edgeInset: 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class CallScreenVideoBlurView: UIView, VideoRenderingView {
|
||||
private weak var mainView: CallScreenVideoView?
|
||||
|
||||
private let blurredLayer: MetalEngineSubjectLayer
|
||||
|
||||
init(mainView: CallScreenVideoView) {
|
||||
self.mainView = mainView
|
||||
self.blurredLayer = mainView.videoLayer.blurredLayer
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.layer.addSublayer(self.blurredLayer)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
func setOnFirstFrameReceived(_ f: @escaping (Float) -> Void) {
|
||||
}
|
||||
|
||||
func setOnOrientationUpdated(_ f: @escaping (PresentationCallVideoView.Orientation, CGFloat) -> Void) {
|
||||
}
|
||||
|
||||
func getOrientation() -> PresentationCallVideoView.Orientation {
|
||||
return .rotation0
|
||||
}
|
||||
|
||||
func getAspect() -> CGFloat {
|
||||
return 1.0
|
||||
}
|
||||
|
||||
func setOnIsMirroredUpdated(_ f: @escaping (Bool) -> Void) {
|
||||
}
|
||||
|
||||
func updateIsEnabled(_ isEnabled: Bool) {
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
transition.updateFrame(layer: self.blurredLayer, frame: CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
}
|
||||
|
@ -2418,7 +2418,7 @@ final class VoiceChatControllerImpl: ViewController, VoiceChatController {
|
||||
}
|
||||
} else {
|
||||
if let input = (strongSelf.call as! PresentationGroupCallImpl).video(endpointId: endpointId) {
|
||||
if let videoView = strongSelf.videoRenderingContext.makeView(input: input) {
|
||||
if let videoView = strongSelf.videoRenderingContext.makeView(input: input, blur: false) {
|
||||
completion(GroupVideoNode(videoView: videoView, backdropVideoView: strongSelf.videoRenderingContext.makeBlurView(input: input, mainView: videoView)))
|
||||
}
|
||||
}
|
||||
@ -3738,7 +3738,7 @@ final class VoiceChatControllerImpl: ViewController, VoiceChatController {
|
||||
var isFrontCamera = true
|
||||
let videoCapturer = OngoingCallVideoCapturer()
|
||||
let input = videoCapturer.video()
|
||||
if let videoView = strongSelf.videoRenderingContext.makeView(input: input) {
|
||||
if let videoView = strongSelf.videoRenderingContext.makeView(input: input, blur: false) {
|
||||
videoView.updateIsEnabled(true)
|
||||
|
||||
let cameraNode = GroupVideoNode(videoView: videoView, backdropVideoView: nil)
|
||||
@ -5514,7 +5514,7 @@ final class VoiceChatControllerImpl: ViewController, VoiceChatController {
|
||||
self.requestedVideoSources.insert(channel.endpointId)
|
||||
|
||||
let input = (self.call as! PresentationGroupCallImpl).video(endpointId: channel.endpointId)
|
||||
if let input = input, let videoView = self.videoRenderingContext.makeView(input: input) {
|
||||
if let input = input, let videoView = self.videoRenderingContext.makeView(input: input, blur: false) {
|
||||
let videoNode = GroupVideoNode(videoView: videoView, backdropVideoView: self.videoRenderingContext.makeBlurView(input: input, mainView: videoView))
|
||||
|
||||
self.readyVideoDisposables.set((combineLatest(videoNode.ready, .single(false) |> then(.single(true) |> delay(10.0, queue: Queue.mainQueue())))
|
||||
@ -7097,8 +7097,21 @@ final class VoiceChatContextReferenceContentSource: ContextReferenceContentSourc
|
||||
}
|
||||
}
|
||||
|
||||
private func calculateUseV2(context: AccountContext) -> Bool {
|
||||
var useV2 = true
|
||||
if context.sharedContext.immediateExperimentalUISettings.disableCallV2 {
|
||||
useV2 = false
|
||||
}
|
||||
if let data = context.currentAppConfiguration.with({ $0 }).data, let _ = data["ios_killswitch_disable_videochatui_v2"] {
|
||||
useV2 = false
|
||||
}
|
||||
return useV2
|
||||
}
|
||||
|
||||
public func makeVoiceChatControllerInitialData(sharedContext: SharedAccountContext, accountContext: AccountContext, call: PresentationGroupCall) -> Signal<Any, NoError> {
|
||||
if sharedContext.immediateExperimentalUISettings.callV2 {
|
||||
let useV2 = calculateUseV2(context: accountContext)
|
||||
|
||||
if useV2 {
|
||||
return VideoChatScreenV2Impl.initialData(call: call) |> map { $0 as Any }
|
||||
} else {
|
||||
return .single(Void())
|
||||
@ -7106,7 +7119,9 @@ public func makeVoiceChatControllerInitialData(sharedContext: SharedAccountConte
|
||||
}
|
||||
|
||||
public func makeVoiceChatController(sharedContext: SharedAccountContext, accountContext: AccountContext, call: PresentationGroupCall, initialData: Any) -> VoiceChatController {
|
||||
if sharedContext.immediateExperimentalUISettings.callV2 {
|
||||
let useV2 = calculateUseV2(context: accountContext)
|
||||
|
||||
if useV2 {
|
||||
return VideoChatScreenV2Impl(initialData: initialData as! VideoChatScreenV2Impl.InitialData, call: call)
|
||||
} else {
|
||||
return VoiceChatControllerImpl(sharedContext: sharedContext, accountContext: accountContext, call: call)
|
||||
|
@ -180,6 +180,29 @@ public final class PeerListItemComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
public final class ExtractedTheme: Equatable {
|
||||
public let inset: CGFloat
|
||||
public let background: UIColor
|
||||
|
||||
public init(inset: CGFloat, background: UIColor) {
|
||||
self.inset = inset
|
||||
self.background = background
|
||||
}
|
||||
|
||||
public static func ==(lhs: ExtractedTheme, rhs: ExtractedTheme) -> Bool {
|
||||
if lhs === rhs {
|
||||
return true
|
||||
}
|
||||
if lhs.inset != rhs.inset {
|
||||
return false
|
||||
}
|
||||
if lhs.background != rhs.background {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
@ -202,7 +225,8 @@ public final class PeerListItemComponent: Component {
|
||||
let selectionPosition: SelectionPosition
|
||||
let isEnabled: Bool
|
||||
let hasNext: Bool
|
||||
let action: (EnginePeer, EngineMessage.Id?, UIView?) -> Void
|
||||
let extractedTheme: ExtractedTheme?
|
||||
let action: (EnginePeer, EngineMessage.Id?, PeerListItemComponent.View) -> Void
|
||||
let inlineActions: InlineActionsState?
|
||||
let contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)?
|
||||
let openStories: ((EnginePeer, AvatarNode) -> Void)?
|
||||
@ -230,7 +254,8 @@ public final class PeerListItemComponent: Component {
|
||||
selectionPosition: SelectionPosition = .left,
|
||||
isEnabled: Bool = true,
|
||||
hasNext: Bool,
|
||||
action: @escaping (EnginePeer, EngineMessage.Id?, UIView?) -> Void,
|
||||
extractedTheme: ExtractedTheme? = nil,
|
||||
action: @escaping (EnginePeer, EngineMessage.Id?, PeerListItemComponent.View) -> Void,
|
||||
inlineActions: InlineActionsState? = nil,
|
||||
contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? = nil,
|
||||
openStories: ((EnginePeer, AvatarNode) -> Void)? = nil
|
||||
@ -257,6 +282,7 @@ public final class PeerListItemComponent: Component {
|
||||
self.selectionPosition = selectionPosition
|
||||
self.isEnabled = isEnabled
|
||||
self.hasNext = hasNext
|
||||
self.extractedTheme = extractedTheme
|
||||
self.action = action
|
||||
self.inlineActions = inlineActions
|
||||
self.contextAction = contextAction
|
||||
@ -337,7 +363,7 @@ public final class PeerListItemComponent: Component {
|
||||
}
|
||||
|
||||
public final class View: ContextControllerSourceView, ListSectionComponent.ChildView {
|
||||
private let extractedContainerView: ContextExtractedContentContainingView
|
||||
public let extractedContainerView: ContextExtractedContentContainingView
|
||||
private let containerButton: HighlightTrackingButton
|
||||
|
||||
private let swipeOptionContainer: ListItemSwipeOptionContainer
|
||||
@ -432,8 +458,16 @@ public final class PeerListItemComponent: Component {
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
||||
let extractedBackgroundColor: UIColor
|
||||
if let extractedTheme = component.extractedTheme {
|
||||
extractedBackgroundColor = extractedTheme.background
|
||||
} else {
|
||||
extractedBackgroundColor = component.theme.rootController.navigationBar.blurredBackgroundColor
|
||||
}
|
||||
|
||||
self.containerButton.clipsToBounds = value
|
||||
self.containerButton.backgroundColor = value ? component.theme.rootController.navigationBar.blurredBackgroundColor : nil
|
||||
self.containerButton.backgroundColor = value ? extractedBackgroundColor : nil
|
||||
self.containerButton.layer.cornerRadius = value ? 10.0 : 0.0
|
||||
}
|
||||
self.extractedContainerView.willUpdateIsExtractedToContextPreview = { [weak self] value, transition in
|
||||
@ -500,7 +534,7 @@ public final class PeerListItemComponent: Component {
|
||||
guard let component = self.component, let peer = component.peer else {
|
||||
return
|
||||
}
|
||||
component.action(peer, component.message?.id, self.imageNode?.view)
|
||||
component.action(peer, component.message?.id, self)
|
||||
}
|
||||
|
||||
@objc private func avatarButtonPressed() {
|
||||
@ -620,7 +654,16 @@ public final class PeerListItemComponent: Component {
|
||||
labelData = ("", .neutral)
|
||||
}
|
||||
|
||||
let contextInset: CGFloat = self.isExtractedToContextMenu ? 12.0 : 0.0
|
||||
let contextInset: CGFloat
|
||||
if self.isExtractedToContextMenu {
|
||||
if let extractedTheme = component.extractedTheme {
|
||||
contextInset = extractedTheme.inset
|
||||
} else {
|
||||
contextInset = 12.0
|
||||
}
|
||||
} else {
|
||||
contextInset = 0.0
|
||||
}
|
||||
|
||||
let height: CGFloat
|
||||
let titleFont: UIFont
|
||||
@ -1104,10 +1147,11 @@ public final class PeerListItemComponent: Component {
|
||||
if let rightAccessoryComponentViewImpl = self.rightAccessoryComponentView?.view, let rightAccessoryComponentSize {
|
||||
var rightAccessoryComponentTransition = transition
|
||||
if rightAccessoryComponentViewImpl.superview == nil {
|
||||
rightAccessoryComponentViewImpl.isUserInteractionEnabled = false
|
||||
rightAccessoryComponentTransition = rightAccessoryComponentTransition.withAnimation(.none)
|
||||
self.containerButton.addSubview(rightAccessoryComponentViewImpl)
|
||||
}
|
||||
rightAccessoryComponentTransition.setFrame(view: rightAccessoryComponentViewImpl, frame: CGRect(origin: CGPoint(x: availableSize.width - rightAccessoryComponentSize.width, y: floor((height - verticalInset * 2.0 - rightAccessoryComponentSize.width) / 2.0)), size: rightAccessoryComponentSize))
|
||||
rightAccessoryComponentTransition.setFrame(view: rightAccessoryComponentViewImpl, frame: CGRect(origin: CGPoint(x: availableSize.width - (contextInset * 2.0 + component.sideInset) - rightAccessoryComponentSize.width, y: floor((height - verticalInset * 2.0 - rightAccessoryComponentSize.width) / 2.0)), size: rightAccessoryComponentSize))
|
||||
}
|
||||
|
||||
var reactionIconTransition = transition
|
||||
|
@ -589,7 +589,7 @@ final class StoryItemSetViewListComponent: Component {
|
||||
message: item.message,
|
||||
selectionState: .none,
|
||||
hasNext: index != viewListState.totalCount - 1 || itemLayout.premiumFooterSize != nil,
|
||||
action: { [weak self] peer, messageId, sourceView in
|
||||
action: { [weak self] peer, messageId, itemView in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
@ -598,7 +598,7 @@ final class StoryItemSetViewListComponent: Component {
|
||||
}
|
||||
if let messageId {
|
||||
component.openMessage(peer, messageId)
|
||||
} else if let storyItem, let sourceView {
|
||||
} else if let storyItem, let sourceView = itemView.imageNode?.view {
|
||||
component.openReposts(peer, storyItem.id, sourceView)
|
||||
} else {
|
||||
component.openPeer(peer)
|
||||
|
@ -54,7 +54,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
||||
public var storiesJpegExperiment: Bool
|
||||
public var crashOnMemoryPressure: Bool
|
||||
public var dustEffect: Bool
|
||||
public var callV2: Bool
|
||||
public var disableCallV2: Bool
|
||||
public var experimentalCallMute: Bool
|
||||
public var allowWebViewInspection: Bool
|
||||
public var disableReloginTokens: Bool
|
||||
@ -91,7 +91,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
||||
storiesJpegExperiment: false,
|
||||
crashOnMemoryPressure: false,
|
||||
dustEffect: false,
|
||||
callV2: false,
|
||||
disableCallV2: false,
|
||||
experimentalCallMute: false,
|
||||
allowWebViewInspection: false,
|
||||
disableReloginTokens: false,
|
||||
@ -129,7 +129,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
||||
storiesJpegExperiment: Bool,
|
||||
crashOnMemoryPressure: Bool,
|
||||
dustEffect: Bool,
|
||||
callV2: Bool,
|
||||
disableCallV2: Bool,
|
||||
experimentalCallMute: Bool,
|
||||
allowWebViewInspection: Bool,
|
||||
disableReloginTokens: Bool,
|
||||
@ -164,7 +164,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
||||
self.storiesJpegExperiment = storiesJpegExperiment
|
||||
self.crashOnMemoryPressure = crashOnMemoryPressure
|
||||
self.dustEffect = dustEffect
|
||||
self.callV2 = callV2
|
||||
self.disableCallV2 = disableCallV2
|
||||
self.experimentalCallMute = experimentalCallMute
|
||||
self.allowWebViewInspection = allowWebViewInspection
|
||||
self.disableReloginTokens = disableReloginTokens
|
||||
@ -203,7 +203,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
||||
self.storiesJpegExperiment = try container.decodeIfPresent(Bool.self, forKey: "storiesJpegExperiment") ?? false
|
||||
self.crashOnMemoryPressure = try container.decodeIfPresent(Bool.self, forKey: "crashOnMemoryPressure") ?? false
|
||||
self.dustEffect = try container.decodeIfPresent(Bool.self, forKey: "dustEffect") ?? false
|
||||
self.callV2 = try container.decodeIfPresent(Bool.self, forKey: "callV2") ?? false
|
||||
self.disableCallV2 = try container.decodeIfPresent(Bool.self, forKey: "disableCallV2") ?? false
|
||||
self.experimentalCallMute = try container.decodeIfPresent(Bool.self, forKey: "experimentalCallMute") ?? false
|
||||
self.allowWebViewInspection = try container.decodeIfPresent(Bool.self, forKey: "allowWebViewInspection") ?? false
|
||||
self.disableReloginTokens = try container.decodeIfPresent(Bool.self, forKey: "disableReloginTokens") ?? false
|
||||
@ -242,7 +242,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
||||
try container.encode(self.storiesJpegExperiment, forKey: "storiesJpegExperiment")
|
||||
try container.encode(self.crashOnMemoryPressure, forKey: "crashOnMemoryPressure")
|
||||
try container.encode(self.dustEffect, forKey: "dustEffect")
|
||||
try container.encode(self.callV2, forKey: "callV2")
|
||||
try container.encode(self.disableCallV2, forKey: "disableCallV2")
|
||||
try container.encode(self.experimentalCallMute, forKey: "experimentalCallMute")
|
||||
try container.encode(self.allowWebViewInspection, forKey: "allowWebViewInspection")
|
||||
try container.encode(self.disableReloginTokens, forKey: "disableReloginTokens")
|
||||
|
@ -730,6 +730,7 @@ public class TranslateScreen: ViewController {
|
||||
inputHeight: layout.inputHeight ?? 0.0,
|
||||
metrics: layout.metrics,
|
||||
deviceMetrics: layout.deviceMetrics,
|
||||
orientation: layout.metrics.orientation,
|
||||
isVisible: self.currentIsVisible,
|
||||
theme: self.theme ?? self.presentationData.theme,
|
||||
strings: self.presentationData.strings,
|
||||
|
Loading…
x
Reference in New Issue
Block a user