mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
3a9cacfa68
@ -1370,7 +1370,7 @@ public class BrowserScreen: ViewController, MinimizableController {
|
|||||||
inputHeight: layout.inputHeight ?? 0.0,
|
inputHeight: layout.inputHeight ?? 0.0,
|
||||||
metrics: layout.metrics,
|
metrics: layout.metrics,
|
||||||
deviceMetrics: layout.deviceMetrics,
|
deviceMetrics: layout.deviceMetrics,
|
||||||
orientation: nil,
|
orientation: layout.metrics.orientation,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
theme: self.presentationData.theme,
|
theme: self.presentationData.theme,
|
||||||
strings: self.presentationData.strings,
|
strings: self.presentationData.strings,
|
||||||
|
@ -65,7 +65,7 @@ open class ViewControllerComponentContainer: ViewController {
|
|||||||
inputHeight: CGFloat,
|
inputHeight: CGFloat,
|
||||||
metrics: LayoutMetrics,
|
metrics: LayoutMetrics,
|
||||||
deviceMetrics: DeviceMetrics,
|
deviceMetrics: DeviceMetrics,
|
||||||
orientation: UIInterfaceOrientation? = nil,
|
orientation: UIInterfaceOrientation?,
|
||||||
isVisible: Bool,
|
isVisible: Bool,
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
@ -177,6 +177,7 @@ open class ViewControllerComponentContainer: ViewController {
|
|||||||
inputHeight: layout.inputHeight ?? 0.0,
|
inputHeight: layout.inputHeight ?? 0.0,
|
||||||
metrics: layout.metrics,
|
metrics: layout.metrics,
|
||||||
deviceMetrics: layout.deviceMetrics,
|
deviceMetrics: layout.deviceMetrics,
|
||||||
|
orientation: layout.metrics.orientation,
|
||||||
isVisible: self.currentIsVisible,
|
isVisible: self.currentIsVisible,
|
||||||
theme: self.resolvedTheme,
|
theme: self.resolvedTheme,
|
||||||
strings: self.presentationData.strings,
|
strings: self.presentationData.strings,
|
||||||
|
@ -102,7 +102,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
case playlistPlayback(Bool)
|
case playlistPlayback(Bool)
|
||||||
case enableQuickReactionSwitch(Bool)
|
case enableQuickReactionSwitch(Bool)
|
||||||
case disableReloginTokens(Bool)
|
case disableReloginTokens(Bool)
|
||||||
case callV2(Bool)
|
case disableCallV2(Bool)
|
||||||
case experimentalCallMute(Bool)
|
case experimentalCallMute(Bool)
|
||||||
case liveStreamV2(Bool)
|
case liveStreamV2(Bool)
|
||||||
case preferredVideoCodec(Int, String, String?, Bool)
|
case preferredVideoCodec(Int, String, String?, Bool)
|
||||||
@ -129,7 +129,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return DebugControllerSection.web.rawValue
|
return DebugControllerSection.web.rawValue
|
||||||
case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
|
case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
|
||||||
return DebugControllerSection.experiments.rawValue
|
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
|
return DebugControllerSection.experiments.rawValue
|
||||||
case .logTranslationRecognition, .resetTranslationStates:
|
case .logTranslationRecognition, .resetTranslationStates:
|
||||||
return DebugControllerSection.translation.rawValue
|
return DebugControllerSection.translation.rawValue
|
||||||
@ -242,7 +242,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return 49
|
return 49
|
||||||
case .enableQuickReactionSwitch:
|
case .enableQuickReactionSwitch:
|
||||||
return 50
|
return 50
|
||||||
case .callV2:
|
case .disableCallV2:
|
||||||
return 51
|
return 51
|
||||||
case .experimentalCallMute:
|
case .experimentalCallMute:
|
||||||
return 52
|
return 52
|
||||||
@ -1318,12 +1318,12 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
})
|
})
|
||||||
}).start()
|
}).start()
|
||||||
})
|
})
|
||||||
case let .callV2(value):
|
case let .disableCallV2(value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, title: "[WIP] Video Chat V2", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
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
|
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
|
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
|
||||||
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
|
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
|
||||||
settings.callV2 = value
|
settings.disableCallV2 = value
|
||||||
return PreferencesEntry(settings)
|
return PreferencesEntry(settings)
|
||||||
})
|
})
|
||||||
}).start()
|
}).start()
|
||||||
@ -1502,7 +1502,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
|||||||
}
|
}
|
||||||
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
||||||
entries.append(.enableQuickReactionSwitch(!experimentalSettings.disableQuickReaction))
|
entries.append(.enableQuickReactionSwitch(!experimentalSettings.disableQuickReaction))
|
||||||
entries.append(.callV2(experimentalSettings.callV2))
|
entries.append(.disableCallV2(experimentalSettings.disableCallV2))
|
||||||
entries.append(.experimentalCallMute(experimentalSettings.experimentalCallMute))
|
entries.append(.experimentalCallMute(experimentalSettings.experimentalCallMute))
|
||||||
entries.append(.liveStreamV2(experimentalSettings.liveStreamV2))
|
entries.append(.liveStreamV2(experimentalSettings.liveStreamV2))
|
||||||
}
|
}
|
||||||
|
@ -481,6 +481,7 @@ public class ReplaceBoostScreen: ViewController {
|
|||||||
inputHeight: layout.inputHeight ?? 0.0,
|
inputHeight: layout.inputHeight ?? 0.0,
|
||||||
metrics: layout.metrics,
|
metrics: layout.metrics,
|
||||||
deviceMetrics: layout.deviceMetrics,
|
deviceMetrics: layout.deviceMetrics,
|
||||||
|
orientation: layout.metrics.orientation,
|
||||||
isVisible: self.currentIsVisible,
|
isVisible: self.currentIsVisible,
|
||||||
theme: self.presentationData.theme,
|
theme: self.presentationData.theme,
|
||||||
strings: self.presentationData.strings,
|
strings: self.presentationData.strings,
|
||||||
|
@ -335,7 +335,7 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
stallTimer = _stallTimer
|
stallTimer = _stallTimer
|
||||||
self.clipsToBounds = component.isFullscreen // or just true
|
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.videoView = videoView
|
||||||
self.addSubview(videoView)
|
self.addSubview(videoView)
|
||||||
videoView.alpha = 0
|
videoView.alpha = 0
|
||||||
|
@ -134,10 +134,14 @@ final class VideoChatParticipantThumbnailComponent: Component {
|
|||||||
if transition.animation.isImmediate {
|
if transition.animation.isImmediate {
|
||||||
speakingAlphaTransition = .immediate
|
speakingAlphaTransition = .immediate
|
||||||
} else {
|
} else {
|
||||||
if !wasSpeaking {
|
if let previousComponent, previousComponent.isSelected == component.isSelected {
|
||||||
speakingAlphaTransition = .easeInOut(duration: 0.1)
|
if !wasSpeaking {
|
||||||
|
speakingAlphaTransition = .easeInOut(duration: 0.1)
|
||||||
|
} else {
|
||||||
|
speakingAlphaTransition = .easeInOut(duration: 0.25)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
speakingAlphaTransition = .easeInOut(duration: 0.25)
|
speakingAlphaTransition = .immediate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,8 +172,7 @@ final class VideoChatParticipantThumbnailComponent: Component {
|
|||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(VideoChatMuteIconComponent(
|
component: AnyComponent(VideoChatMuteIconComponent(
|
||||||
color: .white,
|
color: .white,
|
||||||
isFilled: true,
|
content: component.isPresentation ? .screenshare : .mute(isFilled: true, isMuted: component.participant.muteState != nil && !component.isSpeaking)
|
||||||
isMuted: component.participant.muteState != nil
|
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 36.0, height: 36.0)
|
containerSize: CGSize(width: 36.0, height: 36.0)
|
||||||
@ -182,8 +185,6 @@ final class VideoChatParticipantThumbnailComponent: Component {
|
|||||||
transition.setPosition(view: muteStatusView, position: muteStatusFrame.center)
|
transition.setPosition(view: muteStatusView, position: muteStatusFrame.center)
|
||||||
transition.setBounds(view: muteStatusView, bounds: CGRect(origin: CGPoint(), size: muteStatusFrame.size))
|
transition.setBounds(view: muteStatusView, bounds: CGRect(origin: CGPoint(), size: muteStatusFrame.size))
|
||||||
transition.setScale(view: muteStatusView, scale: 0.65)
|
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(
|
let titleSize = self.title.update(
|
||||||
@ -203,8 +204,6 @@ final class VideoChatParticipantThumbnailComponent: Component {
|
|||||||
}
|
}
|
||||||
transition.setPosition(view: titleView, position: titleFrame.origin)
|
transition.setPosition(view: titleView, position: titleFrame.origin)
|
||||||
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
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 {
|
if let videoDescription = component.isPresentation ? component.participant.presentationDescription : component.participant.videoDescription {
|
||||||
@ -289,20 +288,26 @@ final class VideoChatParticipantThumbnailComponent: Component {
|
|||||||
var rotatedVideoResolution = videoResolution
|
var rotatedVideoResolution = videoResolution
|
||||||
var rotatedVideoFrame = videoFrame
|
var rotatedVideoFrame = videoFrame
|
||||||
var rotatedBlurredVideoFrame = blurredVideoFrame
|
var rotatedBlurredVideoFrame = blurredVideoFrame
|
||||||
|
var rotatedVideoBoundsSize = videoFrame.size
|
||||||
|
var rotatedBlurredVideoBoundsSize = blurredVideoFrame.size
|
||||||
|
|
||||||
if videoIsRotated {
|
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)
|
rotatedVideoFrame = rotatedVideoFrame.size.centered(around: rotatedVideoFrame.center)
|
||||||
|
|
||||||
|
rotatedBlurredVideoBoundsSize = CGSize(width: rotatedBlurredVideoBoundsSize.height, height: rotatedBlurredVideoBoundsSize.width)
|
||||||
rotatedBlurredVideoFrame = rotatedBlurredVideoFrame.size.centered(around: rotatedBlurredVideoFrame.center)
|
rotatedBlurredVideoFrame = rotatedBlurredVideoFrame.size.centered(around: rotatedBlurredVideoFrame.center)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rotatedVideoResolution = rotatedVideoResolution.aspectFittedOrSmaller(CGSize(width: rotatedVideoFrame.width * UIScreenScale, height: rotatedVideoFrame.height * UIScreenScale))
|
||||||
|
|
||||||
transition.setPosition(layer: videoLayer, position: rotatedVideoFrame.center)
|
transition.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))
|
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)
|
videoLayer.renderSpec = RenderLayerSpec(size: RenderSize(width: Int(rotatedVideoResolution.width), height: Int(rotatedVideoResolution.height)), edgeInset: 2)
|
||||||
|
|
||||||
transition.setPosition(layer: videoLayer.blurredLayer, position: rotatedBlurredVideoFrame.center)
|
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))
|
transition.setTransform(layer: videoLayer.blurredLayer, transform: CATransform3DMakeRotation(CGFloat(videoSpec.rotationAngle), 0.0, 0.0, 1.0))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -330,11 +335,13 @@ final class VideoChatParticipantThumbnailComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
selectedBorderView = UIImageView()
|
selectedBorderView = UIImageView()
|
||||||
self.selectedBorderView = selectedBorderView
|
self.selectedBorderView = selectedBorderView
|
||||||
|
selectedBorderView.alpha = 0.0
|
||||||
self.addSubview(selectedBorderView)
|
self.addSubview(selectedBorderView)
|
||||||
selectedBorderView.image = View.selectedBorderImage
|
selectedBorderView.image = View.selectedBorderImage
|
||||||
|
|
||||||
selectedBorderView.frame = CGRect(origin: CGPoint(), size: availableSize)
|
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)
|
ComponentTransition.immediate.setTintColor(layer: selectedBorderView.layer, color: component.isSpeaking ? UIColor(rgb: 0x33C758) : component.theme.list.itemAccentColor)
|
||||||
}
|
}
|
||||||
} else if let selectedBorderView = self.selectedBorderView {
|
} else if let selectedBorderView = self.selectedBorderView {
|
||||||
|
@ -9,13 +9,16 @@ import BundleIconComponent
|
|||||||
final class VideoChatListInviteComponent: Component {
|
final class VideoChatListInviteComponent: Component {
|
||||||
let title: String
|
let title: String
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
|
let action: () -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
title: String,
|
title: String,
|
||||||
theme: PresentationTheme
|
theme: PresentationTheme,
|
||||||
|
action: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.title = title
|
self.title = title
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: VideoChatListInviteComponent, rhs: VideoChatListInviteComponent) -> Bool {
|
static func ==(lhs: VideoChatListInviteComponent, rhs: VideoChatListInviteComponent) -> Bool {
|
||||||
@ -28,21 +31,61 @@ final class VideoChatListInviteComponent: Component {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
final class View: UIView {
|
final class View: HighlightTrackingButton {
|
||||||
private let icon = ComponentView<Empty>()
|
private let icon = ComponentView<Empty>()
|
||||||
private let title = ComponentView<Empty>()
|
private let title = ComponentView<Empty>()
|
||||||
|
|
||||||
private var component: VideoChatListInviteComponent?
|
private var component: VideoChatListInviteComponent?
|
||||||
private var isUpdating: Bool = false
|
private var isUpdating: Bool = false
|
||||||
|
|
||||||
|
private var highlightBackgroundLayer: SimpleLayer?
|
||||||
|
private var highlightBackgroundFrame: CGRect?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
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) {
|
required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
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 {
|
func update(component: VideoChatListInviteComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
self.isUpdating = true
|
self.isUpdating = true
|
||||||
defer {
|
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)
|
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 let titleView = self.title.view {
|
||||||
if titleView.superview == nil {
|
if titleView.superview == nil {
|
||||||
|
titleView.isUserInteractionEnabled = false
|
||||||
titleView.layer.anchorPoint = CGPoint()
|
titleView.layer.anchorPoint = CGPoint()
|
||||||
self.addSubview(titleView)
|
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)
|
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 let iconView = self.icon.view {
|
||||||
if iconView.superview == nil {
|
if iconView.superview == nil {
|
||||||
|
iconView.isUserInteractionEnabled = false
|
||||||
self.addSubview(iconView)
|
self.addSubview(iconView)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: iconView, frame: iconFrame)
|
transition.setFrame(view: iconView, frame: iconFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//self.highlightBackgroundFrame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,50 +6,49 @@ import MultilineTextComponent
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import AppBundle
|
import AppBundle
|
||||||
import LottieComponent
|
import LottieComponent
|
||||||
|
import BundleIconComponent
|
||||||
|
|
||||||
final class VideoChatMuteIconComponent: Component {
|
final class VideoChatMuteIconComponent: Component {
|
||||||
|
enum Content: Equatable {
|
||||||
|
case mute(isFilled: Bool, isMuted: Bool)
|
||||||
|
case screenshare
|
||||||
|
}
|
||||||
|
|
||||||
let color: UIColor
|
let color: UIColor
|
||||||
let isFilled: Bool
|
let content: Content
|
||||||
let isMuted: Bool
|
|
||||||
|
|
||||||
init(
|
init(
|
||||||
color: UIColor,
|
color: UIColor,
|
||||||
isFilled: Bool,
|
content: Content
|
||||||
isMuted: Bool
|
|
||||||
) {
|
) {
|
||||||
self.color = color
|
self.color = color
|
||||||
self.isFilled = isFilled
|
self.content = content
|
||||||
self.isMuted = isMuted
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: VideoChatMuteIconComponent, rhs: VideoChatMuteIconComponent) -> Bool {
|
static func ==(lhs: VideoChatMuteIconComponent, rhs: VideoChatMuteIconComponent) -> Bool {
|
||||||
if lhs.color != rhs.color {
|
if lhs.color != rhs.color {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.isFilled != rhs.isFilled {
|
if lhs.content != rhs.content {
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.isMuted != rhs.isMuted {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
final class View: HighlightTrackingButton {
|
final class View: HighlightTrackingButton {
|
||||||
private let icon: VoiceChatMicrophoneNode
|
private var icon: VoiceChatMicrophoneNode?
|
||||||
|
private var scheenshareIcon: ComponentView<Empty>?
|
||||||
|
|
||||||
private var component: VideoChatMuteIconComponent?
|
private var component: VideoChatMuteIconComponent?
|
||||||
private var isUpdating: Bool = false
|
private var isUpdating: Bool = false
|
||||||
|
|
||||||
private var contentImage: UIImage?
|
private var contentImage: UIImage?
|
||||||
|
|
||||||
var iconView: UIView {
|
var iconView: UIView? {
|
||||||
return self.icon.view
|
return self.icon?.view
|
||||||
}
|
}
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.icon = VoiceChatMicrophoneNode()
|
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,14 +64,59 @@ final class VideoChatMuteIconComponent: Component {
|
|||||||
|
|
||||||
self.component = component
|
self.component = component
|
||||||
|
|
||||||
let animationSize = availableSize
|
if case let .mute(isFilled, isMuted) = component.content {
|
||||||
|
let icon: VoiceChatMicrophoneNode
|
||||||
let animationFrame = animationSize.centered(in: CGRect(origin: CGPoint(), size: availableSize))
|
if let current = self.icon {
|
||||||
if self.icon.view.superview == nil {
|
icon = current
|
||||||
self.addSubview(self.icon.view)
|
} 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
|
return availableSize
|
||||||
}
|
}
|
||||||
|
@ -3,24 +3,25 @@ import UIKit
|
|||||||
import Display
|
import Display
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
import TelegramCore
|
||||||
|
|
||||||
final class VideoChatParticipantStatusComponent: Component {
|
final class VideoChatParticipantStatusComponent: Component {
|
||||||
let isMuted: Bool
|
let muteState: GroupCallParticipantsContext.Participant.MuteState?
|
||||||
let isSpeaking: Bool
|
let isSpeaking: Bool
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
|
|
||||||
init(
|
init(
|
||||||
isMuted: Bool,
|
muteState: GroupCallParticipantsContext.Participant.MuteState?,
|
||||||
isSpeaking: Bool,
|
isSpeaking: Bool,
|
||||||
theme: PresentationTheme
|
theme: PresentationTheme
|
||||||
) {
|
) {
|
||||||
self.isMuted = isMuted
|
self.muteState = muteState
|
||||||
self.isSpeaking = isSpeaking
|
self.isSpeaking = isSpeaking
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: VideoChatParticipantStatusComponent, rhs: VideoChatParticipantStatusComponent) -> Bool {
|
static func ==(lhs: VideoChatParticipantStatusComponent, rhs: VideoChatParticipantStatusComponent) -> Bool {
|
||||||
if lhs.isMuted != rhs.isMuted {
|
if lhs.muteState != rhs.muteState {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.isSpeaking != rhs.isSpeaking {
|
if lhs.isSpeaking != rhs.isSpeaking {
|
||||||
@ -61,8 +62,7 @@ final class VideoChatParticipantStatusComponent: Component {
|
|||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(VideoChatMuteIconComponent(
|
component: AnyComponent(VideoChatMuteIconComponent(
|
||||||
color: .white,
|
color: .white,
|
||||||
isFilled: false,
|
content: .mute(isFilled: false, isMuted: component.muteState != nil && !component.isSpeaking)
|
||||||
isMuted: component.isMuted && !component.isSpeaking
|
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 36.0, height: 36.0)
|
containerSize: CGSize(width: 36.0, height: 36.0)
|
||||||
@ -80,7 +80,24 @@ final class VideoChatParticipantStatusComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
tintTransition = .immediate
|
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
|
return size
|
||||||
|
@ -40,7 +40,10 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
let isPresentation: Bool
|
let isPresentation: Bool
|
||||||
let isSpeaking: Bool
|
let isSpeaking: Bool
|
||||||
let isExpanded: Bool
|
let isExpanded: Bool
|
||||||
let bottomInset: CGFloat
|
let isUIHidden: Bool
|
||||||
|
let contentInsets: UIEdgeInsets
|
||||||
|
let controlInsets: UIEdgeInsets
|
||||||
|
let interfaceOrientation: UIInterfaceOrientation
|
||||||
weak var rootVideoLoadingEffectView: VideoChatVideoLoadingEffectView?
|
weak var rootVideoLoadingEffectView: VideoChatVideoLoadingEffectView?
|
||||||
let action: (() -> Void)?
|
let action: (() -> Void)?
|
||||||
|
|
||||||
@ -50,7 +53,10 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
isPresentation: Bool,
|
isPresentation: Bool,
|
||||||
isSpeaking: Bool,
|
isSpeaking: Bool,
|
||||||
isExpanded: Bool,
|
isExpanded: Bool,
|
||||||
bottomInset: CGFloat,
|
isUIHidden: Bool,
|
||||||
|
contentInsets: UIEdgeInsets,
|
||||||
|
controlInsets: UIEdgeInsets,
|
||||||
|
interfaceOrientation: UIInterfaceOrientation,
|
||||||
rootVideoLoadingEffectView: VideoChatVideoLoadingEffectView?,
|
rootVideoLoadingEffectView: VideoChatVideoLoadingEffectView?,
|
||||||
action: (() -> Void)?
|
action: (() -> Void)?
|
||||||
) {
|
) {
|
||||||
@ -59,7 +65,10 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
self.isPresentation = isPresentation
|
self.isPresentation = isPresentation
|
||||||
self.isSpeaking = isSpeaking
|
self.isSpeaking = isSpeaking
|
||||||
self.isExpanded = isExpanded
|
self.isExpanded = isExpanded
|
||||||
self.bottomInset = bottomInset
|
self.isUIHidden = isUIHidden
|
||||||
|
self.contentInsets = contentInsets
|
||||||
|
self.controlInsets = controlInsets
|
||||||
|
self.interfaceOrientation = interfaceOrientation
|
||||||
self.rootVideoLoadingEffectView = rootVideoLoadingEffectView
|
self.rootVideoLoadingEffectView = rootVideoLoadingEffectView
|
||||||
self.action = action
|
self.action = action
|
||||||
}
|
}
|
||||||
@ -77,7 +86,16 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
if lhs.isExpanded != rhs.isExpanded {
|
if lhs.isExpanded != rhs.isExpanded {
|
||||||
return false
|
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
|
return false
|
||||||
}
|
}
|
||||||
if (lhs.action == nil) != (rhs.action == nil) {
|
if (lhs.action == nil) != (rhs.action == nil) {
|
||||||
@ -89,10 +107,12 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
private struct VideoSpec: Equatable {
|
private struct VideoSpec: Equatable {
|
||||||
var resolution: CGSize
|
var resolution: CGSize
|
||||||
var rotationAngle: Float
|
var rotationAngle: Float
|
||||||
|
var followsDeviceOrientation: Bool
|
||||||
|
|
||||||
init(resolution: CGSize, rotationAngle: Float) {
|
init(resolution: CGSize, rotationAngle: Float, followsDeviceOrientation: Bool) {
|
||||||
self.resolution = resolution
|
self.resolution = resolution
|
||||||
self.rotationAngle = rotationAngle
|
self.rotationAngle = rotationAngle
|
||||||
|
self.followsDeviceOrientation = followsDeviceOrientation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +173,15 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
self.component = component
|
self.component = component
|
||||||
self.componentState = state
|
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 nameColor = component.participant.peer.nameColor ?? .blue
|
||||||
let nameColors = component.call.accountContext.peerNameColors.get(nameColor, dark: true)
|
let nameColors = component.call.accountContext.peerNameColors.get(nameColor, dark: true)
|
||||||
self.backgroundColor = nameColors.main.withMultiplied(hue: 1.0, saturation: 1.0, brightness: 0.4)
|
self.backgroundColor = nameColors.main.withMultiplied(hue: 1.0, saturation: 1.0, brightness: 0.4)
|
||||||
@ -210,25 +239,26 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(VideoChatMuteIconComponent(
|
component: AnyComponent(VideoChatMuteIconComponent(
|
||||||
color: .white,
|
color: .white,
|
||||||
isFilled: true,
|
content: component.isPresentation ? .screenshare : .mute(isFilled: true, isMuted: component.participant.muteState != nil && !component.isSpeaking)
|
||||||
isMuted: component.participant.muteState != nil
|
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 36.0, height: 36.0)
|
containerSize: CGSize(width: 36.0, height: 36.0)
|
||||||
)
|
)
|
||||||
let muteStatusFrame: CGRect
|
let muteStatusFrame: CGRect
|
||||||
if component.isExpanded {
|
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 {
|
} 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 let muteStatusView = self.muteStatus.view {
|
||||||
if muteStatusView.superview == nil {
|
if muteStatusView.superview == nil {
|
||||||
self.addSubview(muteStatusView)
|
self.addSubview(muteStatusView)
|
||||||
|
muteStatusView.alpha = controlsAlpha
|
||||||
}
|
}
|
||||||
transition.setPosition(view: muteStatusView, position: muteStatusFrame.center)
|
transition.setPosition(view: muteStatusView, position: muteStatusFrame.center)
|
||||||
transition.setBounds(view: muteStatusView, bounds: CGRect(origin: CGPoint(), size: muteStatusFrame.size))
|
transition.setBounds(view: muteStatusView, bounds: CGRect(origin: CGPoint(), size: muteStatusFrame.size))
|
||||||
transition.setScale(view: muteStatusView, scale: component.isExpanded ? 1.0 : 0.7)
|
transition.setScale(view: muteStatusView, scale: component.isExpanded ? 1.0 : 0.7)
|
||||||
|
alphaTransition.setAlpha(view: muteStatusView, alpha: controlsAlpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleSize = self.title.update(
|
let titleSize = self.title.update(
|
||||||
@ -241,18 +271,20 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
)
|
)
|
||||||
let titleFrame: CGRect
|
let titleFrame: CGRect
|
||||||
if component.isExpanded {
|
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 {
|
} 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 let titleView = self.title.view {
|
||||||
if titleView.superview == nil {
|
if titleView.superview == nil {
|
||||||
titleView.layer.anchorPoint = CGPoint()
|
titleView.layer.anchorPoint = CGPoint()
|
||||||
self.addSubview(titleView)
|
self.addSubview(titleView)
|
||||||
|
titleView.alpha = controlsAlpha
|
||||||
}
|
}
|
||||||
transition.setPosition(view: titleView, position: titleFrame.origin)
|
transition.setPosition(view: titleView, position: titleFrame.origin)
|
||||||
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||||
transition.setScale(view: titleView, scale: component.isExpanded ? 1.0 : 0.825)
|
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 {
|
if let videoDescription = component.isPresentation ? component.participant.presentationDescription : component.participant.videoDescription {
|
||||||
@ -296,7 +328,7 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
videoLayer.video = videoOutput
|
videoLayer.video = videoOutput
|
||||||
|
|
||||||
if let 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 {
|
if self.videoSpec != videoSpec {
|
||||||
self.videoSpec = videoSpec
|
self.videoSpec = videoSpec
|
||||||
if !self.isUpdating {
|
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 {
|
if let videoSpec = self.videoSpec {
|
||||||
videoBackgroundLayer.isHidden = false
|
videoBackgroundLayer.isHidden = false
|
||||||
|
|
||||||
|
let rotationAngle = resolveCallVideoRotationAngle(angle: videoSpec.rotationAngle, followsDeviceOrientation: videoSpec.followsDeviceOrientation, interfaceOrientation: component.interfaceOrientation)
|
||||||
|
|
||||||
var rotatedResolution = videoSpec.resolution
|
var rotatedResolution = videoSpec.resolution
|
||||||
var videoIsRotated = false
|
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
|
videoIsRotated = true
|
||||||
}
|
}
|
||||||
if videoIsRotated {
|
if videoIsRotated {
|
||||||
@ -397,26 +368,31 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
let blurredVideoSize = rotatedResolution.aspectFilled(availableSize)
|
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 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 rotatedVideoResolution = videoResolution
|
||||||
var rotatedVideoFrame = videoFrame
|
var rotatedVideoFrame = videoFrame
|
||||||
var rotatedBlurredVideoFrame = blurredVideoFrame
|
var rotatedBlurredVideoFrame = blurredVideoFrame
|
||||||
|
var rotatedVideoBoundsSize = videoFrame.size
|
||||||
|
var rotatedBlurredVideoBoundsSize = blurredVideoFrame.size
|
||||||
|
|
||||||
if videoIsRotated {
|
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)
|
rotatedVideoFrame = rotatedVideoFrame.size.centered(around: rotatedVideoFrame.center)
|
||||||
|
|
||||||
|
rotatedBlurredVideoBoundsSize = CGSize(width: rotatedBlurredVideoBoundsSize.height, height: rotatedBlurredVideoBoundsSize.width)
|
||||||
rotatedBlurredVideoFrame = rotatedBlurredVideoFrame.size.centered(around: rotatedBlurredVideoFrame.center)
|
rotatedBlurredVideoFrame = rotatedBlurredVideoFrame.size.centered(around: rotatedBlurredVideoFrame.center)
|
||||||
}
|
}
|
||||||
|
rotatedVideoResolution = rotatedVideoResolution.aspectFittedOrSmaller(CGSize(width: rotatedVideoFrame.width * UIScreenScale, height: rotatedVideoFrame.height * UIScreenScale))
|
||||||
|
|
||||||
transition.setPosition(layer: videoLayer, position: rotatedVideoFrame.center)
|
transition.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))
|
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)
|
videoLayer.renderSpec = RenderLayerSpec(size: RenderSize(width: Int(rotatedVideoResolution.width), height: Int(rotatedVideoResolution.height)), edgeInset: 2)
|
||||||
|
|
||||||
transition.setPosition(layer: videoLayer.blurredLayer, position: rotatedBlurredVideoFrame.center)
|
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))
|
transition.setTransform(layer: videoLayer.blurredLayer, transform: CATransform3DMakeRotation(CGFloat(rotationAngle), 0.0, 0.0, 1.0))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let videoBackgroundLayer = self.videoBackgroundLayer {
|
if let videoBackgroundLayer = self.videoBackgroundLayer {
|
||||||
@ -439,6 +415,7 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
self.loadingEffectView = loadingEffectView
|
self.loadingEffectView = loadingEffectView
|
||||||
self.addSubview(loadingEffectView.view)
|
self.addSubview(loadingEffectView.view)
|
||||||
rootVideoLoadingEffectView.portalSource.addPortal(view: loadingEffectView)
|
rootVideoLoadingEffectView.portalSource.addPortal(view: loadingEffectView)
|
||||||
|
loadingEffectView.view.isUserInteractionEnabled = false
|
||||||
loadingEffectView.view.frame = CGRect(origin: CGPoint(), size: availableSize)
|
loadingEffectView.view.frame = CGRect(origin: CGPoint(), size: availableSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,21 +12,26 @@ import TelegramPresentationData
|
|||||||
import PeerListItemComponent
|
import PeerListItemComponent
|
||||||
|
|
||||||
final class VideoChatParticipantsComponent: Component {
|
final class VideoChatParticipantsComponent: Component {
|
||||||
enum LayoutType: Equatable {
|
struct Layout: Equatable {
|
||||||
struct Horizontal: Equatable {
|
struct Column: Equatable {
|
||||||
var rightColumnWidth: CGFloat
|
var width: CGFloat
|
||||||
var columnSpacing: CGFloat
|
var insets: UIEdgeInsets
|
||||||
var isCentered: Bool
|
|
||||||
|
|
||||||
init(rightColumnWidth: CGFloat, columnSpacing: CGFloat, isCentered: Bool) {
|
init(width: CGFloat, insets: UIEdgeInsets) {
|
||||||
self.rightColumnWidth = rightColumnWidth
|
self.width = width
|
||||||
self.columnSpacing = columnSpacing
|
self.insets = insets
|
||||||
self.isCentered = isCentered
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case vertical
|
var videoColumn: Column?
|
||||||
case horizontal(Horizontal)
|
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 {
|
final class Participants: Equatable {
|
||||||
@ -75,10 +80,12 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
final class ExpandedVideoState: Equatable {
|
final class ExpandedVideoState: Equatable {
|
||||||
let mainParticipant: VideoParticipantKey
|
let mainParticipant: VideoParticipantKey
|
||||||
let isMainParticipantPinned: Bool
|
let isMainParticipantPinned: Bool
|
||||||
|
let isUIHidden: Bool
|
||||||
|
|
||||||
init(mainParticipant: VideoParticipantKey, isMainParticipantPinned: Bool) {
|
init(mainParticipant: VideoParticipantKey, isMainParticipantPinned: Bool, isUIHidden: Bool) {
|
||||||
self.mainParticipant = mainParticipant
|
self.mainParticipant = mainParticipant
|
||||||
self.isMainParticipantPinned = isMainParticipantPinned
|
self.isMainParticipantPinned = isMainParticipantPinned
|
||||||
|
self.isUIHidden = isUIHidden
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: ExpandedVideoState, rhs: ExpandedVideoState) -> Bool {
|
static func ==(lhs: ExpandedVideoState, rhs: ExpandedVideoState) -> Bool {
|
||||||
@ -91,6 +98,9 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
if lhs.isMainParticipantPinned != rhs.isMainParticipantPinned {
|
if lhs.isMainParticipantPinned != rhs.isMainParticipantPinned {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.isUIHidden != rhs.isUIHidden {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,12 +111,15 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
let expandedVideoState: ExpandedVideoState?
|
let expandedVideoState: ExpandedVideoState?
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
let layoutType: LayoutType
|
let layout: Layout
|
||||||
let collapsedContainerInsets: UIEdgeInsets
|
let expandedInsets: UIEdgeInsets
|
||||||
let expandedContainerInsets: UIEdgeInsets
|
let safeInsets: UIEdgeInsets
|
||||||
let sideInset: CGFloat
|
let interfaceOrientation: UIInterfaceOrientation
|
||||||
|
let openParticipantContextMenu: (EnginePeer.Id, ContextExtractedContentContainingView, ContextGesture?) -> Void
|
||||||
let updateMainParticipant: (VideoParticipantKey?) -> Void
|
let updateMainParticipant: (VideoParticipantKey?) -> Void
|
||||||
let updateIsMainParticipantPinned: (Bool) -> Void
|
let updateIsMainParticipantPinned: (Bool) -> Void
|
||||||
|
let updateIsExpandedUIHidden: (Bool) -> Void
|
||||||
|
let openInviteMembers: () -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
call: PresentationGroupCall,
|
call: PresentationGroupCall,
|
||||||
@ -115,12 +128,15 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
expandedVideoState: ExpandedVideoState?,
|
expandedVideoState: ExpandedVideoState?,
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
layoutType: LayoutType,
|
layout: Layout,
|
||||||
collapsedContainerInsets: UIEdgeInsets,
|
expandedInsets: UIEdgeInsets,
|
||||||
expandedContainerInsets: UIEdgeInsets,
|
safeInsets: UIEdgeInsets,
|
||||||
sideInset: CGFloat,
|
interfaceOrientation: UIInterfaceOrientation,
|
||||||
|
openParticipantContextMenu: @escaping (EnginePeer.Id, ContextExtractedContentContainingView, ContextGesture?) -> Void,
|
||||||
updateMainParticipant: @escaping (VideoParticipantKey?) -> Void,
|
updateMainParticipant: @escaping (VideoParticipantKey?) -> Void,
|
||||||
updateIsMainParticipantPinned: @escaping (Bool) -> Void
|
updateIsMainParticipantPinned: @escaping (Bool) -> Void,
|
||||||
|
updateIsExpandedUIHidden: @escaping (Bool) -> Void,
|
||||||
|
openInviteMembers: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.call = call
|
self.call = call
|
||||||
self.participants = participants
|
self.participants = participants
|
||||||
@ -128,12 +144,15 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
self.expandedVideoState = expandedVideoState
|
self.expandedVideoState = expandedVideoState
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.layoutType = layoutType
|
self.layout = layout
|
||||||
self.collapsedContainerInsets = collapsedContainerInsets
|
self.expandedInsets = expandedInsets
|
||||||
self.expandedContainerInsets = expandedContainerInsets
|
self.safeInsets = safeInsets
|
||||||
self.sideInset = sideInset
|
self.interfaceOrientation = interfaceOrientation
|
||||||
|
self.openParticipantContextMenu = openParticipantContextMenu
|
||||||
self.updateMainParticipant = updateMainParticipant
|
self.updateMainParticipant = updateMainParticipant
|
||||||
self.updateIsMainParticipantPinned = updateIsMainParticipantPinned
|
self.updateIsMainParticipantPinned = updateIsMainParticipantPinned
|
||||||
|
self.updateIsExpandedUIHidden = updateIsExpandedUIHidden
|
||||||
|
self.openInviteMembers = openInviteMembers
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: VideoChatParticipantsComponent, rhs: VideoChatParticipantsComponent) -> Bool {
|
static func ==(lhs: VideoChatParticipantsComponent, rhs: VideoChatParticipantsComponent) -> Bool {
|
||||||
@ -152,16 +171,16 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
if lhs.strings !== rhs.strings {
|
if lhs.strings !== rhs.strings {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.layoutType != rhs.layoutType {
|
if lhs.layout != rhs.layout {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.collapsedContainerInsets != rhs.collapsedContainerInsets {
|
if lhs.expandedInsets != rhs.expandedInsets {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.expandedContainerInsets != rhs.expandedContainerInsets {
|
if lhs.safeInsets != rhs.safeInsets {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.sideInset != rhs.sideInset {
|
if lhs.interfaceOrientation != rhs.interfaceOrientation {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -178,40 +197,84 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
let containerSize: CGSize
|
let containerSize: CGSize
|
||||||
let sideInset: CGFloat
|
let sideInset: CGFloat
|
||||||
let itemCount: Int
|
let itemCount: Int
|
||||||
|
let isDedicatedColumn: Bool
|
||||||
let itemSize: CGSize
|
let itemSize: CGSize
|
||||||
let itemSpacing: CGFloat
|
let itemSpacing: CGFloat
|
||||||
let lastItemSize: CGFloat
|
let lastItemSize: CGFloat
|
||||||
|
let lastRowItemCount: Int
|
||||||
|
let lastRowItemSize: CGFloat
|
||||||
let itemsPerRow: Int
|
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.containerSize = containerSize
|
||||||
self.sideInset = sideInset
|
self.sideInset = sideInset
|
||||||
self.itemCount = itemCount
|
self.itemCount = itemCount
|
||||||
|
self.isDedicatedColumn = isDedicatedColumn
|
||||||
|
|
||||||
let width: CGFloat = containerSize.width - sideInset * 2.0
|
let width: CGFloat = containerSize.width - sideInset * 2.0
|
||||||
|
|
||||||
self.itemSpacing = 4.0
|
self.itemSpacing = 4.0
|
||||||
|
|
||||||
let itemsPerRow: Int
|
let itemsPerRow: Int
|
||||||
if itemCount == 1 {
|
if isDedicatedColumn {
|
||||||
itemsPerRow = 1
|
if itemCount <= 2 {
|
||||||
|
itemsPerRow = 1
|
||||||
|
} else {
|
||||||
|
itemsPerRow = 2
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
itemsPerRow = 2
|
if itemCount == 1 {
|
||||||
|
itemsPerRow = 1
|
||||||
|
} else {
|
||||||
|
itemsPerRow = 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.itemsPerRow = Int(itemsPerRow)
|
self.itemsPerRow = Int(itemsPerRow)
|
||||||
|
|
||||||
let itemWidth = floorToScreenPixels((width - (self.itemSpacing * CGFloat(self.itemsPerRow - 1))) / CGFloat(itemsPerRow))
|
let itemWidth = floorToScreenPixels((width - (self.itemSpacing * CGFloat(self.itemsPerRow - 1))) / CGFloat(itemsPerRow))
|
||||||
let itemHeight = min(180.0, itemWidth)
|
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)
|
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 {
|
func frame(at index: Int) -> CGRect {
|
||||||
let row = index / self.itemsPerRow
|
let row = index / self.itemsPerRow
|
||||||
let column = 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
|
return frame
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,21 +300,37 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
|
|
||||||
struct ExpandedGrid {
|
struct ExpandedGrid {
|
||||||
let containerSize: CGSize
|
let containerSize: CGSize
|
||||||
let layoutType: LayoutType
|
let layout: Layout
|
||||||
let containerInsets: UIEdgeInsets
|
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.containerSize = containerSize
|
||||||
self.layoutType = layoutType
|
self.layout = layout
|
||||||
self.containerInsets = containerInsets
|
self.expandedInsets = expandedInsets
|
||||||
|
self.isUIHidden = isUIHidden
|
||||||
}
|
}
|
||||||
|
|
||||||
func itemContainerFrame() -> CGRect {
|
func itemContainerFrame() -> CGRect {
|
||||||
switch self.layoutType {
|
let containerInsets: UIEdgeInsets
|
||||||
case .vertical:
|
if self.isUIHidden {
|
||||||
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))
|
containerInsets = UIEdgeInsets()
|
||||||
case .horizontal:
|
} else {
|
||||||
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 = 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 containerSize: CGSize
|
||||||
let layoutType: LayoutType
|
let layout: Layout
|
||||||
let collapsedContainerInsets: UIEdgeInsets
|
let isUIHidden: Bool
|
||||||
let sideInset: CGFloat
|
let expandedInsets: UIEdgeInsets
|
||||||
|
let safeInsets: UIEdgeInsets
|
||||||
let grid: Grid
|
let grid: Grid
|
||||||
let expandedGrid: ExpandedGrid
|
let expandedGrid: ExpandedGrid
|
||||||
let list: List
|
let list: List
|
||||||
@ -320,32 +400,41 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
let scrollClippingFrame: CGRect
|
let scrollClippingFrame: CGRect
|
||||||
let separateVideoScrollClippingFrame: 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.containerSize = containerSize
|
||||||
self.layoutType = layoutType
|
self.layout = layout
|
||||||
self.collapsedContainerInsets = collapsedContainerInsets
|
self.isUIHidden = isUIHidden
|
||||||
self.sideInset = sideInset
|
self.expandedInsets = expandedInsets
|
||||||
|
self.safeInsets = safeInsets
|
||||||
|
|
||||||
|
let listWidth: CGFloat = layout.mainColumn.width
|
||||||
let gridWidth: CGFloat
|
let gridWidth: CGFloat
|
||||||
let listWidth: CGFloat
|
let gridSideInset: CGFloat
|
||||||
switch layoutType {
|
let gridContainerHeight: CGFloat
|
||||||
case .vertical:
|
if let videoColumn = layout.videoColumn {
|
||||||
listWidth = containerSize.width - sideInset * 2.0
|
gridWidth = videoColumn.width
|
||||||
|
gridSideInset = videoColumn.insets.left
|
||||||
|
gridContainerHeight = containerSize.height - videoColumn.insets.top - videoColumn.insets.bottom
|
||||||
|
} else {
|
||||||
gridWidth = listWidth
|
gridWidth = listWidth
|
||||||
case let .horizontal(horizontal):
|
gridSideInset = layout.mainColumn.insets.left
|
||||||
listWidth = horizontal.rightColumnWidth
|
gridContainerHeight = containerSize.height
|
||||||
gridWidth = max(10.0, containerSize.width - sideInset * 2.0 - horizontal.rightColumnWidth - horizontal.columnSpacing)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.grid = Grid(containerSize: CGSize(width: gridWidth, height: containerSize.height), sideInset: 0.0, itemCount: gridItemCount)
|
self.grid = Grid(containerSize: CGSize(width: gridWidth, height: gridContainerHeight), sideInset: gridSideInset, itemCount: gridItemCount, isDedicatedColumn: layout.videoColumn != nil)
|
||||||
self.expandedGrid = ExpandedGrid(containerSize: containerSize, layoutType: layoutType, containerInsets: expandedContainerInsets)
|
self.list = List(containerSize: CGSize(width: listWidth, height: containerSize.height), sideInset: layout.mainColumn.insets.left, itemCount: listItemCount, itemHeight: listItemHeight, trailingItemHeight: listTrailingItemHeight)
|
||||||
self.list = List(containerSize: CGSize(width: listWidth, height: containerSize.height), sideInset: 0.0, itemCount: listItemCount, itemHeight: listItemHeight, trailingItemHeight: listTrailingItemHeight)
|
|
||||||
self.spacing = 4.0
|
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
|
var listOffsetY: CGFloat = self.gridOffsetY
|
||||||
if case .vertical = layoutType {
|
if layout.videoColumn == nil {
|
||||||
if self.grid.itemCount != 0 {
|
if self.grid.itemCount != 0 {
|
||||||
listOffsetY += self.grid.contentHeight()
|
listOffsetY += self.grid.contentHeight()
|
||||||
listOffsetY += self.spacing
|
listOffsetY += self.spacing
|
||||||
@ -353,55 +442,54 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
}
|
}
|
||||||
self.listOffsetY = listOffsetY
|
self.listOffsetY = listOffsetY
|
||||||
|
|
||||||
switch layoutType {
|
if let videoColumn = layout.videoColumn {
|
||||||
case .vertical:
|
let columnsWidth: CGFloat = videoColumn.width + layout.columnSpacing + layout.mainColumn.width
|
||||||
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))
|
let columnsSideInset: CGFloat = floorToScreenPixels((containerSize.width - columnsWidth) * 0.5)
|
||||||
self.listFrame = CGRect(origin: CGPoint(), size: containerSize)
|
|
||||||
|
|
||||||
self.separateVideoGridFrame = CGRect(origin: CGPoint(x: -containerSize.width, y: 0.0), size: containerSize)
|
var separateVideoGridFrame = CGRect(origin: CGPoint(x: floor((containerSize.width - columnsWidth) * 0.5), 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))
|
|
||||||
case let .horizontal(horizontal):
|
var listFrame = CGRect(origin: CGPoint(x: separateVideoGridFrame.maxX + layout.columnSpacing, y: 0.0), size: CGSize(width: listWidth, height: containerSize.height))
|
||||||
if horizontal.isCentered {
|
if isUIHidden {
|
||||||
self.listFrame = CGRect(origin: CGPoint(x: floor((containerSize.width - horizontal.rightColumnWidth) * 0.5), y: 0.0), size: CGSize(width: horizontal.rightColumnWidth, height: containerSize.height))
|
listFrame.origin.x += columnsSideInset + layout.mainColumn.width
|
||||||
} else {
|
separateVideoGridFrame = CGRect(origin: CGPoint(x: floor((containerSize.width - columnsWidth) * 0.5), y: 0.0), size: CGSize(width: columnsWidth, height: containerSize.height))
|
||||||
self.listFrame = CGRect(origin: CGPoint(x: containerSize.width - self.sideInset - horizontal.rightColumnWidth, y: 0.0), size: CGSize(width: horizontal.rightColumnWidth, 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.separateVideoGridFrame = separateVideoGridFrame
|
||||||
self.separateVideoScrollClippingFrame = CGRect(origin: CGPoint(x: self.separateVideoGridFrame.minX, y: collapsedContainerInsets.top), size: CGSize(width: self.separateVideoGridFrame.width, height: containerSize.height - collapsedContainerInsets.top))
|
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 {
|
func contentHeight() -> CGFloat {
|
||||||
var result: CGFloat = self.gridOffsetY
|
var result: CGFloat = self.gridOffsetY
|
||||||
switch self.layoutType {
|
if self.layout.videoColumn == nil {
|
||||||
case .vertical:
|
|
||||||
if self.grid.itemCount != 0 {
|
if self.grid.itemCount != 0 {
|
||||||
result += self.grid.contentHeight()
|
result += self.grid.contentHeight()
|
||||||
result += self.spacing
|
result += self.spacing
|
||||||
}
|
}
|
||||||
case .horizontal:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
result += self.list.contentHeight()
|
result += self.list.contentHeight()
|
||||||
result += self.collapsedContainerInsets.bottom
|
result += self.layout.mainColumn.insets.bottom
|
||||||
result += 24.0
|
result += 24.0
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func separateVideoGridContentHeight() -> CGFloat {
|
func separateVideoGridContentHeight() -> CGFloat {
|
||||||
var result: CGFloat = self.gridOffsetY
|
var result: CGFloat = self.gridOffsetY
|
||||||
switch self.layoutType {
|
if let videoColumn = self.layout.videoColumn {
|
||||||
case .vertical:
|
|
||||||
break
|
|
||||||
case .horizontal:
|
|
||||||
if self.grid.itemCount != 0 {
|
if self.grid.itemCount != 0 {
|
||||||
result += self.grid.contentHeight()
|
result += self.grid.contentHeight()
|
||||||
}
|
}
|
||||||
|
result += videoColumn.insets.bottom
|
||||||
}
|
}
|
||||||
result += self.collapsedContainerInsets.bottom
|
|
||||||
result += 24.0
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,11 +502,10 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func gridItemContainerFrame() -> CGRect {
|
func gridItemContainerFrame() -> CGRect {
|
||||||
switch self.layoutType {
|
if let _ = self.layout.videoColumn {
|
||||||
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:
|
|
||||||
return CGRect(origin: CGPoint(x: 0.0, y: self.gridOffsetY), size: CGSize(width: self.separateVideoGridFrame.width, height: self.grid.contentHeight()))
|
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 {
|
func listItemContainerFrame() -> CGRect {
|
||||||
switch self.layoutType {
|
if let _ = self.layout.videoColumn {
|
||||||
case .vertical:
|
return CGRect(origin: CGPoint(x: 0.0, y: self.listOffsetY), size: CGSize(width: self.separateVideoGridFrame.width, height: self.list.contentHeight()))
|
||||||
return CGRect(origin: CGPoint(x: self.sideInset, y: self.listOffsetY), size: CGSize(width: self.containerSize.width - self.sideInset * 2.0, height: self.list.contentHeight()))
|
} else {
|
||||||
case .horizontal:
|
return CGRect(origin: CGPoint(x: 0.0, y: self.listOffsetY), size: CGSize(width: self.containerSize.width, height: self.list.contentHeight()))
|
||||||
return CGRect(origin: CGPoint(x: 0.0, y: self.listOffsetY), size: CGSize(width: self.listFrame.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 {
|
private final class VideoParticipant: Equatable {
|
||||||
let participant: GroupCallParticipantsContext.Participant
|
let participant: GroupCallParticipantsContext.Participant
|
||||||
let isPresentation: Bool
|
let isPresentation: Bool
|
||||||
@ -496,6 +590,7 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
private let separateVideoScrollView: ScrollView
|
private let separateVideoScrollView: ScrollView
|
||||||
|
|
||||||
private var component: VideoChatParticipantsComponent?
|
private var component: VideoChatParticipantsComponent?
|
||||||
|
private weak var state: EmptyComponentState?
|
||||||
private var isUpdating: Bool = false
|
private var isUpdating: Bool = false
|
||||||
|
|
||||||
private var ignoreScrolling: Bool = false
|
private var ignoreScrolling: Bool = false
|
||||||
@ -519,6 +614,7 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
private let listItemsBackground = ComponentView<Empty>()
|
private let listItemsBackground = ComponentView<Empty>()
|
||||||
|
|
||||||
private var itemLayout: ItemLayout?
|
private var itemLayout: ItemLayout?
|
||||||
|
private var expandedGridSwipeState: ExpandedGridSwipeState?
|
||||||
|
|
||||||
private var appliedGridIsEmpty: Bool = true
|
private var appliedGridIsEmpty: Bool = true
|
||||||
|
|
||||||
@ -581,6 +677,8 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
|
|
||||||
self.scrollView.addSubview(self.listItemViewContainer)
|
self.scrollView.addSubview(self.listItemViewContainer)
|
||||||
self.addSubview(self.expandedGridItemContainer)
|
self.addSubview(self.expandedGridItemContainer)
|
||||||
|
|
||||||
|
self.expandedGridItemContainer.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.expandedGridPanGesture(_:))))
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
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) {
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
if !self.ignoreScrolling {
|
if !self.ignoreScrolling {
|
||||||
self.updateScrolling(transition: .immediate)
|
self.updateScrolling(transition: .immediate)
|
||||||
@ -620,6 +747,13 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let alphaTransition: ComponentTransition
|
||||||
|
if !transition.animation.isImmediate {
|
||||||
|
alphaTransition = .easeInOut(duration: 0.2)
|
||||||
|
} else {
|
||||||
|
alphaTransition = .immediate
|
||||||
|
}
|
||||||
|
|
||||||
let gridWasEmpty = self.appliedGridIsEmpty
|
let gridWasEmpty = self.appliedGridIsEmpty
|
||||||
let gridIsEmpty = self.gridParticipants.isEmpty
|
let gridIsEmpty = self.gridParticipants.isEmpty
|
||||||
self.appliedGridIsEmpty = gridIsEmpty
|
self.appliedGridIsEmpty = gridIsEmpty
|
||||||
@ -636,28 +770,30 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
var expandedGridItemContainerFrame: CGRect
|
var expandedGridItemContainerFrame: CGRect
|
||||||
if component.expandedVideoState != nil {
|
if component.expandedVideoState != nil {
|
||||||
expandedGridItemContainerFrame = itemLayout.expandedGrid.itemContainerFrame()
|
expandedGridItemContainerFrame = itemLayout.expandedGrid.itemContainerFrame()
|
||||||
|
if let expandedGridSwipeState = self.expandedGridSwipeState {
|
||||||
|
expandedGridItemContainerFrame.origin.y += expandedGridSwipeState.fraction * itemLayout.containerSize.height
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
switch itemLayout.layoutType {
|
if let videoColumn = itemLayout.layout.videoColumn {
|
||||||
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:
|
|
||||||
expandedGridItemContainerFrame = itemLayout.gridItemContainerFrame().offsetBy(dx: itemLayout.separateVideoScrollClippingFrame.minX, dy: 0.0).offsetBy(dx: 0.0, dy: -self.separateVideoScrollView.bounds.minY)
|
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 {
|
if expandedGridItemContainerFrame.origin.y < videoColumn.insets.top {
|
||||||
expandedGridItemContainerFrame.size.height -= component.collapsedContainerInsets.top - expandedGridItemContainerFrame.origin.y
|
expandedGridItemContainerFrame.size.height -= videoColumn.insets.top - expandedGridItemContainerFrame.origin.y
|
||||||
expandedGridItemContainerFrame.origin.y = component.collapsedContainerInsets.top
|
expandedGridItemContainerFrame.origin.y = videoColumn.insets.top
|
||||||
}
|
}
|
||||||
if expandedGridItemContainerFrame.origin.y + expandedGridItemContainerFrame.height > itemLayout.containerSize.height {
|
if expandedGridItemContainerFrame.origin.y + expandedGridItemContainerFrame.height > itemLayout.containerSize.height {
|
||||||
expandedGridItemContainerFrame.size.height -= (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 {
|
if expandedGridItemContainerFrame.size.height < 0.0 {
|
||||||
expandedGridItemContainerFrame.size.height = 0.0
|
expandedGridItemContainerFrame.size.height = 0.0
|
||||||
@ -670,10 +806,9 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
var validGridItemIndices: [Int] = []
|
var validGridItemIndices: [Int] = []
|
||||||
|
|
||||||
let visibleGridItemRange: (minIndex: Int, maxIndex: Int)
|
let visibleGridItemRange: (minIndex: Int, maxIndex: Int)
|
||||||
switch itemLayout.layoutType {
|
if itemLayout.layout.videoColumn == nil {
|
||||||
case .vertical:
|
|
||||||
visibleGridItemRange = itemLayout.visibleGridItemRange(for: self.scrollView.bounds)
|
visibleGridItemRange = itemLayout.visibleGridItemRange(for: self.scrollView.bounds)
|
||||||
case .horizontal:
|
} else {
|
||||||
visibleGridItemRange = itemLayout.visibleGridItemRange(for: self.separateVideoScrollView.bounds)
|
visibleGridItemRange = itemLayout.visibleGridItemRange(for: self.separateVideoScrollView.bounds)
|
||||||
}
|
}
|
||||||
if visibleGridItemRange.maxIndex >= visibleGridItemRange.minIndex {
|
if visibleGridItemRange.maxIndex >= visibleGridItemRange.minIndex {
|
||||||
@ -707,8 +842,14 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var isItemExpanded = false
|
var isItemExpanded = false
|
||||||
if let expandedVideoState = component.expandedVideoState, expandedVideoState.mainParticipant == videoParticipantKey {
|
var isItemUIHidden = false
|
||||||
isItemExpanded = true
|
if let expandedVideoState = component.expandedVideoState {
|
||||||
|
if expandedVideoState.mainParticipant == videoParticipantKey {
|
||||||
|
isItemExpanded = true
|
||||||
|
}
|
||||||
|
if expandedVideoState.isUIHidden {
|
||||||
|
isItemUIHidden = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var suppressItemExpansionCollapseAnimation = false
|
var suppressItemExpansionCollapseAnimation = false
|
||||||
@ -734,14 +875,28 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
itemFrame = itemLayout.gridItemFrame(at: index)
|
itemFrame = itemLayout.gridItemFrame(at: index)
|
||||||
}
|
}
|
||||||
|
|
||||||
var itemBottomInset: CGFloat = isItemExpanded ? 96.0 : 0.0
|
let itemContentInsets: UIEdgeInsets
|
||||||
switch itemLayout.layoutType {
|
if isItemExpanded {
|
||||||
case .vertical:
|
itemContentInsets = itemLayout.expandedGrid.itemContainerInsets()
|
||||||
break
|
} else {
|
||||||
case .horizontal:
|
itemContentInsets = UIEdgeInsets()
|
||||||
if isItemExpanded {
|
}
|
||||||
itemBottomInset += itemLayout.expandedGrid.containerInsets.bottom
|
|
||||||
}
|
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(
|
let _ = itemView.view.update(
|
||||||
@ -752,14 +907,17 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
isPresentation: videoParticipant.isPresentation,
|
isPresentation: videoParticipant.isPresentation,
|
||||||
isSpeaking: component.speakingParticipants.contains(videoParticipant.participant.peer.id),
|
isSpeaking: component.speakingParticipants.contains(videoParticipant.participant.peer.id),
|
||||||
isExpanded: isItemExpanded,
|
isExpanded: isItemExpanded,
|
||||||
bottomInset: itemBottomInset,
|
isUIHidden: isItemUIHidden,
|
||||||
|
contentInsets: itemContentInsets,
|
||||||
|
controlInsets: itemControlInsets,
|
||||||
|
interfaceOrientation: component.interfaceOrientation,
|
||||||
rootVideoLoadingEffectView: self.rootVideoLoadingEffectView,
|
rootVideoLoadingEffectView: self.rootVideoLoadingEffectView,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
guard let self, let component = self.component else {
|
guard let self, let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if component.expandedVideoState?.mainParticipant == videoParticipantKey {
|
if let expandedVideoState = component.expandedVideoState, expandedVideoState.mainParticipant == videoParticipantKey {
|
||||||
component.updateMainParticipant(nil)
|
component.updateIsExpandedUIHidden(!expandedVideoState.isUIHidden)
|
||||||
} else {
|
} else {
|
||||||
component.updateMainParticipant(videoParticipantKey)
|
component.updateMainParticipant(videoParticipantKey)
|
||||||
}
|
}
|
||||||
@ -770,6 +928,8 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
)
|
)
|
||||||
if let itemComponentView = itemView.view.view {
|
if let itemComponentView = itemView.view.view {
|
||||||
if itemComponentView.superview == nil {
|
if itemComponentView.superview == nil {
|
||||||
|
itemComponentView.layer.allowsGroupOpacity = true
|
||||||
|
|
||||||
if isItemExpanded {
|
if isItemExpanded {
|
||||||
if let expandedThumbnailsView = self.expandedThumbnailsView?.view {
|
if let expandedThumbnailsView = self.expandedThumbnailsView?.view {
|
||||||
self.expandedGridItemContainer.insertSubview(itemComponentView, belowSubview: expandedThumbnailsView)
|
self.expandedGridItemContainer.insertSubview(itemComponentView, belowSubview: expandedThumbnailsView)
|
||||||
@ -781,12 +941,13 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
itemComponentView.frame = itemFrame
|
itemComponentView.frame = itemFrame
|
||||||
|
itemComponentView.alpha = itemAlpha
|
||||||
|
|
||||||
if !resultingItemTransition.animation.isImmediate {
|
if !resultingItemTransition.animation.isImmediate {
|
||||||
resultingItemTransition.animateScale(view: itemComponentView, from: 0.001, to: 1.0)
|
resultingItemTransition.animateScale(view: itemComponentView, from: 0.001, to: 1.0)
|
||||||
}
|
}
|
||||||
if !resultingItemTransition.animation.isImmediate {
|
if !resultingItemTransition.animation.isImmediate && itemAlpha != 0.0 {
|
||||||
itemComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
itemComponentView.layer.animateAlpha(from: 0.0, to: itemAlpha, duration: 0.1)
|
||||||
}
|
}
|
||||||
} else if isItemExpanded && itemComponentView.superview != self.expandedGridItemContainer {
|
} else if isItemExpanded && itemComponentView.superview != self.expandedGridItemContainer {
|
||||||
let fromFrame = itemComponentView.convert(itemComponentView.bounds, to: self.expandedGridItemContainer)
|
let fromFrame = itemComponentView.convert(itemComponentView.bounds, to: self.expandedGridItemContainer)
|
||||||
@ -820,6 +981,14 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
if !itemView.isCollapsing {
|
if !itemView.isCollapsing {
|
||||||
resultingItemTransition.setPosition(view: itemComponentView, position: itemFrame.center)
|
resultingItemTransition.setPosition(view: itemComponentView, position: itemFrame.center)
|
||||||
resultingItemTransition.setBounds(view: itemComponentView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
|
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(
|
let rightAccessoryComponent: AnyComponent<Empty> = AnyComponent(VideoChatParticipantStatusComponent(
|
||||||
isMuted: participant.muteState != nil,
|
muteState: participant.muteState,
|
||||||
isSpeaking: component.speakingParticipants.contains(participant.peer.id),
|
isSpeaking: component.speakingParticipants.contains(participant.peer.id),
|
||||||
theme: component.theme
|
theme: component.theme
|
||||||
))
|
))
|
||||||
@ -903,12 +1072,21 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
rightAccessoryComponent: rightAccessoryComponent,
|
rightAccessoryComponent: rightAccessoryComponent,
|
||||||
selectionState: .none,
|
selectionState: .none,
|
||||||
hasNext: false,
|
hasNext: false,
|
||||||
action: { [weak self] peer, _, _ in
|
extractedTheme: PeerListItemComponent.ExtractedTheme(
|
||||||
guard let self else {
|
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
|
return
|
||||||
}
|
}
|
||||||
let _ = self
|
component.openParticipantContextMenu(peer.id, itemView.extractedContainerView, nil)
|
||||||
let _ = peer
|
},
|
||||||
|
contextAction: { [weak self] peer, sourceView, gesture in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
component.openParticipantContextMenu(peer.id, sourceView, gesture)
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
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
|
var expandedThumbnailsTransition = transition
|
||||||
let expandedThumbnailsView: ComponentView<Empty>
|
let expandedThumbnailsView: ComponentView<Empty>
|
||||||
if let current = self.expandedThumbnailsView {
|
if let current = self.expandedThumbnailsView {
|
||||||
@ -1046,16 +1236,11 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
environment: {},
|
environment: {},
|
||||||
containerSize: itemLayout.expandedGrid.itemContainerFrame().size
|
containerSize: itemLayout.expandedGrid.itemContainerFrame().size
|
||||||
)
|
)
|
||||||
var expandedThumbnailsFrame = CGRect(origin: CGPoint(x: 0.0, y: expandedGridItemContainerFrame.height - expandedThumbnailsSize.height), size: expandedThumbnailsSize)
|
let 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
|
|
||||||
}
|
|
||||||
if let expandedThumbnailsComponentView = expandedThumbnailsView.view {
|
if let expandedThumbnailsComponentView = expandedThumbnailsView.view {
|
||||||
if expandedThumbnailsComponentView.superview == nil {
|
if expandedThumbnailsComponentView.superview == nil {
|
||||||
self.expandedGridItemContainer.addSubview(expandedThumbnailsComponentView)
|
self.expandedGridItemContainer.addSubview(expandedThumbnailsComponentView)
|
||||||
|
expandedThumbnailsComponentView.alpha = expandedThumbnailsAlpha
|
||||||
|
|
||||||
let fromReferenceFrame: CGRect
|
let fromReferenceFrame: CGRect
|
||||||
if let index = self.gridParticipants.firstIndex(where: { $0.participant.peer.id == expandedVideoState.mainParticipant.id && $0.isPresentation == expandedVideoState.mainParticipant.isPresentation }) {
|
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)
|
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)
|
expandedThumbnailsComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
transition.setFrame(view: expandedThumbnailsComponentView, frame: expandedThumbnailsFrame)
|
transition.setFrame(view: expandedThumbnailsComponentView, frame: expandedThumbnailsFrame)
|
||||||
|
alphaTransition.setAlpha(view: expandedThumbnailsComponentView, alpha: expandedThumbnailsAlpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
var expandedControlsTransition = transition
|
var expandedControlsTransition = transition
|
||||||
@ -1113,6 +1299,8 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
if expandedControlsComponentView.superview == nil {
|
if expandedControlsComponentView.superview == nil {
|
||||||
self.expandedGridItemContainer.addSubview(expandedControlsComponentView)
|
self.expandedGridItemContainer.addSubview(expandedControlsComponentView)
|
||||||
|
|
||||||
|
expandedControlsComponentView.alpha = expandedControlsAlpha
|
||||||
|
|
||||||
let fromReferenceFrame: CGRect
|
let fromReferenceFrame: CGRect
|
||||||
if let index = self.gridParticipants.firstIndex(where: { $0.participant.peer.id == expandedVideoState.mainParticipant.id && $0.isPresentation == expandedVideoState.mainParticipant.isPresentation }) {
|
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)
|
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)
|
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)
|
expandedControlsComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
transition.setFrame(view: expandedControlsComponentView, frame: expandedControlsFrame)
|
transition.setFrame(view: expandedControlsComponentView, frame: expandedControlsFrame)
|
||||||
|
alphaTransition.setAlpha(view: expandedControlsComponentView, alpha: expandedControlsAlpha)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let expandedThumbnailsView = self.expandedThumbnailsView {
|
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)
|
let targetThumbnailsFrame = CGRect(origin: CGPoint(x: targetItemFrame.minX, y: targetItemFrame.maxY - expandedThumbnailsComponentView.bounds.height), size: expandedThumbnailsComponentView.bounds.size)
|
||||||
transition.setFrame(view: expandedThumbnailsComponentView, frame: targetThumbnailsFrame)
|
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()
|
expandedThumbnailsComponentView?.removeFromSuperview()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -1163,7 +1352,7 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
let targetThumbnailsFrame = CGRect(origin: CGPoint(x: targetItemFrame.minX, y: targetItemFrame.minY), size: expandedControlsComponentView.bounds.size)
|
let targetThumbnailsFrame = CGRect(origin: CGPoint(x: targetItemFrame.minX, y: targetItemFrame.minY), size: expandedControlsComponentView.bounds.size)
|
||||||
transition.setFrame(view: expandedControlsComponentView, frame: targetThumbnailsFrame)
|
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()
|
expandedControlsComponentView?.removeFromSuperview()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -1180,28 +1369,7 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.component = component
|
self.component = component
|
||||||
|
self.state = state
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
let measureListItemSize = self.measureListItemView.update(
|
let measureListItemSize = self.measureListItemView.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
@ -1229,7 +1397,13 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(VideoChatListInviteComponent(
|
component: AnyComponent(VideoChatListInviteComponent(
|
||||||
title: "Invite Members",
|
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: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width, height: 1000.0)
|
containerSize: CGSize(width: availableSize.width, height: 1000.0)
|
||||||
@ -1258,7 +1432,7 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
gridParticipants.append(videoParticipant)
|
gridParticipants.append(videoParticipant)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !hasVideo {
|
if !hasVideo || component.layout.videoColumn != nil {
|
||||||
if participant.peer.id == component.call.accountContext.account.peerId {
|
if participant.peer.id == component.call.accountContext.account.peerId {
|
||||||
listParticipants.insert(participant, at: 0)
|
listParticipants.insert(participant, at: 0)
|
||||||
} else {
|
} else {
|
||||||
@ -1272,10 +1446,10 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
|
|
||||||
let itemLayout = ItemLayout(
|
let itemLayout = ItemLayout(
|
||||||
containerSize: availableSize,
|
containerSize: availableSize,
|
||||||
layoutType: component.layoutType,
|
layout: component.layout,
|
||||||
sideInset: component.sideInset,
|
isUIHidden: component.expandedVideoState?.isUIHidden ?? false,
|
||||||
collapsedContainerInsets: component.collapsedContainerInsets,
|
expandedInsets: component.expandedInsets,
|
||||||
expandedContainerInsets: component.expandedContainerInsets,
|
safeInsets: component.safeInsets,
|
||||||
gridItemCount: gridParticipants.count,
|
gridItemCount: gridParticipants.count,
|
||||||
listItemCount: listParticipants.count,
|
listItemCount: listParticipants.count,
|
||||||
listItemHeight: measureListItemSize.height,
|
listItemHeight: measureListItemSize.height,
|
||||||
@ -1290,9 +1464,9 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
cornerRadius: 10.0
|
cornerRadius: 10.0
|
||||||
)),
|
)),
|
||||||
environment: {},
|
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 let listItemsBackgroundView = self.listItemsBackground.view {
|
||||||
if listItemsBackgroundView.superview == nil {
|
if listItemsBackgroundView.superview == nil {
|
||||||
self.listItemViewContainer.addSubview(listItemsBackgroundView)
|
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 let videoChannel = participant.requestedVideoChannel(minQuality: .thumbnail, maxQuality: maxVideoQuality) {
|
||||||
if !requestedVideo.contains(videoChannel) {
|
if !requestedVideo.contains(videoChannel) {
|
||||||
requestedVideo.append(videoChannel)
|
requestedVideo.append(videoChannel)
|
||||||
@ -1375,12 +1554,11 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
|
|
||||||
self.ignoreScrolling = false
|
self.ignoreScrolling = false
|
||||||
|
|
||||||
switch component.layoutType {
|
if itemLayout.layout.videoColumn == nil {
|
||||||
case .vertical:
|
|
||||||
if self.gridItemViewContainer.superview !== self.scrollView {
|
if self.gridItemViewContainer.superview !== self.scrollView {
|
||||||
self.scrollView.addSubview(self.gridItemViewContainer)
|
self.scrollView.addSubview(self.gridItemViewContainer)
|
||||||
}
|
}
|
||||||
case .horizontal:
|
} else {
|
||||||
if self.gridItemViewContainer.superview !== self.separateVideoScrollView {
|
if self.gridItemViewContainer.superview !== self.separateVideoScrollView {
|
||||||
self.separateVideoScrollView.addSubview(self.gridItemViewContainer)
|
self.separateVideoScrollView.addSubview(self.gridItemViewContainer)
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -45,6 +45,8 @@ final class VideoChatVideoLoadingEffectView: UIView {
|
|||||||
|
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
|
|
||||||
|
self.portalSource.backgroundColor = .red
|
||||||
|
|
||||||
self.portalSource.layer.addSublayer(self.hierarchyTrackingLayer)
|
self.portalSource.layer.addSublayer(self.hierarchyTrackingLayer)
|
||||||
self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in
|
self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in
|
||||||
guard let self, self.bounds.width != 0.0 else {
|
guard let self, self.bounds.width != 0.0 else {
|
||||||
|
@ -6,8 +6,6 @@ import SwiftSignalKit
|
|||||||
import AccountContext
|
import AccountContext
|
||||||
import TelegramVoip
|
import TelegramVoip
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
import CallScreen
|
|
||||||
import MetalEngine
|
|
||||||
|
|
||||||
protocol VideoRenderingView: UIView {
|
protocol VideoRenderingView: UIView {
|
||||||
func setOnFirstFrameReceived(_ f: @escaping (Float) -> Void)
|
func setOnFirstFrameReceived(_ f: @escaping (Float) -> Void)
|
||||||
@ -36,40 +34,30 @@ class VideoRenderingContext {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
func makeView(input: Signal<OngoingGroupCallContext.VideoFrameData, NoError>, forceSampleBufferDisplayLayer: Bool = false) -> VideoRenderingView? {
|
func makeView(input: Signal<OngoingGroupCallContext.VideoFrameData, NoError>, blur: Bool, forceSampleBufferDisplayLayer: Bool = false) -> VideoRenderingView? {
|
||||||
if !forceSampleBufferDisplayLayer {
|
|
||||||
return CallScreenVideoView(input: input)
|
|
||||||
}
|
|
||||||
|
|
||||||
#if targetEnvironment(simulator)
|
#if targetEnvironment(simulator)
|
||||||
|
if blur {
|
||||||
|
#if DEBUG
|
||||||
|
return SampleBufferVideoRenderingView(input: input)
|
||||||
|
#else
|
||||||
|
return nil
|
||||||
|
#endif
|
||||||
|
}
|
||||||
return SampleBufferVideoRenderingView(input: input)
|
return SampleBufferVideoRenderingView(input: input)
|
||||||
#else
|
#else
|
||||||
if #available(iOS 13.0, *), !forceSampleBufferDisplayLayer {
|
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 {
|
} else {
|
||||||
|
if blur {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return SampleBufferVideoRenderingView(input: input)
|
return SampleBufferVideoRenderingView(input: input)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeBlurView(input: Signal<OngoingGroupCallContext.VideoFrameData, NoError>, mainView: VideoRenderingView?, forceSampleBufferDisplayLayer: Bool = false) -> VideoRenderingView? {
|
func makeBlurView(input: Signal<OngoingGroupCallContext.VideoFrameData, NoError>, mainView: VideoRenderingView?, forceSampleBufferDisplayLayer: Bool = false) -> VideoRenderingView? {
|
||||||
if let mainView = mainView as? CallScreenVideoView {
|
return self.makeView(input: input, blur: true, forceSampleBufferDisplayLayer: forceSampleBufferDisplayLayer)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateVisibility(isVisible: Bool) {
|
func updateVisibility(isVisible: Bool) {
|
||||||
@ -96,194 +84,3 @@ extension PresentationCallVideoView.Orientation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class CallScreenVideoView: UIView, VideoRenderingView {
|
|
||||||
private var isEnabled: Bool = false
|
|
||||||
|
|
||||||
private var onFirstFrameReceived: ((Float) -> Void)?
|
|
||||||
private var onOrientationUpdated: ((PresentationCallVideoView.Orientation, CGFloat) -> Void)?
|
|
||||||
private var onIsMirroredUpdated: ((Bool) -> Void)?
|
|
||||||
|
|
||||||
private var didReportFirstFrame: Bool = false
|
|
||||||
private var currentIsMirrored: Bool = false
|
|
||||||
private var currentOrientation: PresentationCallVideoView.Orientation = .rotation0
|
|
||||||
private var currentAspect: CGFloat = 1.0
|
|
||||||
|
|
||||||
fileprivate let videoSource: AdaptedCallVideoSource
|
|
||||||
private var disposable: Disposable?
|
|
||||||
|
|
||||||
fileprivate let videoLayer: PrivateCallVideoLayer
|
|
||||||
|
|
||||||
init(input: Signal<OngoingGroupCallContext.VideoFrameData, NoError>) {
|
|
||||||
self.videoLayer = PrivateCallVideoLayer()
|
|
||||||
self.videoLayer.masksToBounds = true
|
|
||||||
|
|
||||||
self.videoSource = AdaptedCallVideoSource(videoStreamSignal: input)
|
|
||||||
|
|
||||||
super.init(frame: CGRect())
|
|
||||||
|
|
||||||
self.layer.addSublayer(self.videoLayer)
|
|
||||||
|
|
||||||
self.disposable = self.videoSource.addOnUpdated { [weak self] in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let videoOutput = self.videoSource.currentOutput
|
|
||||||
self.videoLayer.video = videoOutput
|
|
||||||
|
|
||||||
var notifyOrientationUpdated = false
|
|
||||||
var notifyIsMirroredUpdated = false
|
|
||||||
|
|
||||||
if !self.didReportFirstFrame {
|
|
||||||
notifyOrientationUpdated = true
|
|
||||||
notifyIsMirroredUpdated = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if let currentOutput = videoOutput {
|
|
||||||
let currentAspect: CGFloat
|
|
||||||
if currentOutput.resolution.height > 0.0 {
|
|
||||||
currentAspect = currentOutput.resolution.width / currentOutput.resolution.height
|
|
||||||
} else {
|
|
||||||
currentAspect = 1.0
|
|
||||||
}
|
|
||||||
if self.currentAspect != currentAspect {
|
|
||||||
self.currentAspect = currentAspect
|
|
||||||
notifyOrientationUpdated = true
|
|
||||||
}
|
|
||||||
|
|
||||||
let currentOrientation: PresentationCallVideoView.Orientation
|
|
||||||
if currentOutput.followsDeviceOrientation {
|
|
||||||
currentOrientation = .rotation0
|
|
||||||
} else {
|
|
||||||
if abs(currentOutput.rotationAngle - 0.0) < .ulpOfOne {
|
|
||||||
currentOrientation = .rotation0
|
|
||||||
} else if abs(currentOutput.rotationAngle - Float.pi * 0.5) < .ulpOfOne {
|
|
||||||
currentOrientation = .rotation90
|
|
||||||
} else if abs(currentOutput.rotationAngle - Float.pi) < .ulpOfOne {
|
|
||||||
currentOrientation = .rotation180
|
|
||||||
} else if abs(currentOutput.rotationAngle - Float.pi * 3.0 / 2.0) < .ulpOfOne {
|
|
||||||
currentOrientation = .rotation270
|
|
||||||
} else {
|
|
||||||
currentOrientation = .rotation0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.currentOrientation != currentOrientation {
|
|
||||||
self.currentOrientation = currentOrientation
|
|
||||||
notifyOrientationUpdated = true
|
|
||||||
}
|
|
||||||
|
|
||||||
let currentIsMirrored = !currentOutput.mirrorDirection.isEmpty
|
|
||||||
if self.currentIsMirrored != currentIsMirrored {
|
|
||||||
self.currentIsMirrored = currentIsMirrored
|
|
||||||
notifyIsMirroredUpdated = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.didReportFirstFrame {
|
|
||||||
self.didReportFirstFrame = true
|
|
||||||
self.onFirstFrameReceived?(Float(self.currentAspect))
|
|
||||||
}
|
|
||||||
|
|
||||||
if notifyOrientationUpdated {
|
|
||||||
self.onOrientationUpdated?(self.currentOrientation, self.currentAspect)
|
|
||||||
}
|
|
||||||
|
|
||||||
if notifyIsMirroredUpdated {
|
|
||||||
self.onIsMirroredUpdated?(self.currentIsMirrored)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
self.disposable?.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
func setOnFirstFrameReceived(_ f: @escaping (Float) -> Void) {
|
|
||||||
self.onFirstFrameReceived = f
|
|
||||||
self.didReportFirstFrame = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func setOnOrientationUpdated(_ f: @escaping (PresentationCallVideoView.Orientation, CGFloat) -> Void) {
|
|
||||||
self.onOrientationUpdated = f
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOrientation() -> PresentationCallVideoView.Orientation {
|
|
||||||
return self.currentOrientation
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAspect() -> CGFloat {
|
|
||||||
return self.currentAspect
|
|
||||||
}
|
|
||||||
|
|
||||||
func setOnIsMirroredUpdated(_ f: @escaping (Bool) -> Void) {
|
|
||||||
self.onIsMirroredUpdated = f
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateIsEnabled(_ isEnabled: Bool) {
|
|
||||||
self.isEnabled = isEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
|
||||||
if let currentOutput = self.videoSource.currentOutput {
|
|
||||||
let rotatedResolution = currentOutput.resolution
|
|
||||||
let videoSize = size
|
|
||||||
|
|
||||||
let videoResolution = rotatedResolution.aspectFittedOrSmaller(CGSize(width: 1280, height: 1280)).aspectFittedOrSmaller(CGSize(width: videoSize.width * 3.0, height: videoSize.height * 3.0))
|
|
||||||
let rotatedVideoResolution = videoResolution
|
|
||||||
|
|
||||||
transition.updateFrame(layer: self.videoLayer, frame: CGRect(origin: CGPoint(), size: size))
|
|
||||||
self.videoLayer.renderSpec = RenderLayerSpec(size: RenderSize(width: Int(rotatedVideoResolution.width), height: Int(rotatedVideoResolution.height)), edgeInset: 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class CallScreenVideoBlurView: UIView, VideoRenderingView {
|
|
||||||
private weak var mainView: CallScreenVideoView?
|
|
||||||
|
|
||||||
private let blurredLayer: MetalEngineSubjectLayer
|
|
||||||
|
|
||||||
init(mainView: CallScreenVideoView) {
|
|
||||||
self.mainView = mainView
|
|
||||||
self.blurredLayer = mainView.videoLayer.blurredLayer
|
|
||||||
|
|
||||||
super.init(frame: CGRect())
|
|
||||||
|
|
||||||
self.layer.addSublayer(self.blurredLayer)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
}
|
|
||||||
|
|
||||||
func setOnFirstFrameReceived(_ f: @escaping (Float) -> Void) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func setOnOrientationUpdated(_ f: @escaping (PresentationCallVideoView.Orientation, CGFloat) -> Void) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOrientation() -> PresentationCallVideoView.Orientation {
|
|
||||||
return .rotation0
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAspect() -> CGFloat {
|
|
||||||
return 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
func setOnIsMirroredUpdated(_ f: @escaping (Bool) -> Void) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateIsEnabled(_ isEnabled: Bool) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
|
||||||
transition.updateFrame(layer: self.blurredLayer, frame: CGRect(origin: CGPoint(), size: size))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -2418,7 +2418,7 @@ final class VoiceChatControllerImpl: ViewController, VoiceChatController {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let input = (strongSelf.call as! PresentationGroupCallImpl).video(endpointId: endpointId) {
|
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)))
|
completion(GroupVideoNode(videoView: videoView, backdropVideoView: strongSelf.videoRenderingContext.makeBlurView(input: input, mainView: videoView)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3738,7 +3738,7 @@ final class VoiceChatControllerImpl: ViewController, VoiceChatController {
|
|||||||
var isFrontCamera = true
|
var isFrontCamera = true
|
||||||
let videoCapturer = OngoingCallVideoCapturer()
|
let videoCapturer = OngoingCallVideoCapturer()
|
||||||
let input = videoCapturer.video()
|
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)
|
videoView.updateIsEnabled(true)
|
||||||
|
|
||||||
let cameraNode = GroupVideoNode(videoView: videoView, backdropVideoView: nil)
|
let cameraNode = GroupVideoNode(videoView: videoView, backdropVideoView: nil)
|
||||||
@ -5514,7 +5514,7 @@ final class VoiceChatControllerImpl: ViewController, VoiceChatController {
|
|||||||
self.requestedVideoSources.insert(channel.endpointId)
|
self.requestedVideoSources.insert(channel.endpointId)
|
||||||
|
|
||||||
let input = (self.call as! PresentationGroupCallImpl).video(endpointId: 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))
|
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())))
|
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> {
|
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 }
|
return VideoChatScreenV2Impl.initialData(call: call) |> map { $0 as Any }
|
||||||
} else {
|
} else {
|
||||||
return .single(Void())
|
return .single(Void())
|
||||||
@ -7106,7 +7119,9 @@ public func makeVoiceChatControllerInitialData(sharedContext: SharedAccountConte
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func makeVoiceChatController(sharedContext: SharedAccountContext, accountContext: AccountContext, call: PresentationGroupCall, initialData: Any) -> VoiceChatController {
|
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)
|
return VideoChatScreenV2Impl(initialData: initialData as! VideoChatScreenV2Impl.InitialData, call: call)
|
||||||
} else {
|
} else {
|
||||||
return VoiceChatControllerImpl(sharedContext: sharedContext, accountContext: accountContext, call: call)
|
return VoiceChatControllerImpl(sharedContext: sharedContext, accountContext: accountContext, call: call)
|
||||||
|
@ -180,6 +180,29 @@ public final class PeerListItemComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class ExtractedTheme: Equatable {
|
||||||
|
public let inset: CGFloat
|
||||||
|
public let background: UIColor
|
||||||
|
|
||||||
|
public init(inset: CGFloat, background: UIColor) {
|
||||||
|
self.inset = inset
|
||||||
|
self.background = background
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: ExtractedTheme, rhs: ExtractedTheme) -> Bool {
|
||||||
|
if lhs === rhs {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if lhs.inset != rhs.inset {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.background != rhs.background {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
@ -202,7 +225,8 @@ public final class PeerListItemComponent: Component {
|
|||||||
let selectionPosition: SelectionPosition
|
let selectionPosition: SelectionPosition
|
||||||
let isEnabled: Bool
|
let isEnabled: Bool
|
||||||
let hasNext: 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 inlineActions: InlineActionsState?
|
||||||
let contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)?
|
let contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)?
|
||||||
let openStories: ((EnginePeer, AvatarNode) -> Void)?
|
let openStories: ((EnginePeer, AvatarNode) -> Void)?
|
||||||
@ -230,7 +254,8 @@ public final class PeerListItemComponent: Component {
|
|||||||
selectionPosition: SelectionPosition = .left,
|
selectionPosition: SelectionPosition = .left,
|
||||||
isEnabled: Bool = true,
|
isEnabled: Bool = true,
|
||||||
hasNext: Bool,
|
hasNext: Bool,
|
||||||
action: @escaping (EnginePeer, EngineMessage.Id?, UIView?) -> Void,
|
extractedTheme: ExtractedTheme? = nil,
|
||||||
|
action: @escaping (EnginePeer, EngineMessage.Id?, PeerListItemComponent.View) -> Void,
|
||||||
inlineActions: InlineActionsState? = nil,
|
inlineActions: InlineActionsState? = nil,
|
||||||
contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? = nil,
|
contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? = nil,
|
||||||
openStories: ((EnginePeer, AvatarNode) -> Void)? = nil
|
openStories: ((EnginePeer, AvatarNode) -> Void)? = nil
|
||||||
@ -257,6 +282,7 @@ public final class PeerListItemComponent: Component {
|
|||||||
self.selectionPosition = selectionPosition
|
self.selectionPosition = selectionPosition
|
||||||
self.isEnabled = isEnabled
|
self.isEnabled = isEnabled
|
||||||
self.hasNext = hasNext
|
self.hasNext = hasNext
|
||||||
|
self.extractedTheme = extractedTheme
|
||||||
self.action = action
|
self.action = action
|
||||||
self.inlineActions = inlineActions
|
self.inlineActions = inlineActions
|
||||||
self.contextAction = contextAction
|
self.contextAction = contextAction
|
||||||
@ -337,7 +363,7 @@ public final class PeerListItemComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class View: ContextControllerSourceView, ListSectionComponent.ChildView {
|
public final class View: ContextControllerSourceView, ListSectionComponent.ChildView {
|
||||||
private let extractedContainerView: ContextExtractedContentContainingView
|
public let extractedContainerView: ContextExtractedContentContainingView
|
||||||
private let containerButton: HighlightTrackingButton
|
private let containerButton: HighlightTrackingButton
|
||||||
|
|
||||||
private let swipeOptionContainer: ListItemSwipeOptionContainer
|
private let swipeOptionContainer: ListItemSwipeOptionContainer
|
||||||
@ -432,8 +458,16 @@ public final class PeerListItemComponent: Component {
|
|||||||
guard let self, let component = self.component else {
|
guard let self, let component = self.component else {
|
||||||
return
|
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.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.containerButton.layer.cornerRadius = value ? 10.0 : 0.0
|
||||||
}
|
}
|
||||||
self.extractedContainerView.willUpdateIsExtractedToContextPreview = { [weak self] value, transition in
|
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 {
|
guard let component = self.component, let peer = component.peer else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
component.action(peer, component.message?.id, self.imageNode?.view)
|
component.action(peer, component.message?.id, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func avatarButtonPressed() {
|
@objc private func avatarButtonPressed() {
|
||||||
@ -620,7 +654,16 @@ public final class PeerListItemComponent: Component {
|
|||||||
labelData = ("", .neutral)
|
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 height: CGFloat
|
||||||
let titleFont: UIFont
|
let titleFont: UIFont
|
||||||
@ -1104,10 +1147,11 @@ public final class PeerListItemComponent: Component {
|
|||||||
if let rightAccessoryComponentViewImpl = self.rightAccessoryComponentView?.view, let rightAccessoryComponentSize {
|
if let rightAccessoryComponentViewImpl = self.rightAccessoryComponentView?.view, let rightAccessoryComponentSize {
|
||||||
var rightAccessoryComponentTransition = transition
|
var rightAccessoryComponentTransition = transition
|
||||||
if rightAccessoryComponentViewImpl.superview == nil {
|
if rightAccessoryComponentViewImpl.superview == nil {
|
||||||
|
rightAccessoryComponentViewImpl.isUserInteractionEnabled = false
|
||||||
rightAccessoryComponentTransition = rightAccessoryComponentTransition.withAnimation(.none)
|
rightAccessoryComponentTransition = rightAccessoryComponentTransition.withAnimation(.none)
|
||||||
self.containerButton.addSubview(rightAccessoryComponentViewImpl)
|
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
|
var reactionIconTransition = transition
|
||||||
|
@ -589,7 +589,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
message: item.message,
|
message: item.message,
|
||||||
selectionState: .none,
|
selectionState: .none,
|
||||||
hasNext: index != viewListState.totalCount - 1 || itemLayout.premiumFooterSize != nil,
|
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 {
|
guard let self, let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -598,7 +598,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
}
|
}
|
||||||
if let messageId {
|
if let messageId {
|
||||||
component.openMessage(peer, 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)
|
component.openReposts(peer, storyItem.id, sourceView)
|
||||||
} else {
|
} else {
|
||||||
component.openPeer(peer)
|
component.openPeer(peer)
|
||||||
|
@ -54,7 +54,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
public var storiesJpegExperiment: Bool
|
public var storiesJpegExperiment: Bool
|
||||||
public var crashOnMemoryPressure: Bool
|
public var crashOnMemoryPressure: Bool
|
||||||
public var dustEffect: Bool
|
public var dustEffect: Bool
|
||||||
public var callV2: Bool
|
public var disableCallV2: Bool
|
||||||
public var experimentalCallMute: Bool
|
public var experimentalCallMute: Bool
|
||||||
public var allowWebViewInspection: Bool
|
public var allowWebViewInspection: Bool
|
||||||
public var disableReloginTokens: Bool
|
public var disableReloginTokens: Bool
|
||||||
@ -91,7 +91,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
storiesJpegExperiment: false,
|
storiesJpegExperiment: false,
|
||||||
crashOnMemoryPressure: false,
|
crashOnMemoryPressure: false,
|
||||||
dustEffect: false,
|
dustEffect: false,
|
||||||
callV2: false,
|
disableCallV2: false,
|
||||||
experimentalCallMute: false,
|
experimentalCallMute: false,
|
||||||
allowWebViewInspection: false,
|
allowWebViewInspection: false,
|
||||||
disableReloginTokens: false,
|
disableReloginTokens: false,
|
||||||
@ -129,7 +129,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
storiesJpegExperiment: Bool,
|
storiesJpegExperiment: Bool,
|
||||||
crashOnMemoryPressure: Bool,
|
crashOnMemoryPressure: Bool,
|
||||||
dustEffect: Bool,
|
dustEffect: Bool,
|
||||||
callV2: Bool,
|
disableCallV2: Bool,
|
||||||
experimentalCallMute: Bool,
|
experimentalCallMute: Bool,
|
||||||
allowWebViewInspection: Bool,
|
allowWebViewInspection: Bool,
|
||||||
disableReloginTokens: Bool,
|
disableReloginTokens: Bool,
|
||||||
@ -164,7 +164,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
self.storiesJpegExperiment = storiesJpegExperiment
|
self.storiesJpegExperiment = storiesJpegExperiment
|
||||||
self.crashOnMemoryPressure = crashOnMemoryPressure
|
self.crashOnMemoryPressure = crashOnMemoryPressure
|
||||||
self.dustEffect = dustEffect
|
self.dustEffect = dustEffect
|
||||||
self.callV2 = callV2
|
self.disableCallV2 = disableCallV2
|
||||||
self.experimentalCallMute = experimentalCallMute
|
self.experimentalCallMute = experimentalCallMute
|
||||||
self.allowWebViewInspection = allowWebViewInspection
|
self.allowWebViewInspection = allowWebViewInspection
|
||||||
self.disableReloginTokens = disableReloginTokens
|
self.disableReloginTokens = disableReloginTokens
|
||||||
@ -203,7 +203,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
self.storiesJpegExperiment = try container.decodeIfPresent(Bool.self, forKey: "storiesJpegExperiment") ?? false
|
self.storiesJpegExperiment = try container.decodeIfPresent(Bool.self, forKey: "storiesJpegExperiment") ?? false
|
||||||
self.crashOnMemoryPressure = try container.decodeIfPresent(Bool.self, forKey: "crashOnMemoryPressure") ?? false
|
self.crashOnMemoryPressure = try container.decodeIfPresent(Bool.self, forKey: "crashOnMemoryPressure") ?? false
|
||||||
self.dustEffect = try container.decodeIfPresent(Bool.self, forKey: "dustEffect") ?? 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.experimentalCallMute = try container.decodeIfPresent(Bool.self, forKey: "experimentalCallMute") ?? false
|
||||||
self.allowWebViewInspection = try container.decodeIfPresent(Bool.self, forKey: "allowWebViewInspection") ?? false
|
self.allowWebViewInspection = try container.decodeIfPresent(Bool.self, forKey: "allowWebViewInspection") ?? false
|
||||||
self.disableReloginTokens = try container.decodeIfPresent(Bool.self, forKey: "disableReloginTokens") ?? 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.storiesJpegExperiment, forKey: "storiesJpegExperiment")
|
||||||
try container.encode(self.crashOnMemoryPressure, forKey: "crashOnMemoryPressure")
|
try container.encode(self.crashOnMemoryPressure, forKey: "crashOnMemoryPressure")
|
||||||
try container.encode(self.dustEffect, forKey: "dustEffect")
|
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.experimentalCallMute, forKey: "experimentalCallMute")
|
||||||
try container.encode(self.allowWebViewInspection, forKey: "allowWebViewInspection")
|
try container.encode(self.allowWebViewInspection, forKey: "allowWebViewInspection")
|
||||||
try container.encode(self.disableReloginTokens, forKey: "disableReloginTokens")
|
try container.encode(self.disableReloginTokens, forKey: "disableReloginTokens")
|
||||||
|
@ -730,6 +730,7 @@ public class TranslateScreen: ViewController {
|
|||||||
inputHeight: layout.inputHeight ?? 0.0,
|
inputHeight: layout.inputHeight ?? 0.0,
|
||||||
metrics: layout.metrics,
|
metrics: layout.metrics,
|
||||||
deviceMetrics: layout.deviceMetrics,
|
deviceMetrics: layout.deviceMetrics,
|
||||||
|
orientation: layout.metrics.orientation,
|
||||||
isVisible: self.currentIsVisible,
|
isVisible: self.currentIsVisible,
|
||||||
theme: self.theme ?? self.presentationData.theme,
|
theme: self.theme ?? self.presentationData.theme,
|
||||||
strings: self.presentationData.strings,
|
strings: self.presentationData.strings,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user