Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2024-09-12 15:21:47 +04:00
commit 3a9cacfa68
19 changed files with 1814 additions and 606 deletions

View File

@ -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,

View File

@ -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,

View File

@ -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))
}

View File

@ -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,

View File

@ -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

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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))
}
}

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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")

View File

@ -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,