mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Video chat UI
This commit is contained in:
parent
ca5b6c0f0b
commit
5aa7784d2c
@ -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))
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -415,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,6 +119,7 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
let updateMainParticipant: (VideoParticipantKey?) -> Void
|
let updateMainParticipant: (VideoParticipantKey?) -> Void
|
||||||
let updateIsMainParticipantPinned: (Bool) -> Void
|
let updateIsMainParticipantPinned: (Bool) -> Void
|
||||||
let updateIsExpandedUIHidden: (Bool) -> Void
|
let updateIsExpandedUIHidden: (Bool) -> Void
|
||||||
|
let openInviteMembers: () -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
call: PresentationGroupCall,
|
call: PresentationGroupCall,
|
||||||
@ -134,7 +135,8 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
openParticipantContextMenu: @escaping (EnginePeer.Id, ContextExtractedContentContainingView, ContextGesture?) -> Void,
|
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
|
updateIsExpandedUIHidden: @escaping (Bool) -> Void,
|
||||||
|
openInviteMembers: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.call = call
|
self.call = call
|
||||||
self.participants = participants
|
self.participants = participants
|
||||||
@ -150,6 +152,7 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
self.updateMainParticipant = updateMainParticipant
|
self.updateMainParticipant = updateMainParticipant
|
||||||
self.updateIsMainParticipantPinned = updateIsMainParticipantPinned
|
self.updateIsMainParticipantPinned = updateIsMainParticipantPinned
|
||||||
self.updateIsExpandedUIHidden = updateIsExpandedUIHidden
|
self.updateIsExpandedUIHidden = updateIsExpandedUIHidden
|
||||||
|
self.openInviteMembers = openInviteMembers
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: VideoChatParticipantsComponent, rhs: VideoChatParticipantsComponent) -> Bool {
|
static func ==(lhs: VideoChatParticipantsComponent, rhs: VideoChatParticipantsComponent) -> Bool {
|
||||||
@ -527,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
|
||||||
@ -579,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
|
||||||
@ -602,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
|
||||||
|
|
||||||
@ -664,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) {
|
||||||
@ -692,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)
|
||||||
@ -726,6 +770,9 @@ 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 {
|
||||||
if let videoColumn = itemLayout.layout.videoColumn {
|
if let videoColumn = itemLayout.layout.videoColumn {
|
||||||
expandedGridItemContainerFrame = itemLayout.gridItemContainerFrame().offsetBy(dx: itemLayout.separateVideoScrollClippingFrame.minX, dy: 0.0).offsetBy(dx: 0.0, dy: -self.separateVideoScrollView.bounds.minY)
|
expandedGridItemContainerFrame = itemLayout.gridItemContainerFrame().offsetBy(dx: itemLayout.separateVideoScrollClippingFrame.minX, dy: 0.0).offsetBy(dx: 0.0, dy: -self.separateVideoScrollView.bounds.minY)
|
||||||
@ -1322,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,
|
||||||
@ -1371,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)
|
||||||
@ -1464,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)
|
||||||
|
@ -22,6 +22,8 @@ import ShareController
|
|||||||
import AvatarNode
|
import AvatarNode
|
||||||
import TelegramAudio
|
import TelegramAudio
|
||||||
|
|
||||||
|
import PeerInfoUI
|
||||||
|
|
||||||
import DeleteChatPeerActionSheetItem
|
import DeleteChatPeerActionSheetItem
|
||||||
import PeerListItemComponent
|
import PeerListItemComponent
|
||||||
import LegacyComponents
|
import LegacyComponents
|
||||||
@ -105,6 +107,7 @@ private final class VideoChatScreenComponent: Component {
|
|||||||
|
|
||||||
private var expandedParticipantsVideoState: VideoChatParticipantsComponent.ExpandedVideoState?
|
private var expandedParticipantsVideoState: VideoChatParticipantsComponent.ExpandedVideoState?
|
||||||
|
|
||||||
|
private let inviteDisposable = MetaDisposable()
|
||||||
private let currentAvatarMixin = Atomic<TGMediaAvatarMenuMixin?>(value: nil)
|
private let currentAvatarMixin = Atomic<TGMediaAvatarMenuMixin?>(value: nil)
|
||||||
private let updateAvatarDisposable = MetaDisposable()
|
private let updateAvatarDisposable = MetaDisposable()
|
||||||
private var currentUpdatingAvatar: (TelegramMediaImageRepresentation, Float)?
|
private var currentUpdatingAvatar: (TelegramMediaImageRepresentation, Float)?
|
||||||
@ -138,6 +141,7 @@ private final class VideoChatScreenComponent: Component {
|
|||||||
self.audioOutputStateDisposable?.dispose()
|
self.audioOutputStateDisposable?.dispose()
|
||||||
self.inviteLinksDisposable?.dispose()
|
self.inviteLinksDisposable?.dispose()
|
||||||
self.updateAvatarDisposable.dispose()
|
self.updateAvatarDisposable.dispose()
|
||||||
|
self.inviteDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateIn() {
|
func animateIn() {
|
||||||
@ -1423,6 +1427,305 @@ private final class VideoChatScreenComponent: Component {
|
|||||||
environment.controller()?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: action), in: .current)
|
environment.controller()?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: action), in: .current)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func openInviteMembers() {
|
||||||
|
guard let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let groupPeer = component.call.accountContext.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: component.call.peerId))
|
||||||
|
let _ = (groupPeer
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] groupPeer in
|
||||||
|
guard let self, let component = self.component, let environment = self.environment, let groupPeer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let inviteLinks = self.inviteLinks
|
||||||
|
|
||||||
|
if case let .channel(groupPeer) = groupPeer {
|
||||||
|
var canInviteMembers = true
|
||||||
|
if case .broadcast = groupPeer.info, !(groupPeer.addressName?.isEmpty ?? true) {
|
||||||
|
canInviteMembers = false
|
||||||
|
}
|
||||||
|
if !canInviteMembers {
|
||||||
|
if let inviteLinks {
|
||||||
|
self.presentShare(inviteLinks)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var filters: [ChannelMembersSearchFilter] = []
|
||||||
|
if let members = self.members {
|
||||||
|
filters.append(.disable(Array(members.participants.map { $0.peer.id })))
|
||||||
|
}
|
||||||
|
if case let .channel(groupPeer) = groupPeer {
|
||||||
|
if !groupPeer.hasPermission(.inviteMembers) && inviteLinks?.listenerLink == nil {
|
||||||
|
filters.append(.excludeNonMembers)
|
||||||
|
}
|
||||||
|
} else if case let .legacyGroup(groupPeer) = groupPeer {
|
||||||
|
if groupPeer.hasBannedPermission(.banAddMembers) {
|
||||||
|
filters.append(.excludeNonMembers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filters.append(.excludeBots)
|
||||||
|
|
||||||
|
var dismissController: (() -> Void)?
|
||||||
|
let controller = ChannelMembersSearchController(context: component.call.accountContext, peerId: groupPeer.id, forceTheme: environment.theme, mode: .inviteToCall, filters: filters, openPeer: { [weak self] peer, participant in
|
||||||
|
guard let self, let component = self.component, let environment = self.environment else {
|
||||||
|
dismissController?()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let callState = self.callState else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let presentationData = component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
|
||||||
|
if peer.id == callState.myPeerId {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let participant {
|
||||||
|
dismissController?()
|
||||||
|
|
||||||
|
if component.call.invitePeer(participant.peer.id) {
|
||||||
|
let text: String
|
||||||
|
if case let .channel(channel) = self.peer, case .broadcast = channel.info {
|
||||||
|
text = environment.strings.LiveStream_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string
|
||||||
|
} else {
|
||||||
|
text = environment.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string
|
||||||
|
}
|
||||||
|
self.presentUndoOverlay(content: .invitedToVoiceChat(context: component.call.accountContext, peer: EnginePeer(participant.peer), title: nil, text: text, action: nil, duration: 3), action: { _ in return false })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if case let .channel(groupPeer) = groupPeer, let listenerLink = inviteLinks?.listenerLink, !groupPeer.hasPermission(.inviteMembers) {
|
||||||
|
let text = environment.strings.VoiceChat_SendPublicLinkText(peer.displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), EnginePeer(groupPeer).displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string
|
||||||
|
|
||||||
|
environment.controller()?.present(textAlertController(context: component.call.accountContext, forceTheme: environment.theme, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: environment.strings.VoiceChat_SendPublicLinkSend, action: { [weak self] in
|
||||||
|
dismissController?()
|
||||||
|
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = (enqueueMessages(account: component.call.accountContext.account, peerId: peer.id, messages: [.message(text: listenerLink, attributes: [], inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])])
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||||
|
guard let self, let environment = self.environment else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.presentUndoOverlay(content: .forward(savedMessages: false, text: environment.strings.UserInfo_LinkForwardTooltip_Chat_One(peer.displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string), action: { _ in return true })
|
||||||
|
})
|
||||||
|
})]), in: .window(.root))
|
||||||
|
} else {
|
||||||
|
let text: String
|
||||||
|
if case let .channel(groupPeer) = groupPeer, case .broadcast = groupPeer.info {
|
||||||
|
text = environment.strings.VoiceChat_InviteMemberToChannelFirstText(peer.displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), EnginePeer(groupPeer).displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string
|
||||||
|
} else {
|
||||||
|
text = environment.strings.VoiceChat_InviteMemberToGroupFirstText(peer.displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), groupPeer.displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string
|
||||||
|
}
|
||||||
|
|
||||||
|
environment.controller()?.present(textAlertController(context: component.call.accountContext, forceTheme: environment.theme, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: environment.strings.VoiceChat_InviteMemberToGroupFirstAdd, action: { [weak self] in
|
||||||
|
guard let self, let component = self.component, let environment = self.environment else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if case let .channel(groupPeer) = groupPeer {
|
||||||
|
guard let selfController = environment.controller() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let inviteDisposable = self.inviteDisposable
|
||||||
|
var inviteSignal = component.call.accountContext.peerChannelMemberCategoriesContextsManager.addMembers(engine: component.call.accountContext.engine, peerId: groupPeer.id, memberIds: [peer.id])
|
||||||
|
var cancelImpl: (() -> Void)?
|
||||||
|
let progressSignal = Signal<Never, NoError> { [weak selfController] subscriber in
|
||||||
|
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
||||||
|
cancelImpl?()
|
||||||
|
}))
|
||||||
|
selfController?.present(controller, in: .window(.root))
|
||||||
|
return ActionDisposable { [weak controller] in
|
||||||
|
Queue.mainQueue().async() {
|
||||||
|
controller?.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> runOn(Queue.mainQueue())
|
||||||
|
|> delay(0.15, queue: Queue.mainQueue())
|
||||||
|
let progressDisposable = progressSignal.start()
|
||||||
|
|
||||||
|
inviteSignal = inviteSignal
|
||||||
|
|> afterDisposed {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
progressDisposable.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cancelImpl = {
|
||||||
|
inviteDisposable.set(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
inviteDisposable.set((inviteSignal |> deliverOnMainQueue).start(error: { [weak self] error in
|
||||||
|
dismissController?()
|
||||||
|
guard let self, let component = self.component, let environment = self.environment else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let text: String
|
||||||
|
switch error {
|
||||||
|
case .limitExceeded:
|
||||||
|
text = environment.strings.Channel_ErrorAddTooMuch
|
||||||
|
case .tooMuchJoined:
|
||||||
|
text = environment.strings.Invite_ChannelsTooMuch
|
||||||
|
case .generic:
|
||||||
|
text = environment.strings.Login_UnknownError
|
||||||
|
case .restricted:
|
||||||
|
text = environment.strings.Channel_ErrorAddBlocked
|
||||||
|
case .notMutualContact:
|
||||||
|
if case .broadcast = groupPeer.info {
|
||||||
|
text = environment.strings.Channel_AddUserLeftError
|
||||||
|
} else {
|
||||||
|
text = environment.strings.GroupInfo_AddUserLeftError
|
||||||
|
}
|
||||||
|
case .botDoesntSupportGroups:
|
||||||
|
text = environment.strings.Channel_BotDoesntSupportGroups
|
||||||
|
case .tooMuchBots:
|
||||||
|
text = environment.strings.Channel_TooMuchBots
|
||||||
|
case .bot:
|
||||||
|
text = environment.strings.Login_UnknownError
|
||||||
|
case .kicked:
|
||||||
|
text = environment.strings.Channel_AddUserKickedError
|
||||||
|
}
|
||||||
|
environment.controller()?.present(textAlertController(context: component.call.accountContext, forceTheme: environment.theme, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: environment.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
|
}, completed: { [weak self] in
|
||||||
|
guard let self, let component = self.component, let environment = self.environment else {
|
||||||
|
dismissController?()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dismissController?()
|
||||||
|
|
||||||
|
if component.call.invitePeer(peer.id) {
|
||||||
|
let text: String
|
||||||
|
if case let .channel(channel) = self.peer, case .broadcast = channel.info {
|
||||||
|
text = environment.strings.LiveStream_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||||
|
} else {
|
||||||
|
text = environment.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||||
|
}
|
||||||
|
self.presentUndoOverlay(content: .invitedToVoiceChat(context: component.call.accountContext, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
} else if case let .legacyGroup(groupPeer) = groupPeer {
|
||||||
|
guard let selfController = environment.controller() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let inviteDisposable = self.inviteDisposable
|
||||||
|
var inviteSignal = component.call.accountContext.engine.peers.addGroupMember(peerId: groupPeer.id, memberId: peer.id)
|
||||||
|
var cancelImpl: (() -> Void)?
|
||||||
|
let progressSignal = Signal<Never, NoError> { [weak selfController] subscriber in
|
||||||
|
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
||||||
|
cancelImpl?()
|
||||||
|
}))
|
||||||
|
selfController?.present(controller, in: .window(.root))
|
||||||
|
return ActionDisposable { [weak controller] in
|
||||||
|
Queue.mainQueue().async() {
|
||||||
|
controller?.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> runOn(Queue.mainQueue())
|
||||||
|
|> delay(0.15, queue: Queue.mainQueue())
|
||||||
|
let progressDisposable = progressSignal.start()
|
||||||
|
|
||||||
|
inviteSignal = inviteSignal
|
||||||
|
|> afterDisposed {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
progressDisposable.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cancelImpl = {
|
||||||
|
inviteDisposable.set(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
inviteDisposable.set((inviteSignal |> deliverOnMainQueue).start(error: { [weak self] error in
|
||||||
|
dismissController?()
|
||||||
|
guard let self, let component = self.component, let environment = self.environment else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let context = component.call.accountContext
|
||||||
|
|
||||||
|
switch error {
|
||||||
|
case .privacy:
|
||||||
|
let _ = (component.call.accountContext.account.postbox.loadedPeerWithId(peer.id)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||||
|
guard let self, let component = self.component, let environment = self.environment else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
environment.controller()?.present(textAlertController(context: component.call.accountContext, title: nil, text: environment.strings.Privacy_GroupsAndChannels_InviteToGroupError(EnginePeer(peer).compactDisplayTitle, EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
|
})
|
||||||
|
case .notMutualContact:
|
||||||
|
environment.controller()?.present(textAlertController(context: context, title: nil, text: environment.strings.GroupInfo_AddUserLeftError, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
|
case .tooManyChannels:
|
||||||
|
environment.controller()?.present(textAlertController(context: context, title: nil, text: environment.strings.Invite_ChannelsTooMuch, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
|
case .groupFull, .generic:
|
||||||
|
environment.controller()?.present(textAlertController(context: context, forceTheme: environment.theme, title: nil, text: environment.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: environment.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
|
}
|
||||||
|
}, completed: { [weak self] in
|
||||||
|
guard let self, let component = self.component, let environment = self.environment else {
|
||||||
|
dismissController?()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dismissController?()
|
||||||
|
|
||||||
|
if component.call.invitePeer(peer.id) {
|
||||||
|
let text: String
|
||||||
|
if case let .channel(channel) = self.peer, case .broadcast = channel.info {
|
||||||
|
text = environment.strings.LiveStream_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||||
|
} else {
|
||||||
|
text = environment.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||||
|
}
|
||||||
|
self.presentUndoOverlay(content: .invitedToVoiceChat(context: component.call.accountContext, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})]), in: .window(.root))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
controller.copyInviteLink = { [weak self] in
|
||||||
|
dismissController?()
|
||||||
|
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let callPeerId = component.call.peerId
|
||||||
|
|
||||||
|
let _ = (component.call.accountContext.engine.data.get(
|
||||||
|
TelegramEngine.EngineData.Item.Peer.Peer(id: callPeerId),
|
||||||
|
TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: callPeerId)
|
||||||
|
)
|
||||||
|
|> map { peer, exportedInvitation -> String? in
|
||||||
|
if let link = inviteLinks?.listenerLink {
|
||||||
|
return link
|
||||||
|
} else if let peer = peer, let addressName = peer.addressName, !addressName.isEmpty {
|
||||||
|
return "https://t.me/\(addressName)"
|
||||||
|
} else if let link = exportedInvitation?.link {
|
||||||
|
return link
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] link in
|
||||||
|
guard let self, let environment = self.environment else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let link {
|
||||||
|
UIPasteboard.general.string = link
|
||||||
|
|
||||||
|
self.presentUndoOverlay(content: .linkCopied(text: environment.strings.VoiceChat_InviteLinkCopiedText), action: { _ in return false })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
dismissController = { [weak controller] in
|
||||||
|
controller?.dismiss()
|
||||||
|
}
|
||||||
|
environment.controller()?.push(controller)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private func presentShare(_ inviteLinks: GroupCallInviteLinks) {
|
private func presentShare(_ inviteLinks: GroupCallInviteLinks) {
|
||||||
guard let component = self.component else {
|
guard let component = self.component else {
|
||||||
return
|
return
|
||||||
@ -2198,6 +2501,12 @@ private final class VideoChatScreenComponent: Component {
|
|||||||
self.expandedParticipantsVideoState = updatedExpandedParticipantsVideoState
|
self.expandedParticipantsVideoState = updatedExpandedParticipantsVideoState
|
||||||
self.state?.updated(transition: .spring(duration: 0.4))
|
self.state?.updated(transition: .spring(duration: 0.4))
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
openInviteMembers: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.openInviteMembers()
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
@ -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")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user