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
f97198d906
@ -51,7 +51,6 @@ BAZEL_OPTIONS=(\
|
||||
--spawn_strategy=standalone \
|
||||
--strategy=SwiftCompile=standalone \
|
||||
--features=swift.enable_batch_mode \
|
||||
--apple_generate_dsym \
|
||||
--swiftcopt=-j${CORE_COUNT_MINUS_ONE} \
|
||||
)
|
||||
|
||||
|
@ -174,6 +174,25 @@ public struct PresentationGroupCallState: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct PresentationGroupCallSummaryState: Equatable {
|
||||
public var info: GroupCallInfo
|
||||
public var participantCount: Int
|
||||
public var callState: PresentationGroupCallState
|
||||
public var topParticipants: [GroupCallParticipantsContext.Participant]
|
||||
|
||||
public init(
|
||||
info: GroupCallInfo,
|
||||
participantCount: Int,
|
||||
callState: PresentationGroupCallState,
|
||||
topParticipants: [GroupCallParticipantsContext.Participant]
|
||||
) {
|
||||
self.info = info
|
||||
self.participantCount = participantCount
|
||||
self.callState = callState
|
||||
self.topParticipants = topParticipants
|
||||
}
|
||||
}
|
||||
|
||||
public struct PresentationGroupCallMemberState: Equatable {
|
||||
public var ssrc: UInt32
|
||||
public var muteState: GroupCallParticipantsContext.Participant.MuteState?
|
||||
@ -197,9 +216,11 @@ public protocol PresentationGroupCall: class {
|
||||
|
||||
var canBeRemoved: Signal<Bool, NoError> { get }
|
||||
var state: Signal<PresentationGroupCallState, NoError> { get }
|
||||
var summaryState: Signal<PresentationGroupCallSummaryState?, NoError> { get }
|
||||
var members: Signal<[PeerId: PresentationGroupCallMemberState], NoError> { get }
|
||||
var audioLevels: Signal<[(PeerId, Float)], NoError> { get }
|
||||
var myAudioLevel: Signal<Float, NoError> { get }
|
||||
var isMuted: Signal<Bool, NoError> { get }
|
||||
|
||||
func leave() -> Signal<Bool, NoError>
|
||||
|
||||
@ -218,5 +239,5 @@ public protocol PresentationCallManager: class {
|
||||
var currentGroupCallSignal: Signal<PresentationGroupCall?, NoError> { get }
|
||||
|
||||
func requestCall(context: AccountContext, peerId: PeerId, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult
|
||||
func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId) -> RequestOrJoinGroupCallResult
|
||||
func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId, initialCall: CachedChannelData.ActiveCall?, endCurrentIfAny: Bool) -> RequestOrJoinGroupCallResult
|
||||
}
|
||||
|
@ -146,9 +146,7 @@ public final class AnimatedAvatarSetNode: ASDisplayNode {
|
||||
super.init()
|
||||
}
|
||||
|
||||
public func update(context: AccountContext, content: AnimatedAvatarSetContext.Content, animated: Bool, synchronousLoad: Bool) -> CGSize {
|
||||
let itemSize = CGSize(width: 30.0, height: 30.0)
|
||||
|
||||
public func update(context: AccountContext, content: AnimatedAvatarSetContext.Content, itemSize: CGSize = CGSize(width: 30.0, height: 30.0), animated: Bool, synchronousLoad: Bool) -> CGSize {
|
||||
var contentWidth: CGFloat = 0.0
|
||||
let contentHeight: CGFloat = itemSize.height
|
||||
|
||||
|
@ -171,7 +171,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
self.tabContainerNode = ChatListFilterTabContainerNode()
|
||||
|
||||
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary)
|
||||
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary, groupCallPanelSource: .all)
|
||||
|
||||
self.tabBarItemContextActionType = .always
|
||||
|
||||
|
@ -391,7 +391,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
|
||||
if let inCallStatusBar = self.inCallStatusBar {
|
||||
let inCallStatusBarFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(40.0, layout.safeInsets.top)))
|
||||
let inCallStatusBarFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(layout.statusBarHeight ?? 0.0, max(40.0, layout.safeInsets.top))))
|
||||
if inCallStatusBar.frame.isEmpty {
|
||||
inCallStatusBar.frame = inCallStatusBarFrame
|
||||
} else {
|
||||
|
@ -33,7 +33,7 @@ public final class HashtagSearchController: TelegramBaseController {
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .specific(size: .compact), locationBroadcastPanelSource: .none)
|
||||
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .specific(size: .compact), locationBroadcastPanelSource: .none, groupCallPanelSource: .none)
|
||||
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||
|
||||
|
@ -6,9 +6,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[571523412] = { return $0.readDouble() }
|
||||
dict[-1255641564] = { return parseString($0) }
|
||||
dict[-1240849242] = { return Api.messages.StickerSet.parse_stickerSet($0) }
|
||||
dict[-1331534976] = { return Api.GroupCall.parse_groupCallPrivate($0) }
|
||||
dict[1435512961] = { return Api.GroupCall.parse_groupCall($0) }
|
||||
dict[2004925620] = { return Api.GroupCall.parse_groupCallDiscarded($0) }
|
||||
dict[1435512961] = { return Api.GroupCall.parse_groupCall($0) }
|
||||
dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) }
|
||||
dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) }
|
||||
dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) }
|
||||
|
@ -1910,19 +1910,18 @@ public struct messages {
|
||||
}
|
||||
public extension Api {
|
||||
public enum GroupCall: TypeConstructorDescription {
|
||||
case groupCallPrivate(id: Int64, accessHash: Int64, participantsCount: Int32)
|
||||
case groupCall(flags: Int32, id: Int64, accessHash: Int64, participantsCount: Int32, params: Api.DataJSON?, version: Int32)
|
||||
case groupCallDiscarded(id: Int64, accessHash: Int64, duration: Int32)
|
||||
case groupCall(flags: Int32, id: Int64, accessHash: Int64, participantsCount: Int32, params: Api.DataJSON?, version: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .groupCallPrivate(let id, let accessHash, let participantsCount):
|
||||
case .groupCallDiscarded(let id, let accessHash, let duration):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1331534976)
|
||||
buffer.appendInt32(2004925620)
|
||||
}
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
serializeInt64(accessHash, buffer: buffer, boxed: false)
|
||||
serializeInt32(participantsCount, buffer: buffer, boxed: false)
|
||||
serializeInt32(duration, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .groupCall(let flags, let id, let accessHash, let participantsCount, let params, let version):
|
||||
if boxed {
|
||||
@ -1935,29 +1934,19 @@ public extension Api {
|
||||
if Int(flags) & Int(1 << 0) != 0 {params!.serialize(buffer, true)}
|
||||
serializeInt32(version, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .groupCallDiscarded(let id, let accessHash, let duration):
|
||||
if boxed {
|
||||
buffer.appendInt32(2004925620)
|
||||
}
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
serializeInt64(accessHash, buffer: buffer, boxed: false)
|
||||
serializeInt32(duration, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .groupCallPrivate(let id, let accessHash, let participantsCount):
|
||||
return ("groupCallPrivate", [("id", id), ("accessHash", accessHash), ("participantsCount", participantsCount)])
|
||||
case .groupCall(let flags, let id, let accessHash, let participantsCount, let params, let version):
|
||||
return ("groupCall", [("flags", flags), ("id", id), ("accessHash", accessHash), ("participantsCount", participantsCount), ("params", params), ("version", version)])
|
||||
case .groupCallDiscarded(let id, let accessHash, let duration):
|
||||
return ("groupCallDiscarded", [("id", id), ("accessHash", accessHash), ("duration", duration)])
|
||||
case .groupCall(let flags, let id, let accessHash, let participantsCount, let params, let version):
|
||||
return ("groupCall", [("flags", flags), ("id", id), ("accessHash", accessHash), ("participantsCount", participantsCount), ("params", params), ("version", version)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_groupCallPrivate(_ reader: BufferReader) -> GroupCall? {
|
||||
public static func parse_groupCallDiscarded(_ reader: BufferReader) -> GroupCall? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: Int64?
|
||||
@ -1968,7 +1957,7 @@ public extension Api {
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.GroupCall.groupCallPrivate(id: _1!, accessHash: _2!, participantsCount: _3!)
|
||||
return Api.GroupCall.groupCallDiscarded(id: _1!, accessHash: _2!, duration: _3!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
@ -2002,23 +1991,6 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_groupCallDiscarded(_ reader: BufferReader) -> GroupCall? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: Int64?
|
||||
_2 = reader.readInt64()
|
||||
var _3: Int32?
|
||||
_3 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.GroupCall.groupCallDiscarded(id: _1!, accessHash: _2!, duration: _3!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum InputGeoPoint: TypeConstructorDescription {
|
||||
|
@ -20,6 +20,8 @@ swift_library(
|
||||
"//submodules/OverlayStatusController:OverlayStatusController",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/Markdown:Markdown",
|
||||
"//submodules/TelegramCallsUI:TelegramCallsUI",
|
||||
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -0,0 +1,326 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import Postbox
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import AppBundle
|
||||
import SwiftSignalKit
|
||||
import AnimatedAvatarSetNode
|
||||
|
||||
private let titleFont = Font.semibold(15.0)
|
||||
private let subtitleFont = Font.regular(13.0)
|
||||
|
||||
final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
private var theme: PresentationTheme
|
||||
private var strings: PresentationStrings
|
||||
|
||||
private let tapAction: () -> Void
|
||||
|
||||
private let contentNode: ASDisplayNode
|
||||
|
||||
private let tapButton: HighlightTrackingButtonNode
|
||||
|
||||
private let joinButton: HighlightableButtonNode
|
||||
private let joinButtonTitleNode: ImmediateTextNode
|
||||
private let joinButtonBackgroundNode: ASImageNode
|
||||
|
||||
private let micButton: HighlightTrackingButtonNode
|
||||
private let micButtonForegroundMutedNode: ASImageNode
|
||||
private let micButtonForegroundUnmutedNode: ASImageNode
|
||||
private let micButtonBackgroundNode: ASImageNode
|
||||
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let textNode: ImmediateTextNode
|
||||
private let muteIconNode: ASImageNode
|
||||
|
||||
private let avatarsContext: AnimatedAvatarSetContext
|
||||
private var avatarsContent: AnimatedAvatarSetContext.Content?
|
||||
private let avatarsNode: AnimatedAvatarSetNode
|
||||
|
||||
private let separatorNode: ASDisplayNode
|
||||
|
||||
private let membersDisposable = MetaDisposable()
|
||||
private let isMutedDisposable = MetaDisposable()
|
||||
|
||||
private var currentData: GroupCallPanelData?
|
||||
private var validLayout: (CGSize, CGFloat, CGFloat)?
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, tapAction: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.theme = presentationData.theme
|
||||
self.strings = presentationData.strings
|
||||
|
||||
self.tapAction = tapAction
|
||||
|
||||
self.contentNode = ASDisplayNode()
|
||||
|
||||
self.tapButton = HighlightTrackingButtonNode()
|
||||
|
||||
self.joinButton = HighlightableButtonNode()
|
||||
self.joinButtonTitleNode = ImmediateTextNode()
|
||||
self.joinButtonBackgroundNode = ASImageNode()
|
||||
|
||||
self.micButton = HighlightTrackingButtonNode()
|
||||
self.micButtonForegroundMutedNode = ASImageNode()
|
||||
self.micButtonForegroundUnmutedNode = ASImageNode()
|
||||
self.micButtonBackgroundNode = ASImageNode()
|
||||
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.textNode = ImmediateTextNode()
|
||||
|
||||
self.muteIconNode = ASImageNode()
|
||||
|
||||
self.avatarsContext = AnimatedAvatarSetContext()
|
||||
self.avatarsNode = AnimatedAvatarSetNode()
|
||||
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.isLayerBacked = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.contentNode)
|
||||
|
||||
self.tapButton.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.titleNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.titleNode.alpha = 0.4
|
||||
strongSelf.textNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.textNode.alpha = 0.4
|
||||
} else {
|
||||
strongSelf.titleNode.alpha = 1.0
|
||||
strongSelf.titleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
strongSelf.textNode.alpha = 1.0
|
||||
strongSelf.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.contentNode.addSubnode(self.titleNode)
|
||||
self.contentNode.addSubnode(self.textNode)
|
||||
self.contentNode.addSubnode(self.muteIconNode)
|
||||
|
||||
self.contentNode.addSubnode(self.avatarsNode)
|
||||
|
||||
self.tapButton.addTarget(self, action: #selector(self.tapped), forControlEvents: [.touchUpInside])
|
||||
self.contentNode.addSubnode(self.tapButton)
|
||||
|
||||
self.joinButton.addSubnode(self.joinButtonBackgroundNode)
|
||||
self.joinButton.addSubnode(self.joinButtonTitleNode)
|
||||
self.contentNode.addSubnode(self.joinButton)
|
||||
self.joinButton.addTarget(self, action: #selector(self.tapped), forControlEvents: [.touchUpInside])
|
||||
|
||||
self.micButton.addSubnode(self.micButtonBackgroundNode)
|
||||
self.micButton.addSubnode(self.micButtonForegroundMutedNode)
|
||||
self.micButton.addSubnode(self.micButtonForegroundUnmutedNode)
|
||||
self.contentNode.addSubnode(self.micButton)
|
||||
self.micButton.addTarget(self, action: #selector(self.micTapped), forControlEvents: [.touchUpInside])
|
||||
|
||||
self.contentNode.addSubnode(self.separatorNode)
|
||||
|
||||
self.updatePresentationData(presentationData)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.membersDisposable.dispose()
|
||||
self.isMutedDisposable.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let longTapRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.micButtonPressGesture(_:)))
|
||||
longTapRecognizer.minimumPressDuration = 0.01
|
||||
self.micButton.view.addGestureRecognizer(longTapRecognizer)
|
||||
}
|
||||
|
||||
@objc private func tapped() {
|
||||
self.tapAction()
|
||||
}
|
||||
|
||||
@objc private func micTapped() {
|
||||
guard let call = self.currentData?.groupCall else {
|
||||
return
|
||||
}
|
||||
call.toggleIsMuted()
|
||||
}
|
||||
|
||||
@objc private func micButtonPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) {
|
||||
guard let call = self.currentData?.groupCall else {
|
||||
return
|
||||
}
|
||||
switch gestureRecognizer.state {
|
||||
case .began:
|
||||
call.setIsMuted(false)
|
||||
case .ended, .cancelled:
|
||||
call.setIsMuted(true)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func updatePresentationData(_ presentationData: PresentationData) {
|
||||
self.theme = presentationData.theme
|
||||
self.strings = presentationData.strings
|
||||
|
||||
self.contentNode.backgroundColor = self.theme.rootController.navigationBar.backgroundColor
|
||||
|
||||
self.theme = presentationData.theme
|
||||
|
||||
self.separatorNode.backgroundColor = presentationData.theme.chat.historyNavigation.strokeColor
|
||||
|
||||
self.joinButtonTitleNode.attributedText = NSAttributedString(string: presentationData.strings.Channel_JoinChannel.uppercased(), font: Font.semibold(15.0), textColor: presentationData.theme.chat.inputPanel.actionControlForegroundColor)
|
||||
self.joinButtonBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: presentationData.theme.chat.inputPanel.actionControlFillColor)
|
||||
|
||||
//TODO:localize
|
||||
self.micButtonBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 36.0, color: UIColor(rgb: 0x30B251))
|
||||
self.micButtonForegroundMutedNode.image = generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Mute"), color: .white)
|
||||
self.micButtonForegroundUnmutedNode.image = generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Unmute"), color: .white)
|
||||
|
||||
//TODO:localize
|
||||
self.titleNode.attributedText = NSAttributedString(string: "Voice Chat", font: Font.semibold(15.0), textColor: presentationData.theme.chat.inputPanel.primaryTextColor)
|
||||
self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: presentationData.theme.chat.inputPanel.secondaryTextColor)
|
||||
|
||||
self.muteIconNode.image = PresentationResourcesChat.chatTitleMuteIcon(presentationData.theme)
|
||||
}
|
||||
|
||||
func update(data: GroupCallPanelData) {
|
||||
let previousData = self.currentData
|
||||
self.currentData = data
|
||||
|
||||
if previousData?.groupCall !== data.groupCall {
|
||||
let membersText: String
|
||||
if data.participantCount == 0 {
|
||||
membersText = self.strings.PeopleNearby_NoMembers
|
||||
} else {
|
||||
membersText = self.strings.Conversation_StatusMembers(Int32(data.participantCount))
|
||||
}
|
||||
|
||||
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }, animated: false)
|
||||
|
||||
self.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: self.theme.chat.inputPanel.secondaryTextColor)
|
||||
|
||||
self.membersDisposable.set(nil)
|
||||
self.isMutedDisposable.set(nil)
|
||||
|
||||
if let groupCall = data.groupCall {
|
||||
self.membersDisposable.set((groupCall.summaryState
|
||||
|> deliverOnMainQueue).start(next: { [weak self] summaryState in
|
||||
guard let strongSelf = self, let summaryState = summaryState else {
|
||||
return
|
||||
}
|
||||
|
||||
let membersText: String
|
||||
if summaryState.participantCount == 0 {
|
||||
membersText = strongSelf.strings.PeopleNearby_NoMembers
|
||||
} else {
|
||||
membersText = strongSelf.strings.Conversation_StatusMembers(Int32(summaryState.participantCount))
|
||||
}
|
||||
|
||||
strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: summaryState.topParticipants.map { $0.peer }, animated: false)
|
||||
|
||||
strongSelf.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: strongSelf.theme.chat.inputPanel.secondaryTextColor)
|
||||
if let (size, leftInset, rightInset) = strongSelf.validLayout {
|
||||
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||
}
|
||||
}))
|
||||
|
||||
self.isMutedDisposable.set((groupCall.isMuted
|
||||
|> deliverOnMainQueue).start(next: { [weak self] isMuted in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.micButtonForegroundMutedNode.isHidden = !isMuted
|
||||
strongSelf.micButtonForegroundUnmutedNode.isHidden = isMuted
|
||||
}))
|
||||
}
|
||||
} else if data.groupCall == nil {
|
||||
let membersText: String
|
||||
if data.participantCount == 0 {
|
||||
membersText = self.strings.PeopleNearby_NoMembers
|
||||
} else {
|
||||
membersText = self.strings.Conversation_StatusMembers(Int32(data.participantCount))
|
||||
}
|
||||
|
||||
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }, animated: false)
|
||||
|
||||
self.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: self.theme.chat.inputPanel.secondaryTextColor)
|
||||
}
|
||||
|
||||
if let (size, leftInset, rightInset) = self.validLayout {
|
||||
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = (size, leftInset, rightInset)
|
||||
|
||||
let panelHeight = size.height
|
||||
|
||||
transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
self.tapButton.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width - 7.0 - 36.0 - 7.0, height: panelHeight))
|
||||
|
||||
if let avatarsContent = self.avatarsContent {
|
||||
let avatarsSize = self.avatarsNode.update(context: self.context, content: avatarsContent, itemSize: CGSize(width: 32.0, height: 32.0), animated: true, synchronousLoad: true)
|
||||
transition.updateFrame(node: self.avatarsNode, frame: CGRect(origin: CGPoint(x: 7.0, y: floor((size.height - avatarsSize.height) / 2.0)), size: avatarsSize))
|
||||
}
|
||||
|
||||
let joinButtonTitleSize = self.joinButtonTitleNode.updateLayout(CGSize(width: 150.0, height: .greatestFiniteMagnitude))
|
||||
let joinButtonSize = CGSize(width: joinButtonTitleSize.width + 20.0, height: 28.0)
|
||||
let joinButtonFrame = CGRect(origin: CGPoint(x: size.width - rightInset - 7.0 - joinButtonSize.width, y: floor((panelHeight - joinButtonSize.height) / 2.0)), size: joinButtonSize)
|
||||
transition.updateFrame(node: self.joinButton, frame: joinButtonFrame)
|
||||
transition.updateFrame(node: self.joinButtonBackgroundNode, frame: CGRect(origin: CGPoint(), size: joinButtonFrame.size))
|
||||
transition.updateFrame(node: self.joinButtonTitleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((joinButtonFrame.width - joinButtonTitleSize.width) / 2.0), y: floorToScreenPixels((joinButtonFrame.height - joinButtonTitleSize.height) / 2.0)), size: joinButtonTitleSize))
|
||||
|
||||
let micButtonSize = CGSize(width: 36.0, height: 36.0)
|
||||
let micButtonFrame = CGRect(origin: CGPoint(x: size.width - rightInset - 7.0 - micButtonSize.width, y: floor((panelHeight - micButtonSize.height) / 2.0)), size: micButtonSize)
|
||||
transition.updateFrame(node: self.micButton, frame: micButtonFrame)
|
||||
transition.updateFrame(node: self.micButtonBackgroundNode, frame: CGRect(origin: CGPoint(), size: micButtonFrame.size))
|
||||
if let image = self.micButtonForegroundMutedNode.image {
|
||||
transition.updateFrame(node: self.micButtonForegroundMutedNode, frame: CGRect(origin: CGPoint(x: floor((micButtonFrame.width - image.size.width) / 2.0), y: floor((micButtonFrame.height - image.size.height) / 2.0)), size: image.size))
|
||||
}
|
||||
if let image = self.micButtonForegroundUnmutedNode.image {
|
||||
transition.updateFrame(node: self.micButtonForegroundUnmutedNode, frame: CGRect(origin: CGPoint(x: floor((micButtonFrame.width - image.size.width) / 2.0), y: floor((micButtonFrame.height - image.size.height) / 2.0)), size: image.size))
|
||||
}
|
||||
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: 10.0), size: titleSize)
|
||||
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: titleFrame.maxY + 1.0), size: textSize))
|
||||
|
||||
if let image = self.muteIconNode.image {
|
||||
transition.updateFrame(node: self.muteIconNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: titleFrame.minY + 5.0), size: image.size))
|
||||
}
|
||||
self.muteIconNode.isHidden = self.currentData?.groupCall != nil
|
||||
self.joinButton.isHidden = self.currentData?.groupCall != nil
|
||||
self.micButton.isHidden = self.currentData?.groupCall == nil
|
||||
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelHeight - UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel)))
|
||||
}
|
||||
|
||||
func animateIn(_ transition: ContainedViewLayoutTransition) {
|
||||
self.clipsToBounds = true
|
||||
let contentPosition = self.contentNode.layer.position
|
||||
transition.animatePosition(node: self.contentNode, from: CGPoint(x: contentPosition.x, y: contentPosition.y - 50.0), completion: { [weak self] _ in
|
||||
self?.clipsToBounds = false
|
||||
})
|
||||
}
|
||||
|
||||
func animateOut(_ transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
||||
self.clipsToBounds = true
|
||||
let contentPosition = self.contentNode.layer.position
|
||||
transition.animatePosition(node: self.contentNode, to: CGPoint(x: contentPosition.x, y: contentPosition.y - 50.0), removeOnCompletion: false, completion: { [weak self] _ in
|
||||
self?.clipsToBounds = false
|
||||
completion()
|
||||
})
|
||||
}
|
||||
}
|
@ -25,6 +25,34 @@ public enum LocationBroadcastPanelSource {
|
||||
case peer(PeerId)
|
||||
}
|
||||
|
||||
public enum GroupCallPanelSource {
|
||||
case none
|
||||
case all
|
||||
case peer(PeerId)
|
||||
}
|
||||
|
||||
final class GroupCallPanelData {
|
||||
let peerId: PeerId
|
||||
let info: GroupCallInfo
|
||||
let topParticipants: [GroupCallParticipantsContext.Participant]
|
||||
let participantCount: Int
|
||||
let groupCall: PresentationGroupCall?
|
||||
|
||||
init(
|
||||
peerId: PeerId,
|
||||
info: GroupCallInfo,
|
||||
topParticipants: [GroupCallParticipantsContext.Participant],
|
||||
participantCount: Int,
|
||||
groupCall: PresentationGroupCall?
|
||||
) {
|
||||
self.peerId = peerId
|
||||
self.info = info
|
||||
self.topParticipants = topParticipants
|
||||
self.participantCount = participantCount
|
||||
self.groupCall = groupCall
|
||||
}
|
||||
}
|
||||
|
||||
private func presentLiveLocationController(context: AccountContext, peerId: PeerId, controller: ViewController) {
|
||||
let presentImpl: (Message?) -> Void = { [weak controller] message in
|
||||
if let message = message, let strongController = controller {
|
||||
@ -64,9 +92,11 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
|
||||
public let mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility
|
||||
public let locationBroadcastPanelSource: LocationBroadcastPanelSource
|
||||
public let groupCallPanelSource: GroupCallPanelSource
|
||||
|
||||
private var mediaStatusDisposable: Disposable?
|
||||
private var locationBroadcastDisposable: Disposable?
|
||||
private var currentGroupCallDisposable: Disposable?
|
||||
|
||||
public private(set) var playlistStateAndType: (SharedMediaPlaylistItem, SharedMediaPlaylistItem?, SharedMediaPlaylistItem?, MusicPlaybackSettingsOrder, MediaManagerPlayerType, Account)?
|
||||
private var playlistLocation: SharedMediaPlaylistLocation?
|
||||
@ -81,6 +111,9 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
private var locationBroadcastMessages: [MessageId: Message]?
|
||||
private var locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel?
|
||||
|
||||
private var groupCallPanelData: GroupCallPanelData?
|
||||
private var groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel?
|
||||
|
||||
private var dismissingPanel: ASDisplayNode?
|
||||
|
||||
private var presentationData: PresentationData
|
||||
@ -101,6 +134,9 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
|
||||
public var additionalHeight: CGFloat {
|
||||
var height: CGFloat = 0.0
|
||||
if let _ = self.groupCallAccessoryPanel {
|
||||
height += 50.0
|
||||
}
|
||||
if let _ = self.mediaAccessoryPanel {
|
||||
height += MediaNavigationAccessoryHeaderNode.minimizedHeight
|
||||
}
|
||||
@ -114,11 +150,12 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
return super.navigationHeight
|
||||
}
|
||||
|
||||
public init(context: AccountContext, navigationBarPresentationData: NavigationBarPresentationData?, mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility, locationBroadcastPanelSource: LocationBroadcastPanelSource) {
|
||||
public init(context: AccountContext, navigationBarPresentationData: NavigationBarPresentationData?, mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility, locationBroadcastPanelSource: LocationBroadcastPanelSource, groupCallPanelSource: GroupCallPanelSource) {
|
||||
self.context = context
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.mediaAccessoryPanelVisibility = mediaAccessoryPanelVisibility
|
||||
self.locationBroadcastPanelSource = locationBroadcastPanelSource
|
||||
self.groupCallPanelSource = groupCallPanelSource
|
||||
|
||||
super.init(navigationBarPresentationData: navigationBarPresentationData)
|
||||
|
||||
@ -250,6 +287,93 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
}
|
||||
}
|
||||
|
||||
if let callManager = context.sharedContext.callManager {
|
||||
switch groupCallPanelSource {
|
||||
case .none:
|
||||
break
|
||||
default:
|
||||
let currentGroupCall: Signal<GroupCallPanelData?, NoError> = callManager.currentGroupCallSignal
|
||||
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
||||
return lhs?.internalId == rhs?.internalId
|
||||
})
|
||||
|> mapToSignal { call -> Signal<GroupCallPanelData?, NoError> in
|
||||
guard let call = call else {
|
||||
return .single(nil)
|
||||
}
|
||||
return call.summaryState
|
||||
|> filter { $0 != nil }
|
||||
|> take(1)
|
||||
|> map { summary -> GroupCallPanelData? in
|
||||
guard let summary = summary else {
|
||||
return nil
|
||||
}
|
||||
return GroupCallPanelData(
|
||||
peerId: call.peerId,
|
||||
info: summary.info,
|
||||
topParticipants: summary.topParticipants,
|
||||
participantCount: summary.participantCount,
|
||||
groupCall: call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let availableGroupCall: Signal<GroupCallPanelData?, NoError>
|
||||
if case let .peer(peerId) = groupCallPanelSource {
|
||||
availableGroupCall = context.account.viewTracker.peerView(peerId)
|
||||
|> map { peerView -> CachedChannelData.ActiveCall? in
|
||||
guard let cachedData = peerView.cachedData as? CachedChannelData else {
|
||||
return nil
|
||||
}
|
||||
return cachedData.activeCall
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { activeCall -> Signal<GroupCallPanelData?, NoError> in
|
||||
guard let activeCall = activeCall else {
|
||||
return .single(nil)
|
||||
}
|
||||
return getCurrentGroupCall(account: context.account, callId: activeCall.id, accessHash: activeCall.accessHash)
|
||||
|> `catch` { _ -> Signal<GroupCallSummary?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> map { summary -> GroupCallPanelData? in
|
||||
guard let summary = summary else {
|
||||
return nil
|
||||
}
|
||||
return GroupCallPanelData(
|
||||
peerId: peerId,
|
||||
info: summary.info,
|
||||
topParticipants: summary.topParticipants,
|
||||
participantCount: summary.info.participantCount,
|
||||
groupCall: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
availableGroupCall = .single(nil)
|
||||
}
|
||||
|
||||
self.currentGroupCallDisposable = combineLatest(queue: .mainQueue(),
|
||||
currentGroupCall,
|
||||
availableGroupCall
|
||||
).start(next: { [weak self] currentState, availableState in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let panelData = currentState ?? availableState
|
||||
|
||||
let wasEmpty = strongSelf.groupCallPanelData == nil
|
||||
strongSelf.groupCallPanelData = panelData
|
||||
let isEmpty = strongSelf.groupCallPanelData == nil
|
||||
if wasEmpty != isEmpty {
|
||||
strongSelf.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
|
||||
} else if let groupCallPanelData = strongSelf.groupCallPanelData {
|
||||
strongSelf.groupCallAccessoryPanel?.update(data: groupCallPanelData)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
self.presentationDataDisposable = (context.sharedContext.presentationData
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
@ -269,6 +393,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
deinit {
|
||||
self.mediaStatusDisposable?.dispose()
|
||||
self.locationBroadcastDisposable?.dispose()
|
||||
self.currentGroupCallDisposable?.dispose()
|
||||
self.presentationDataDisposable?.dispose()
|
||||
self.playlistPreloadDisposable?.dispose()
|
||||
}
|
||||
@ -287,6 +412,52 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
|
||||
var additionalHeight: CGFloat = 0.0
|
||||
|
||||
if let groupCallPanelData = self.groupCallPanelData {
|
||||
let panelHeight: CGFloat = 50.0
|
||||
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight.isZero ? -panelHeight : (navigationHeight + additionalHeight + UIScreenPixel)), size: CGSize(width: layout.size.width, height: panelHeight))
|
||||
additionalHeight += panelHeight
|
||||
|
||||
let groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel
|
||||
if let current = self.groupCallAccessoryPanel {
|
||||
groupCallAccessoryPanel = current
|
||||
transition.updateFrame(node: groupCallAccessoryPanel, frame: panelFrame)
|
||||
groupCallAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition)
|
||||
} else {
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
groupCallAccessoryPanel = GroupCallNavigationAccessoryPanel(context: self.context, presentationData: presentationData, tapAction: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.joinGroupCall(
|
||||
peerId: groupCallPanelData.peerId,
|
||||
info: groupCallPanelData.info
|
||||
)
|
||||
})
|
||||
if let navigationBar = self.navigationBar {
|
||||
self.displayNode.insertSubnode(groupCallAccessoryPanel, aboveSubnode: navigationBar)
|
||||
} else {
|
||||
self.displayNode.addSubnode(groupCallAccessoryPanel)
|
||||
}
|
||||
self.groupCallAccessoryPanel = groupCallAccessoryPanel
|
||||
groupCallAccessoryPanel.frame = panelFrame
|
||||
|
||||
groupCallAccessoryPanel.update(data: groupCallPanelData)
|
||||
groupCallAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: .immediate)
|
||||
if transition.isAnimated {
|
||||
groupCallAccessoryPanel.animateIn(transition)
|
||||
}
|
||||
}
|
||||
} else if let groupCallAccessoryPanel = self.groupCallAccessoryPanel {
|
||||
self.groupCallAccessoryPanel = nil
|
||||
if transition.isAnimated {
|
||||
groupCallAccessoryPanel.animateOut(transition, completion: { [weak groupCallAccessoryPanel] in
|
||||
groupCallAccessoryPanel?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
groupCallAccessoryPanel.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
if let locationBroadcastPeers = self.locationBroadcastPeers, let locationBroadcastMode = self.locationBroadcastMode {
|
||||
let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
|
||||
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight.isZero ? -panelHeight : (navigationHeight + additionalHeight + UIScreenPixel)), size: CGSize(width: layout.size.width, height: panelHeight))
|
||||
@ -675,4 +846,36 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
}
|
||||
})]
|
||||
}
|
||||
|
||||
private func joinGroupCall(peerId: PeerId, info: GroupCallInfo) {
|
||||
let callResult = self.context.sharedContext.callManager?.requestOrJoinGroupCall(context: self.context, peerId: peerId, initialCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash), endCurrentIfAny: false)
|
||||
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
|
||||
if currentPeerId == peerId {
|
||||
self.context.sharedContext.navigateToCurrentCall()
|
||||
} else {
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let _ = (self.context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
|
||||
return (transaction.getPeer(peerId), currentPeerId.flatMap(transaction.getPeer))
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer, current in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
guard let peer = peer else {
|
||||
return
|
||||
}
|
||||
if let current = current {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
|
||||
if let strongSelf = self {
|
||||
let _ = strongSelf.context.sharedContext.callManager?.requestOrJoinGroupCall(context: strongSelf.context, peerId: peerId, initialCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash), endCurrentIfAny: true)
|
||||
}
|
||||
})]), in: .window(.root))
|
||||
} else {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_ExternalCallInProgressMessage, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
|
||||
})]), in: .window(.root))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -592,17 +592,32 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
}
|
||||
}
|
||||
|
||||
public func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId) -> RequestOrJoinGroupCallResult {
|
||||
if let currentGroupCall = self.currentGroupCallValue {
|
||||
return .alreadyInProgress(currentGroupCall.peerId)
|
||||
public func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId, initialCall: CachedChannelData.ActiveCall?, endCurrentIfAny: Bool) -> RequestOrJoinGroupCallResult {
|
||||
let begin: () -> Void = { [weak self] in
|
||||
let _ = self?.startGroupCall(accountContext: context, peerId: peerId, initialCall: initialCall).start()
|
||||
}
|
||||
if let currentGroupCall = self.currentGroupCallValue {
|
||||
if endCurrentIfAny {
|
||||
let endSignal = currentGroupCall.leave()
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue
|
||||
self.startCallDisposable.set(endSignal.start(next: { _ in
|
||||
begin()
|
||||
}))
|
||||
} else {
|
||||
return .alreadyInProgress(currentGroupCall.peerId)
|
||||
}
|
||||
} else {
|
||||
begin()
|
||||
}
|
||||
let _ = self.startGroupCall(accountContext: context, peerId: peerId).start()
|
||||
return .requested
|
||||
}
|
||||
|
||||
private func startGroupCall(
|
||||
accountContext: AccountContext,
|
||||
peerId: PeerId,
|
||||
initialCall: CachedChannelData.ActiveCall?,
|
||||
internalId: CallSessionInternalId = CallSessionInternalId()
|
||||
) -> Signal<Bool, NoError> {
|
||||
let (presentationData, present, openSettings) = self.getDeviceAccessData()
|
||||
@ -649,6 +664,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
audioSession: strongSelf.audioSession,
|
||||
callKitIntegration: nil,
|
||||
getDeviceAccessData: strongSelf.getDeviceAccessData,
|
||||
initialCall: initialCall,
|
||||
internalId: internalId,
|
||||
peerId: peerId,
|
||||
peer: nil
|
||||
|
@ -41,6 +41,29 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
}
|
||||
|
||||
private struct SummaryInfoState: Equatable {
|
||||
public var info: GroupCallInfo
|
||||
|
||||
public init(
|
||||
info: GroupCallInfo
|
||||
) {
|
||||
self.info = info
|
||||
}
|
||||
}
|
||||
|
||||
private struct SummaryParticipantsState: Equatable {
|
||||
public var participantCount: Int
|
||||
public var topParticipants: [GroupCallParticipantsContext.Participant]
|
||||
|
||||
public init(
|
||||
participantCount: Int,
|
||||
topParticipants: [GroupCallParticipantsContext.Participant]
|
||||
) {
|
||||
self.participantCount = participantCount
|
||||
self.topParticipants = topParticipants
|
||||
}
|
||||
}
|
||||
|
||||
public let account: Account
|
||||
public let accountContext: AccountContext
|
||||
private let audioSession: ManagedAudioSession
|
||||
@ -51,6 +74,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
|
||||
private let getDeviceAccessData: () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void)
|
||||
|
||||
private let initialCall: CachedChannelData.ActiveCall?
|
||||
public let internalId: CallSessionInternalId
|
||||
public let peerId: PeerId
|
||||
public let peer: Peer?
|
||||
@ -60,7 +84,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
private var callContext: OngoingGroupCallContext?
|
||||
private var ssrcMapping: [UInt32: PeerId] = [:]
|
||||
|
||||
private var sessionStateDisposable: Disposable?
|
||||
private var summaryInfoState = Promise<SummaryInfoState?>(nil)
|
||||
private var summaryParticipantsState = Promise<SummaryParticipantsState?>(nil)
|
||||
|
||||
private let summaryStatePromise = Promise<PresentationGroupCallSummaryState?>(nil)
|
||||
public var summaryState: Signal<PresentationGroupCallSummaryState?, NoError> {
|
||||
return self.summaryStatePromise.get()
|
||||
}
|
||||
private var summaryStateDisposable: Disposable?
|
||||
|
||||
private let isMutedPromise = ValuePromise<Bool>(true)
|
||||
private var isMutedValue = true
|
||||
@ -155,6 +186,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
audioSession: ManagedAudioSession,
|
||||
callKitIntegration: CallKitIntegration?,
|
||||
getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void),
|
||||
initialCall: CachedChannelData.ActiveCall?,
|
||||
internalId: CallSessionInternalId,
|
||||
peerId: PeerId,
|
||||
peer: Peer?
|
||||
@ -165,6 +197,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
self.callKitIntegration = callKitIntegration
|
||||
self.getDeviceAccessData = getDeviceAccessData
|
||||
|
||||
self.initialCall = initialCall
|
||||
self.internalId = internalId
|
||||
self.peerId = peerId
|
||||
self.peer = peer
|
||||
@ -269,13 +302,33 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
})
|
||||
|
||||
self.summaryStatePromise.set(combineLatest(queue: .mainQueue(),
|
||||
self.summaryInfoState.get(),
|
||||
self.summaryParticipantsState.get(),
|
||||
self.statePromise.get()
|
||||
)
|
||||
|> map { infoState, participantsState, callState -> PresentationGroupCallSummaryState? in
|
||||
guard let infoState = infoState else {
|
||||
return nil
|
||||
}
|
||||
guard let participantsState = participantsState else {
|
||||
return nil
|
||||
}
|
||||
return PresentationGroupCallSummaryState(
|
||||
info: infoState.info,
|
||||
participantCount: participantsState.participantCount,
|
||||
callState: callState,
|
||||
topParticipants: participantsState.topParticipants
|
||||
)
|
||||
})
|
||||
|
||||
self.requestCall()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.audioSessionShouldBeActiveDisposable?.dispose()
|
||||
self.audioSessionActiveDisposable?.dispose()
|
||||
self.sessionStateDisposable?.dispose()
|
||||
self.summaryStateDisposable?.dispose()
|
||||
self.audioSessionDisposable?.dispose()
|
||||
self.requestDisposable.dispose()
|
||||
self.groupCallParticipantUpdatesDisposable?.dispose()
|
||||
@ -409,6 +462,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
break
|
||||
default:
|
||||
if case let .estabilished(callInfo, clientParams, _, initialState) = internalState {
|
||||
self.summaryInfoState.set(.single(SummaryInfoState(info: callInfo)))
|
||||
|
||||
self.ssrcMapping.removeAll()
|
||||
var ssrcs: [UInt32] = []
|
||||
for participant in initialState.participants {
|
||||
@ -431,7 +486,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
|
||||
var memberStates: [PeerId: PresentationGroupCallMemberState] = [:]
|
||||
var topParticipants: [GroupCallParticipantsContext.Participant] = []
|
||||
for participant in state.participants {
|
||||
if topParticipants.count < 3 {
|
||||
topParticipants.append(participant)
|
||||
}
|
||||
|
||||
strongSelf.ssrcMapping[participant.ssrc] = participant.peer.id
|
||||
|
||||
memberStates[participant.peer.id] = PresentationGroupCallMemberState(
|
||||
@ -440,6 +500,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
)
|
||||
}
|
||||
strongSelf.membersValue = memberStates
|
||||
|
||||
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
||||
participantCount: state.totalCount,
|
||||
topParticipants: topParticipants
|
||||
)))
|
||||
}))
|
||||
|
||||
if let isCurrentlyConnecting = self.isCurrentlyConnecting, isCurrentlyConnecting {
|
||||
@ -546,9 +611,17 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
let account = self.account
|
||||
let peerId = self.peerId
|
||||
|
||||
let currentCall = getCurrentGroupCall(account: account, peerId: peerId)
|
||||
|> mapError { _ -> CallError in
|
||||
return .generic
|
||||
let currentCall: Signal<GroupCallInfo?, CallError>
|
||||
if let initialCall = self.initialCall {
|
||||
currentCall = getCurrentGroupCall(account: account, callId: initialCall.id, accessHash: initialCall.accessHash)
|
||||
|> mapError { _ -> CallError in
|
||||
return .generic
|
||||
}
|
||||
|> map { summary -> GroupCallInfo? in
|
||||
return summary?.info
|
||||
}
|
||||
} else {
|
||||
currentCall = .single(nil)
|
||||
}
|
||||
|
||||
let currentOrRequestedCall = currentCall
|
||||
|
@ -468,6 +468,7 @@ public final class VoiceChatController: ViewController {
|
||||
guard let peer = view.peers[view.peerId] else {
|
||||
return
|
||||
}
|
||||
//TODO:localize
|
||||
var subtitle = "group"
|
||||
if let cachedData = view.cachedData as? CachedChannelData {
|
||||
if let memberCount = cachedData.participantsSummary.memberCount {
|
||||
@ -476,7 +477,7 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
|
||||
let titleView = VoiceChatControllerTitleView(theme: strongSelf.presentationData.theme)
|
||||
titleView.set(title: peer.debugDisplayTitle, subtitle: subtitle)
|
||||
titleView.set(title: "Voice Chat", subtitle: subtitle)
|
||||
strongSelf.controller?.navigationItem.titleView = titleView
|
||||
|
||||
if !strongSelf.didSetDataReady {
|
||||
@ -548,7 +549,7 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_SpeakPermissionEveryone, icon: { theme in
|
||||
/*items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_SpeakPermissionEveryone, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
f(.dismissWithoutContent)
|
||||
@ -558,7 +559,7 @@ public final class VoiceChatController: ViewController {
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
})))
|
||||
items.append(.separator)
|
||||
items.append(.separator)*/
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_Share, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { [weak self] _, f in
|
||||
|
@ -7,19 +7,19 @@ import SyncCore
|
||||
public struct GroupCallInfo: Equatable {
|
||||
public var id: Int64
|
||||
public var accessHash: Int64
|
||||
public var participantCount: Int
|
||||
public var clientParams: String?
|
||||
}
|
||||
|
||||
public struct GroupCallSummary: Equatable {
|
||||
public var info: GroupCallInfo
|
||||
public var topParticipants: [GroupCallParticipantsContext.Participant]
|
||||
}
|
||||
|
||||
private extension GroupCallInfo {
|
||||
init?(_ call: Api.GroupCall) {
|
||||
switch call {
|
||||
case let .groupCallPrivate(id, accessHash, _):
|
||||
self.init(
|
||||
id: id,
|
||||
accessHash: accessHash,
|
||||
clientParams: nil
|
||||
)
|
||||
case let .groupCall(_, id, accessHash, _, params, _):
|
||||
case let .groupCall(_, id, accessHash, participantCount, params, _):
|
||||
var clientParams: String?
|
||||
if let params = params {
|
||||
switch params {
|
||||
@ -30,6 +30,7 @@ private extension GroupCallInfo {
|
||||
self.init(
|
||||
id: id,
|
||||
accessHash: accessHash,
|
||||
participantCount: Int(participantCount),
|
||||
clientParams: clientParams
|
||||
)
|
||||
case .groupCallDiscarded:
|
||||
@ -42,49 +43,66 @@ public enum GetCurrentGroupCallError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func getCurrentGroupCall(account: Account, peerId: PeerId) -> Signal<GroupCallInfo?, GetCurrentGroupCallError> {
|
||||
return account.postbox.transaction { transaction -> Api.InputChannel? in
|
||||
transaction.getPeer(peerId).flatMap(apiInputChannel)
|
||||
public func getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int64) -> Signal<GroupCallSummary?, GetCurrentGroupCallError> {
|
||||
return account.network.request(Api.functions.phone.getGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash)))
|
||||
|> mapError { _ -> GetCurrentGroupCallError in
|
||||
return .generic
|
||||
}
|
||||
|> castError(GetCurrentGroupCallError.self)
|
||||
|> mapToSignal { inputPeer -> Signal<Api.InputGroupCall?, GetCurrentGroupCallError> in
|
||||
guard let inputPeer = inputPeer else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
return account.network.request(Api.functions.channels.getFullChannel(channel: inputPeer))
|
||||
|> mapError { _ -> GetCurrentGroupCallError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Api.InputGroupCall?, GetCurrentGroupCallError> in
|
||||
switch result {
|
||||
case let .chatFull(fullChat, _, _):
|
||||
switch fullChat {
|
||||
case let .channelFull(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, inputCall):
|
||||
return .single(inputCall)
|
||||
default:
|
||||
return .single(nil)
|
||||
|> mapToSignal { result -> Signal<GroupCallSummary?, GetCurrentGroupCallError> in
|
||||
switch result {
|
||||
case let .groupCall(call, _, participants, users):
|
||||
return account.postbox.transaction { transaction -> GroupCallSummary? in
|
||||
guard let info = GroupCallInfo(call) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var peers: [Peer] = []
|
||||
var peerPresences: [PeerId: PeerPresence] = [:]
|
||||
|
||||
for user in users {
|
||||
let telegramUser = TelegramUser(user: user)
|
||||
peers.append(telegramUser)
|
||||
if let presence = TelegramUserPresence(apiUser: user) {
|
||||
peerPresences[telegramUser.id] = presence
|
||||
}
|
||||
}
|
||||
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
|
||||
return updated
|
||||
})
|
||||
updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences)
|
||||
|
||||
var parsedParticipants: [GroupCallParticipantsContext.Participant] = []
|
||||
|
||||
loop: for participant in participants {
|
||||
switch participant {
|
||||
case let .groupCallParticipant(flags, userId, date, source):
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
|
||||
let ssrc = UInt32(bitPattern: source)
|
||||
guard let peer = transaction.getPeer(peerId) else {
|
||||
continue loop
|
||||
}
|
||||
var muteState: GroupCallParticipantsContext.Participant.MuteState?
|
||||
if (flags & (1 << 0)) != 0 {
|
||||
let canUnmute = (flags & (1 << 2)) != 0
|
||||
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute)
|
||||
}
|
||||
parsedParticipants.append(GroupCallParticipantsContext.Participant(
|
||||
peer: peer,
|
||||
ssrc: ssrc,
|
||||
joinTimestamp: date,
|
||||
muteState: muteState
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
return GroupCallSummary(
|
||||
info: info,
|
||||
topParticipants: parsedParticipants
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> mapToSignal { inputCall -> Signal<GroupCallInfo?, GetCurrentGroupCallError> in
|
||||
guard let inputCall = inputCall else {
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.phone.getGroupCall(call: inputCall))
|
||||
|> mapError { _ -> GetCurrentGroupCallError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<GroupCallInfo?, GetCurrentGroupCallError> in
|
||||
switch result {
|
||||
case let .groupCall(call, _, _, _):
|
||||
return account.postbox.transaction { transaction -> GroupCallInfo? in
|
||||
return GroupCallInfo(call)
|
||||
}
|
||||
|> mapError { _ -> GetCurrentGroupCallError in
|
||||
return .generic
|
||||
}
|
||||
|> mapError { _ -> GetCurrentGroupCallError in
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -397,13 +397,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.peekData = peekData
|
||||
|
||||
var locationBroadcastPanelSource: LocationBroadcastPanelSource
|
||||
var groupCallPanelSource: GroupCallPanelSource
|
||||
|
||||
switch chatLocation {
|
||||
case let .peer(peerId):
|
||||
locationBroadcastPanelSource = .peer(peerId)
|
||||
groupCallPanelSource = .peer(peerId)
|
||||
self.chatLocationInfoData = .peer(Promise())
|
||||
case let .replyThread(replyThreadMessage):
|
||||
locationBroadcastPanelSource = .none
|
||||
groupCallPanelSource = .all
|
||||
let promise = Promise<Message?>()
|
||||
let key = PostboxViewKey.messages([replyThreadMessage.messageId])
|
||||
promise.set(context.account.postbox.combinedView(keys: [key])
|
||||
@ -428,6 +431,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
mediaAccessoryPanelVisibility = .specific(size: .compact)
|
||||
} else {
|
||||
locationBroadcastPanelSource = .none
|
||||
groupCallPanelSource = .none
|
||||
}
|
||||
let navigationBarPresentationData: NavigationBarPresentationData?
|
||||
switch mode {
|
||||
@ -436,7 +440,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
default:
|
||||
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding ? true : false, hideBadge: false)
|
||||
}
|
||||
super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource)
|
||||
super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource, groupCallPanelSource: groupCallPanelSource)
|
||||
|
||||
self.automaticallyControlPresentationContextLayout = false
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
@ -5982,7 +5986,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
|
||||
return
|
||||
}
|
||||
let callResult = strongSelf.context.sharedContext.callManager?.requestOrJoinGroupCall(context: strongSelf.context, peerId: peer.id)
|
||||
let callResult = strongSelf.context.sharedContext.callManager?.requestOrJoinGroupCall(context: strongSelf.context, peerId: peer.id, initialCall: activeCall, endCurrentIfAny: false)
|
||||
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
|
||||
if currentPeerId == peer.id {
|
||||
strongSelf.context.sharedContext.navigateToCurrentCall()
|
||||
@ -5990,12 +5994,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
|
||||
return (transaction.getPeer(peer.id), currentPeerId.flatMap(transaction.getPeer))
|
||||
} |> deliverOnMainQueue).start(next: { [weak self] peer, current in
|
||||
} |> deliverOnMainQueue).start(next: { peer, current in
|
||||
if let peer = peer {
|
||||
if let strongSelf = self, let current = current {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
|
||||
if let strongSelf = self {
|
||||
//let _ = strongSelf.context.sharedContext.callManager?.requestCall(context: context, peerId: peerId, isVideo: isVideo, endCurrentIfAny: true)
|
||||
let _ = strongSelf.context.sharedContext.callManager?.requestOrJoinGroupCall(context: strongSelf.context, peerId: peer.id, initialCall: activeCall, endCurrentIfAny: true)
|
||||
}
|
||||
})]), in: .window(.root))
|
||||
} else {
|
||||
|
@ -64,16 +64,6 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
|
||||
}
|
||||
}
|
||||
|
||||
if chatPresentationInterfaceState.activeGroupCallInfo != nil {
|
||||
if let currentPanel = currentPanel as? ChatCallTitlePanelNode {
|
||||
return currentPanel
|
||||
} else {
|
||||
let panel = ChatCallTitlePanelNode(context: context)
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
return panel
|
||||
}
|
||||
}
|
||||
|
||||
if let selectedContext = selectedContext {
|
||||
switch selectedContext {
|
||||
case .pinnedMessage:
|
||||
|
@ -36,7 +36,7 @@ final class ChatRecentActionsController: TelegramBaseController {
|
||||
|
||||
self.titleView = ChatRecentActionsTitleView(color: self.presentationData.theme.rootController.navigationBar.primaryTextColor)
|
||||
|
||||
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .specific(size: .compact), locationBroadcastPanelSource: .none)
|
||||
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .specific(size: .compact), locationBroadcastPanelSource: .none, groupCallPanelSource: .none)
|
||||
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||
|
||||
|
@ -3166,7 +3166,10 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
|
||||
private func requestCall(isVideo: Bool) {
|
||||
if let peer = self.data?.peer as? TelegramChannel {
|
||||
self.context.sharedContext.callManager?.requestOrJoinGroupCall(context: self.context, peerId: peer.id)
|
||||
guard let cachedChannelData = self.data?.cachedData as? CachedChannelData else {
|
||||
return
|
||||
}
|
||||
let _ = self.context.sharedContext.callManager?.requestOrJoinGroupCall(context: self.context, peerId: peer.id, initialCall: cachedChannelData.activeCall, endCurrentIfAny: false)
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -655,23 +655,26 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}
|
||||
})
|
||||
|
||||
self.callStateDisposable = (self.callState.get()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
self.callStateDisposable = combineLatest(queue: .mainQueue(),
|
||||
self.callState.get(),
|
||||
callManager.currentGroupCallSignal
|
||||
|> map { call -> Bool in
|
||||
return call != nil
|
||||
}
|
||||
).start(next: { [weak self] state, hasGroupCall in
|
||||
if let strongSelf = self {
|
||||
let resolvedText: CallStatusText
|
||||
if let state = state {
|
||||
// if [.active, .paused].contains(state.videoState) || [.active, .paused].contains(state.remoteVideoState) {
|
||||
// resolvedText = .none
|
||||
// } else {
|
||||
switch state.state {
|
||||
case .connecting, .requesting, .terminating, .ringing, .waiting:
|
||||
resolvedText = .inProgress(nil)
|
||||
case .terminated:
|
||||
resolvedText = .none
|
||||
case .active(let timestamp, _, _), .reconnecting(let timestamp, _, _):
|
||||
resolvedText = .inProgress(timestamp)
|
||||
}
|
||||
// }
|
||||
switch state.state {
|
||||
case .connecting, .requesting, .terminating, .ringing, .waiting:
|
||||
resolvedText = .inProgress(nil)
|
||||
case .terminated:
|
||||
resolvedText = .none
|
||||
case .active(let timestamp, _, _), .reconnecting(let timestamp, _, _):
|
||||
resolvedText = .inProgress(timestamp)
|
||||
}
|
||||
} else if hasGroupCall {
|
||||
resolvedText = .inProgress(nil)
|
||||
} else {
|
||||
resolvedText = .none
|
||||
}
|
||||
@ -705,7 +708,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
})
|
||||
|
||||
mainWindow.inCallNavigate = { [weak self] in
|
||||
if let strongSelf = self, let callController = strongSelf.callController {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let callController = strongSelf.callController {
|
||||
if callController.isNodeLoaded {
|
||||
mainWindow.hostView.containerView.endEditing(true)
|
||||
if callController.view.superview == nil {
|
||||
@ -714,6 +720,13 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
callController.expandFromPipIfPossible()
|
||||
}
|
||||
}
|
||||
} else if let groupCallController = strongSelf.groupCallController {
|
||||
if groupCallController.isNodeLoaded {
|
||||
mainWindow.hostView.containerView.endEditing(true)
|
||||
if groupCallController.view.superview == nil {
|
||||
mainWindow.present(groupCallController, on: .calls)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
Loading…
x
Reference in New Issue
Block a user