mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Conference calls
This commit is contained in:
parent
cb83bc1b67
commit
2ab4af656b
@ -1203,6 +1203,8 @@ public protocol SharedAccountContext: AnyObject {
|
||||
|
||||
func makeDebugSettingsController(context: AccountContext?) -> ViewController?
|
||||
|
||||
func openCreateGroupCallUI(context: AccountContext, peerIds: [EnginePeer.Id], parentController: ViewController)
|
||||
|
||||
func navigateToCurrentCall()
|
||||
var hasOngoingCall: ValuePromise<Bool> { get }
|
||||
var immediateHasOngoingCall: Bool { get }
|
||||
|
@ -109,6 +109,7 @@ public final class ContactMultiselectionControllerParams {
|
||||
public let reachedLimit: ((Int32) -> Void)?
|
||||
public let openProfile: ((EnginePeer) -> Void)?
|
||||
public let sendMessage: ((EnginePeer) -> Void)?
|
||||
public let initialSelectedPeers: [EnginePeer]
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
@ -125,7 +126,8 @@ public final class ContactMultiselectionControllerParams {
|
||||
limit: Int32? = nil,
|
||||
reachedLimit: ((Int32) -> Void)? = nil,
|
||||
openProfile: ((EnginePeer) -> Void)? = nil,
|
||||
sendMessage: ((EnginePeer) -> Void)? = nil
|
||||
sendMessage: ((EnginePeer) -> Void)? = nil,
|
||||
initialSelectedPeers: [EnginePeer] = []
|
||||
) {
|
||||
self.context = context
|
||||
self.updatedPresentationData = updatedPresentationData
|
||||
@ -142,6 +144,7 @@ public final class ContactMultiselectionControllerParams {
|
||||
self.reachedLimit = reachedLimit
|
||||
self.openProfile = openProfile
|
||||
self.sendMessage = sendMessage
|
||||
self.initialSelectedPeers = initialSelectedPeers
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -667,8 +667,8 @@ public final class CallListController: TelegramBaseController {
|
||||
return
|
||||
}
|
||||
self.peerViewDisposable.set((self.context.account.viewTracker.peerView(peerId)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] view in
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] view in
|
||||
if let strongSelf = self {
|
||||
guard let peer = peerViewMainPeer(view) else {
|
||||
return
|
||||
@ -676,7 +676,6 @@ public final class CallListController: TelegramBaseController {
|
||||
|
||||
if let cachedUserData = view.cachedData as? CachedUserData, cachedUserData.callsPrivate {
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_ConnectionErrorTitle, text: presentationData.strings.Call_PrivacyErrorMessage(EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
return
|
||||
}
|
||||
@ -700,6 +699,7 @@ public final class CallListController: TelegramBaseController {
|
||||
return
|
||||
}
|
||||
if conferenceCall.duration != nil {
|
||||
self.context.sharedContext.openCreateGroupCallUI(context: self.context, peerIds: conferenceCall.otherParticipants, parentController: self)
|
||||
return
|
||||
}
|
||||
|
||||
@ -728,6 +728,18 @@ public final class CallListController: TelegramBaseController {
|
||||
beginWithVideo: conferenceCall.flags.contains(.isVideo),
|
||||
invitePeerIds: []
|
||||
)
|
||||
}, error: { [weak self] error in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
switch error {
|
||||
case .doesNotExist:
|
||||
self.context.sharedContext.openCreateGroupCallUI(context: self.context, peerIds: conferenceCall.otherParticipants, parentController: self)
|
||||
default:
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
//TODO:localize
|
||||
self.present(textAlertController(context: self.context, title: nil, text: "An error occurred", actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -178,17 +178,23 @@ func _internal_joinCallLinkInformation(_ hash: String, account: Account) -> Sign
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_joinCallInvitationInformation(account: Account, messageId: MessageId) -> Signal<JoinCallLinkInformation, JoinLinkInfoError> {
|
||||
public enum JoinCallLinkInfoError {
|
||||
case generic
|
||||
case flood
|
||||
case doesNotExist
|
||||
}
|
||||
|
||||
func _internal_joinCallInvitationInformation(account: Account, messageId: MessageId) -> Signal<JoinCallLinkInformation, JoinCallLinkInfoError> {
|
||||
return _internal_getCurrentGroupCall(account: account, reference: .message(id: messageId))
|
||||
|> mapError { error -> JoinLinkInfoError in
|
||||
|> mapError { error -> JoinCallLinkInfoError in
|
||||
switch error {
|
||||
case .generic:
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
|> mapToSignal { call -> Signal<JoinCallLinkInformation, JoinLinkInfoError> in
|
||||
guard let call = call else {
|
||||
return .fail(.generic)
|
||||
|> mapToSignal { call -> Signal<JoinCallLinkInformation, JoinCallLinkInfoError> in
|
||||
guard let call else {
|
||||
return .fail(.doesNotExist)
|
||||
}
|
||||
var members: [EnginePeer] = []
|
||||
for participant in call.topParticipants {
|
||||
|
@ -842,7 +842,7 @@ public extension TelegramEngine {
|
||||
return _internal_joinCallLinkInformation(hash, account: self.account)
|
||||
}
|
||||
|
||||
public func joinCallInvitationInformation(messageId: EngineMessage.Id) -> Signal<JoinCallLinkInformation, JoinLinkInfoError> {
|
||||
public func joinCallInvitationInformation(messageId: EngineMessage.Id) -> Signal<JoinCallLinkInformation, JoinCallLinkInfoError> {
|
||||
return _internal_joinCallInvitationInformation(account: self.account, messageId: messageId)
|
||||
}
|
||||
|
||||
|
@ -114,7 +114,6 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
var callDuration: Int32?
|
||||
var callSuccessful = true
|
||||
var isVideo = false
|
||||
var hasCallButton = true
|
||||
var updateConferenceTimerEndTimeout: Int32?
|
||||
for media in item.message.media {
|
||||
if let action = media as? TelegramMediaAction, case let .phoneCall(_, discardReason, duration, isVideoValue) = action.action {
|
||||
@ -173,9 +172,7 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
#else
|
||||
missedTimeout = 30
|
||||
#endif
|
||||
if conferenceCall.duration != nil {
|
||||
hasCallButton = false
|
||||
}
|
||||
|
||||
let currentTime = Int32(Date().timeIntervalSince1970)
|
||||
if conferenceCall.flags.contains(.isMissed) {
|
||||
titleString = "Declined Group Call"
|
||||
@ -296,9 +293,7 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
|
||||
boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom
|
||||
|
||||
if hasCallButton {
|
||||
boundingSize.width += 54.0
|
||||
}
|
||||
boundingSize.width += 54.0
|
||||
|
||||
return (boundingSize.width, { boundingWidth in
|
||||
return (boundingSize, { [weak self] animation, _, _ in
|
||||
@ -359,7 +354,6 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
if let buttonImage = buttonImage {
|
||||
strongSelf.buttonNode.setImage(buttonImage, for: [])
|
||||
strongSelf.buttonNode.frame = CGRect(origin: CGPoint(x: boundingWidth - buttonImage.size.width - 8.0, y: 15.0), size: buttonImage.size)
|
||||
strongSelf.buttonNode.isHidden = !hasCallButton
|
||||
}
|
||||
|
||||
if let activeConferenceUpdateTimer = strongSelf.activeConferenceUpdateTimer {
|
||||
@ -411,10 +405,6 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
|
||||
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
if self.buttonNode.isHidden {
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
|
||||
if self.buttonNode.frame.contains(point) {
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
} else if self.bounds.contains(point), let item = self.item {
|
||||
|
@ -2894,6 +2894,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
if conferenceCall.duration != nil {
|
||||
self.context.sharedContext.openCreateGroupCallUI(context: self.context, peerIds: conferenceCall.otherParticipants, parentController: self)
|
||||
return
|
||||
}
|
||||
|
||||
@ -2922,6 +2923,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
beginWithVideo: conferenceCall.flags.contains(.isVideo),
|
||||
invitePeerIds: []
|
||||
)
|
||||
}, error: { [weak self] error in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
switch error {
|
||||
case .doesNotExist:
|
||||
self.context.sharedContext.openCreateGroupCallUI(context: self.context, peerIds: conferenceCall.otherParticipants, parentController: self)
|
||||
default:
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
//TODO:localize
|
||||
self.present(textAlertController(context: self.context, title: nil, text: "An error occurred", actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}
|
||||
})
|
||||
}, longTap: { [weak self] action, params in
|
||||
if let self {
|
||||
|
@ -337,7 +337,12 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
var addedToken: EditableTokenListToken?
|
||||
var removedTokenId: AnyHashable?
|
||||
|
||||
let maxRegularCount: Int32 = strongSelf.limitsConfiguration?.maxGroupMemberCount ?? 200
|
||||
let maxRegularCount: Int32
|
||||
if case .groupCreation(true) = strongSelf.mode {
|
||||
maxRegularCount = strongSelf.context.userLimits.maxConferenceParticipantCount
|
||||
} else {
|
||||
maxRegularCount = strongSelf.limitsConfiguration?.maxGroupMemberCount ?? 200
|
||||
}
|
||||
var displayCountAlert = false
|
||||
|
||||
var selectionState: ContactListNodeGroupSelectionState?
|
||||
@ -429,6 +434,24 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
}
|
||||
}
|
||||
|
||||
if !self.params.initialSelectedPeers.isEmpty {
|
||||
for peer in self.params.initialSelectedPeers {
|
||||
self.contactsNode.openPeer?(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil))
|
||||
}
|
||||
/*if case let .contacts(contactsNode) = self.contactsNode.contentNode {
|
||||
contactsNode.updateSelectionState { state in
|
||||
var updatedState = state ?? ContactListNodeGroupSelectionState()
|
||||
var selectedPeerMap = updatedState.selectedPeerMap
|
||||
for peer in self.params.initialSelectedPeers {
|
||||
updatedState = updatedState.withToggledPeerId(.peer(peer.id))
|
||||
selectedPeerMap[.peer(peer.id)] = .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil)
|
||||
}
|
||||
updatedState = updatedState.withSelectedPeerMap(selectedPeerMap)
|
||||
return updatedState
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
self.contactsNode.openPeerMore = { [weak self] peer, node, gesture in
|
||||
guard let self, case let .peer(peer, _, _) = peer, let node = node as? ContextReferenceContentNode else {
|
||||
return
|
||||
|
@ -80,6 +80,7 @@ import ShareController
|
||||
import AccountFreezeInfoScreen
|
||||
import JoinSubjectScreen
|
||||
import OldChannelsController
|
||||
import InviteLinksUI
|
||||
|
||||
private final class AccountUserInterfaceInUseContext {
|
||||
let subscribers = Bag<(Bool) -> Void>()
|
||||
@ -1904,6 +1905,200 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
return controller
|
||||
}
|
||||
|
||||
public func openCreateGroupCallUI(context: AccountContext, peerIds: [EnginePeer.Id], parentController: ViewController) {
|
||||
let _ = (context.engine.data.get(
|
||||
EngineDataList(peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
||||
)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak parentController] peers in
|
||||
guard let parentController else {
|
||||
return
|
||||
}
|
||||
|
||||
let peers = peers.compactMap({ $0 })
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(
|
||||
context: context,
|
||||
title: presentationData.strings.Calls_NewCall,
|
||||
mode: .groupCreation(isCall: true),
|
||||
options: .single([]),
|
||||
filters: [.excludeSelf],
|
||||
onlyWriteable: true,
|
||||
isGroupInvitation: false,
|
||||
isPeerEnabled: nil,
|
||||
attemptDisabledItemSelection: nil,
|
||||
alwaysEnabled: false,
|
||||
limit: nil,
|
||||
reachedLimit: nil,
|
||||
openProfile: nil,
|
||||
sendMessage: nil,
|
||||
initialSelectedPeers: peers
|
||||
))
|
||||
controller.navigationPresentation = .modal
|
||||
if let navigationController = parentController.navigationController as? NavigationController {
|
||||
navigationController.pushViewController(controller)
|
||||
} else if let navigationController = context.sharedContext.mainWindow?.viewController as? NavigationController {
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
|
||||
let _ = (controller.result
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak controller] result in
|
||||
guard case let .result(rawPeerIds, _) = result else {
|
||||
controller?.dismiss()
|
||||
return
|
||||
}
|
||||
let peerIds = rawPeerIds.compactMap { id -> EnginePeer.Id? in
|
||||
if case let .peer(id) = id {
|
||||
return id
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if peerIds.isEmpty {
|
||||
controller?.dismiss()
|
||||
return
|
||||
}
|
||||
|
||||
if peerIds.count == 1 {
|
||||
//TODO:release isVideo
|
||||
controller?.dismiss()
|
||||
self.performCall(context: context, parentController: parentController, peerId: peerIds[0], isVideo: false, began: {
|
||||
let _ = (context.sharedContext.hasOngoingCall.get()
|
||||
|> filter { $0 }
|
||||
|> timeout(1.0, queue: Queue.mainQueue(), alternate: .single(true))
|
||||
|> delay(0.5, queue: Queue.mainQueue())
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).startStandalone(next: { _ in
|
||||
if let controller, let navigationController = controller.navigationController as? NavigationController {
|
||||
if navigationController.viewControllers.last === controller {
|
||||
let _ = navigationController.popViewController(animated: true)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
self.createGroupCall(context: context, parentController: parentController, peerIds: peerIds, completion: {
|
||||
controller?.dismiss()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
private func performCall(context: AccountContext, parentController: ViewController, peerId: EnginePeer.Id, isVideo: Bool, began: (() -> Void)? = nil) {
|
||||
let _ = (context.account.viewTracker.peerView(peerId)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak parentController] view in
|
||||
guard let parentController else {
|
||||
return
|
||||
}
|
||||
guard let peer = peerViewMainPeer(view) else {
|
||||
return
|
||||
}
|
||||
|
||||
if let cachedUserData = view.cachedData as? CachedUserData, cachedUserData.callsPrivate {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
parentController.present(textAlertController(context: context, title: presentationData.strings.Call_ConnectionErrorTitle, text: presentationData.strings.Call_PrivacyErrorMessage(EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
return
|
||||
}
|
||||
|
||||
context.requestCall(peerId: peerId, isVideo: isVideo, completion: {
|
||||
began?()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
private func createGroupCall(context: AccountContext, parentController: ViewController, peerIds: [EnginePeer.Id], completion: (() -> Void)? = nil) {
|
||||
parentController.view.endEditing(true)
|
||||
|
||||
var cancelImpl: (() -> Void)?
|
||||
var signal = context.engine.calls.createConferenceCall()
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let progressSignal = Signal<Never, NoError> { [weak parentController] subscriber in
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
||||
cancelImpl?()
|
||||
}))
|
||||
parentController?.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
return ActionDisposable { [weak controller] in
|
||||
Queue.mainQueue().async() {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
|> delay(0.3, queue: Queue.mainQueue())
|
||||
let progressDisposable = progressSignal.start()
|
||||
|
||||
signal = signal
|
||||
|> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
progressDisposable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
let disposable = (signal
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak parentController] call in
|
||||
guard let parentController else {
|
||||
return
|
||||
}
|
||||
|
||||
let openCall: () -> Void = {
|
||||
context.sharedContext.callManager?.joinConferenceCall(
|
||||
accountContext: context,
|
||||
initialCall: EngineGroupCallDescription(
|
||||
id: call.callInfo.id,
|
||||
accessHash: call.callInfo.accessHash,
|
||||
title: call.callInfo.title,
|
||||
scheduleTimestamp: nil,
|
||||
subscribedToScheduled: false,
|
||||
isStream: false
|
||||
),
|
||||
reference: .id(id: call.callInfo.id, accessHash: call.callInfo.accessHash),
|
||||
beginWithVideo: false,
|
||||
invitePeerIds: peerIds
|
||||
)
|
||||
completion?()
|
||||
}
|
||||
|
||||
if !peerIds.isEmpty {
|
||||
openCall()
|
||||
} else {
|
||||
let controller = InviteLinkInviteController(
|
||||
context: context,
|
||||
updatedPresentationData: nil,
|
||||
mode: .groupCall(InviteLinkInviteController.Mode.GroupCall(callId: call.callInfo.id, accessHash: call.callInfo.accessHash, isRecentlyCreated: true, canRevoke: true)),
|
||||
initialInvite: .link(link: call.link, title: nil, isPermanent: true, requestApproval: false, isRevoked: false, adminId: context.account.peerId, date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil, requestedCount: nil, pricing: nil),
|
||||
parentNavigationController: parentController.navigationController as? NavigationController,
|
||||
completed: { [weak parentController] result in
|
||||
guard let parentController else {
|
||||
return
|
||||
}
|
||||
if let result {
|
||||
switch result {
|
||||
case .linkCopied:
|
||||
//TODO:localize
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
parentController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: "Call link copied.", customUndoText: "View Call", timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in
|
||||
if case .undo = action {
|
||||
openCall()
|
||||
}
|
||||
return false
|
||||
}), in: .window(.root))
|
||||
case .openCall:
|
||||
openCall()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
parentController.present(controller, in: .window(.root), with: nil)
|
||||
}
|
||||
})
|
||||
|
||||
cancelImpl = {
|
||||
disposable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
public func openExternalUrl(context: AccountContext, urlContext: OpenURLContext, url: String, forceExternal: Bool, presentationData: PresentationData, navigationController: NavigationController?, dismissInput: @escaping () -> Void) {
|
||||
openExternalUrlImpl(context: context, urlContext: urlContext, url: url, forceExternal: forceExternal, presentationData: presentationData, navigationController: navigationController, dismissInput: dismissInput)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user