mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2026-02-22 10:33:18 +00:00
362 lines
20 KiB
Swift
362 lines
20 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import AccountContext
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramCore
|
|
import AsyncDisplayKit
|
|
import ContextUI
|
|
import CreateExternalMediaStreamScreen
|
|
import OverlayStatusController
|
|
import TelegramPresentationData
|
|
import PresentationDataUtils
|
|
import TelegramCallsUI
|
|
import AvatarNode
|
|
|
|
extension PeerInfoScreenNode {
|
|
func requestCall(isVideo: Bool, gesture: ContextGesture? = nil, contextController: ContextControllerProtocol? = nil, result: ((ContextMenuActionResult) -> Void)? = nil, backAction: ((ContextControllerProtocol) -> Void)? = nil) {
|
|
guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else {
|
|
return
|
|
}
|
|
let peerId = self.peerId
|
|
let requestCall: (PeerId?, EngineGroupCallDescription?) -> Void = { [weak self] defaultJoinAsPeerId, activeCall in
|
|
if let activeCall = activeCall {
|
|
self?.context.joinGroupCall(peerId: peerId, invite: nil, requestJoinAsPeerId: { completion in
|
|
if let defaultJoinAsPeerId = defaultJoinAsPeerId {
|
|
result?(.dismissWithoutContent)
|
|
completion(defaultJoinAsPeerId)
|
|
} else {
|
|
self?.openVoiceChatDisplayAsPeerSelection(completion: { joinAsPeerId in
|
|
completion(joinAsPeerId)
|
|
}, gesture: gesture, contextController: contextController, result: result, backAction: backAction)
|
|
}
|
|
}, activeCall: activeCall)
|
|
} else {
|
|
self?.openVoiceChatOptions(defaultJoinAsPeerId: defaultJoinAsPeerId, gesture: gesture, contextController: contextController)
|
|
}
|
|
}
|
|
|
|
if let cachedChannelData = self.data?.cachedData as? CachedChannelData {
|
|
requestCall(cachedChannelData.callJoinPeerId, cachedChannelData.activeCall.flatMap(EngineGroupCallDescription.init))
|
|
return
|
|
} else if let cachedGroupData = self.data?.cachedData as? CachedGroupData {
|
|
requestCall(cachedGroupData.callJoinPeerId, cachedGroupData.activeCall.flatMap(EngineGroupCallDescription.init))
|
|
return
|
|
}
|
|
|
|
guard let peer = self.data?.peer as? TelegramUser, let cachedUserData = self.data?.cachedData as? CachedUserData else {
|
|
return
|
|
}
|
|
if cachedUserData.callsPrivate {
|
|
self.controller?.push(self.context.sharedContext.makeSendInviteLinkScreen(context: self.context, subject: .groupCall(.create), peers: [TelegramForbiddenInvitePeer(
|
|
peer: EnginePeer(peer),
|
|
canInviteWithPremium: false,
|
|
premiumRequiredToContact: false
|
|
)], theme: self.presentationData.theme))
|
|
return
|
|
}
|
|
|
|
self.context.requestCall(peerId: peer.id, isVideo: isVideo, completion: {})
|
|
}
|
|
|
|
func scheduleGroupCall() {
|
|
guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else {
|
|
return
|
|
}
|
|
self.context.scheduleGroupCall(peerId: self.peerId, parentController: controller)
|
|
}
|
|
|
|
func createExternalStream(credentialsPromise: Promise<GroupCallStreamCredentials>?) {
|
|
guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else {
|
|
return
|
|
}
|
|
self.controller?.push(CreateExternalMediaStreamScreen(context: self.context, peerId: self.peerId, credentialsPromise: credentialsPromise, mode: .create(liveStream: false)))
|
|
}
|
|
|
|
func createAndJoinGroupCall(peerId: PeerId, joinAsPeerId: PeerId?) {
|
|
guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else {
|
|
return
|
|
}
|
|
if let _ = self.context.sharedContext.callManager {
|
|
let startCall: (Bool) -> Void = { [weak self] endCurrentIfAny in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
|
|
var cancelImpl: (() -> Void)?
|
|
let presentationData = strongSelf.presentationData
|
|
let progressSignal = Signal<Never, NoError> { [weak self] subscriber in
|
|
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
|
cancelImpl?()
|
|
}))
|
|
self?.controller?.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()
|
|
let createSignal = strongSelf.context.engine.calls.createGroupCall(peerId: peerId, title: nil, scheduleDate: nil, isExternalStream: false)
|
|
|> afterDisposed {
|
|
Queue.mainQueue().async {
|
|
progressDisposable.dispose()
|
|
}
|
|
}
|
|
cancelImpl = { [weak self] in
|
|
self?.activeActionDisposable.set(nil)
|
|
}
|
|
strongSelf.activeActionDisposable.set((createSignal
|
|
|> deliverOnMainQueue).start(next: { [weak self] info in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
strongSelf.context.joinGroupCall(peerId: peerId, invite: nil, requestJoinAsPeerId: { result in
|
|
result(joinAsPeerId)
|
|
}, activeCall: EngineGroupCallDescription(id: info.id, accessHash: info.accessHash, title: info.title, scheduleTimestamp: nil, subscribedToScheduled: false, isStream: info.isStream))
|
|
}, error: { [weak self] error in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil, nil)
|
|
|
|
let text: String
|
|
switch error {
|
|
case .generic, .scheduledTooLate:
|
|
text = strongSelf.presentationData.strings.Login_UnknownError
|
|
case .anonymousNotAllowed:
|
|
if let channel = strongSelf.data?.peer as? TelegramChannel, case .broadcast = channel.info {
|
|
text = strongSelf.presentationData.strings.LiveStream_AnonymousDisabledAlertText
|
|
} else {
|
|
text = strongSelf.presentationData.strings.VoiceChat_AnonymousDisabledAlertText
|
|
}
|
|
}
|
|
strongSelf.controller?.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
|
}))
|
|
}
|
|
|
|
startCall(true)
|
|
}
|
|
}
|
|
|
|
func openVoiceChatDisplayAsPeerSelection(completion: @escaping (PeerId) -> Void, gesture: ContextGesture? = nil, contextController: ContextControllerProtocol? = nil, result: ((ContextMenuActionResult) -> Void)? = nil, backAction: ((ContextControllerProtocol) -> Void)? = nil) {
|
|
let dismissOnSelection = contextController == nil
|
|
let currentAccountPeer = self.context.account.postbox.loadedPeerWithId(context.account.peerId)
|
|
|> map { peer in
|
|
return [FoundPeer(peer: peer, subscribers: nil)]
|
|
}
|
|
let _ = (combineLatest(queue: Queue.mainQueue(), currentAccountPeer, self.displayAsPeersPromise.get() |> take(1))
|
|
|> map { currentAccountPeer, availablePeers -> [FoundPeer] in
|
|
var result = currentAccountPeer
|
|
result.append(contentsOf: availablePeers)
|
|
return result
|
|
}).startStandalone(next: { [weak self] peers in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
if peers.count == 1, let peer = peers.first {
|
|
result?(.dismissWithoutContent)
|
|
completion(peer.peer.id)
|
|
} else {
|
|
var items: [ContextMenuItem] = []
|
|
|
|
var isGroup = false
|
|
for peer in peers {
|
|
if peer.peer is TelegramGroup {
|
|
isGroup = true
|
|
break
|
|
} else if let peer = peer.peer as? TelegramChannel, case .group = peer.info {
|
|
isGroup = true
|
|
break
|
|
}
|
|
}
|
|
|
|
items.append(.custom(VoiceChatInfoContextItem(text: isGroup ? strongSelf.presentationData.strings.VoiceChat_DisplayAsInfoGroup : strongSelf.presentationData.strings.VoiceChat_DisplayAsInfo, icon: { theme in
|
|
return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Accounts"), color: theme.actionSheet.primaryTextColor)
|
|
}), true))
|
|
|
|
for peer in peers {
|
|
var subtitle: String?
|
|
if peer.peer.id.namespace == Namespaces.Peer.CloudUser {
|
|
subtitle = strongSelf.presentationData.strings.VoiceChat_PersonalAccount
|
|
} else if let subscribers = peer.subscribers {
|
|
if let peer = peer.peer as? TelegramChannel, case .broadcast = peer.info {
|
|
subtitle = strongSelf.presentationData.strings.Conversation_StatusSubscribers(subscribers)
|
|
} else {
|
|
subtitle = strongSelf.presentationData.strings.Conversation_StatusMembers(subscribers)
|
|
}
|
|
}
|
|
|
|
let avatarSize = CGSize(width: 28.0, height: 28.0)
|
|
let avatarSignal = peerAvatarCompleteImage(account: strongSelf.context.account, peer: EnginePeer(peer.peer), size: avatarSize)
|
|
items.append(.action(ContextMenuActionItem(text: EnginePeer(peer.peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: avatarSignal), action: { _, f in
|
|
if dismissOnSelection {
|
|
f(.dismissWithoutContent)
|
|
}
|
|
completion(peer.peer.id)
|
|
})))
|
|
|
|
if peer.peer.id.namespace == Namespaces.Peer.CloudUser {
|
|
items.append(.separator)
|
|
}
|
|
}
|
|
if backAction != nil {
|
|
items.append(.separator)
|
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Back, icon: { theme in
|
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor)
|
|
}, iconPosition: .left, action: { (c, _) in
|
|
if let c, let backAction = backAction {
|
|
backAction(c)
|
|
}
|
|
})))
|
|
}
|
|
|
|
if let contextController = contextController {
|
|
contextController.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil, animated: true)
|
|
} else {
|
|
strongSelf.state = strongSelf.state.withHighlightedButton(.voiceChat)
|
|
if let (layout, navigationHeight) = strongSelf.validLayout {
|
|
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
|
|
}
|
|
|
|
if let sourceNode = strongSelf.headerNode.buttonNodes[.voiceChat]?.referenceNode, let controller = strongSelf.controller {
|
|
let contextController = makeContextController(presentationData: strongSelf.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
|
contextController.dismissed = { [weak self] in
|
|
if let strongSelf = self {
|
|
strongSelf.state = strongSelf.state.withHighlightedButton(nil)
|
|
if let (layout, navigationHeight) = strongSelf.validLayout {
|
|
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
|
|
}
|
|
}
|
|
}
|
|
controller.presentInGlobalOverlay(contextController)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func openVoiceChatOptions(defaultJoinAsPeerId: PeerId?, gesture: ContextGesture? = nil, contextController: ContextControllerProtocol? = nil) {
|
|
guard let chatPeer = self.data?.peer else {
|
|
return
|
|
}
|
|
let context = self.context
|
|
let peerId = self.peerId
|
|
let defaultJoinAsPeerId = defaultJoinAsPeerId ?? self.context.account.peerId
|
|
let currentAccountPeer = self.context.account.postbox.loadedPeerWithId(self.context.account.peerId)
|
|
|> map { peer in
|
|
return [FoundPeer(peer: peer, subscribers: nil)]
|
|
}
|
|
let _ = (combineLatest(queue: Queue.mainQueue(), currentAccountPeer, self.displayAsPeersPromise.get() |> take(1))
|
|
|> map { currentAccountPeer, availablePeers -> [FoundPeer] in
|
|
var result = currentAccountPeer
|
|
result.append(contentsOf: availablePeers)
|
|
return result
|
|
}).startStandalone(next: { [weak self] peers in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
|
|
var items: [ContextMenuItem] = []
|
|
|
|
if peers.count > 1 {
|
|
var selectedPeer: FoundPeer?
|
|
for peer in peers {
|
|
if peer.peer.id == defaultJoinAsPeerId {
|
|
selectedPeer = peer
|
|
}
|
|
}
|
|
if let peer = selectedPeer {
|
|
let avatarSize = CGSize(width: 28.0, height: 28.0)
|
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_DisplayAs, textLayout: .secondLineWithValue(EnginePeer(peer.peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: strongSelf.context.account, peer: EnginePeer(peer.peer), size: avatarSize)), action: { c, f in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
|
|
strongSelf.openVoiceChatDisplayAsPeerSelection(completion: { joinAsPeerId in
|
|
let _ = context.engine.calls.updateGroupCallJoinAsPeer(peerId: peerId, joinAs: joinAsPeerId).startStandalone()
|
|
self?.openVoiceChatOptions(defaultJoinAsPeerId: joinAsPeerId, gesture: nil, contextController: c)
|
|
}, gesture: gesture, contextController: c, result: f, backAction: { [weak self] c in
|
|
self?.openVoiceChatOptions(defaultJoinAsPeerId: defaultJoinAsPeerId, gesture: nil, contextController: c)
|
|
})
|
|
|
|
})))
|
|
items.append(.separator)
|
|
}
|
|
}
|
|
|
|
let createVoiceChatTitle: String
|
|
let scheduleVoiceChatTitle: String
|
|
if let channel = strongSelf.data?.peer as? TelegramChannel, case .broadcast = channel.info {
|
|
createVoiceChatTitle = strongSelf.presentationData.strings.ChannelInfo_CreateLiveStream
|
|
scheduleVoiceChatTitle = strongSelf.presentationData.strings.ChannelInfo_ScheduleLiveStream
|
|
} else {
|
|
createVoiceChatTitle = strongSelf.presentationData.strings.ChannelInfo_CreateVoiceChat
|
|
scheduleVoiceChatTitle = strongSelf.presentationData.strings.ChannelInfo_ScheduleVoiceChat
|
|
}
|
|
|
|
items.append(.action(ContextMenuActionItem(text: createVoiceChatTitle, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VoiceChat"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
|
f(.dismissWithoutContent)
|
|
|
|
self?.createAndJoinGroupCall(peerId: peerId, joinAsPeerId: defaultJoinAsPeerId)
|
|
})))
|
|
|
|
items.append(.action(ContextMenuActionItem(text: scheduleVoiceChatTitle, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Schedule"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
|
f(.dismissWithoutContent)
|
|
|
|
self?.scheduleGroupCall()
|
|
})))
|
|
|
|
var credentialsPromise: Promise<GroupCallStreamCredentials>?
|
|
var canCreateStream = false
|
|
switch chatPeer {
|
|
case let group as TelegramGroup:
|
|
if case .creator = group.role {
|
|
canCreateStream = true
|
|
}
|
|
case let channel as TelegramChannel:
|
|
if channel.hasPermission(.manageCalls) {
|
|
canCreateStream = true
|
|
credentialsPromise = Promise()
|
|
credentialsPromise?.set(context.engine.calls.getGroupCallStreamCredentials(peerId: peerId, isLiveStream: false, revokePreviousCredentials: false) |> `catch` { _ -> Signal<GroupCallStreamCredentials, NoError> in return .never() })
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
|
|
if canCreateStream {
|
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChannelInfo_CreateExternalStream, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VoiceChat"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
|
f(.dismissWithoutContent)
|
|
|
|
self?.createExternalStream(credentialsPromise: credentialsPromise)
|
|
})))
|
|
}
|
|
|
|
if let contextController = contextController {
|
|
contextController.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil, animated: true)
|
|
} else {
|
|
strongSelf.state = strongSelf.state.withHighlightedButton(.voiceChat)
|
|
if let (layout, navigationHeight) = strongSelf.validLayout {
|
|
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
|
|
}
|
|
|
|
if let sourceNode = strongSelf.headerNode.buttonNodes[.voiceChat]?.referenceNode, let controller = strongSelf.controller {
|
|
let contextController = makeContextController(presentationData: strongSelf.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
|
contextController.dismissed = { [weak self] in
|
|
if let strongSelf = self {
|
|
strongSelf.state = strongSelf.state.withHighlightedButton(nil)
|
|
if let (layout, navigationHeight) = strongSelf.validLayout {
|
|
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
|
|
}
|
|
}
|
|
}
|
|
controller.presentInGlobalOverlay(contextController)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|