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
5deca3fac7
commit
1a31d83699
@ -423,6 +423,7 @@ public protocol PresentationGroupCall: AnyObject {
|
|||||||
var internalId: CallSessionInternalId { get }
|
var internalId: CallSessionInternalId { get }
|
||||||
var peerId: EnginePeer.Id? { get }
|
var peerId: EnginePeer.Id? { get }
|
||||||
var callId: Int64? { get }
|
var callId: Int64? { get }
|
||||||
|
var currentReference: InternalGroupCallReference? { get }
|
||||||
|
|
||||||
var hasVideo: Bool { get }
|
var hasVideo: Bool { get }
|
||||||
var hasScreencast: Bool { get }
|
var hasScreencast: Bool { get }
|
||||||
|
@ -138,16 +138,7 @@ class CallListCallItem: ListViewItem {
|
|||||||
|
|
||||||
func selected(listView: ListView) {
|
func selected(listView: ListView) {
|
||||||
listView.clearHighlightAnimated(true)
|
listView.clearHighlightAnimated(true)
|
||||||
var isVideo = false
|
self.interaction.call(self.topMessage)
|
||||||
for media in self.topMessage.media {
|
|
||||||
if let action = media as? TelegramMediaAction {
|
|
||||||
if case let .phoneCall(_, _, _, isVideoValue) = action.action {
|
|
||||||
isVideo = isVideoValue
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.interaction.call(self.topMessage.id.peerId, isVideo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static func mergeType(item: CallListCallItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool) {
|
static func mergeType(item: CallListCallItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool) {
|
||||||
@ -262,15 +253,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
|||||||
guard let item = self?.layoutParams?.0 else {
|
guard let item = self?.layoutParams?.0 else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
var isVideo = false
|
item.interaction.call(item.topMessage)
|
||||||
for media in item.topMessage.media {
|
|
||||||
if let action = media as? TelegramMediaAction {
|
|
||||||
if case let .phoneCall(_, _, _, isVideoValue) = action.action {
|
|
||||||
isVideo = isVideoValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item.interaction.call(item.topMessage.id.peerId, isVideo)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -390,6 +373,9 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
|||||||
var hadDuration = false
|
var hadDuration = false
|
||||||
var callDuration: Int32?
|
var callDuration: Int32?
|
||||||
|
|
||||||
|
var isConference = false
|
||||||
|
var conferenceIsDeclined = false
|
||||||
|
|
||||||
for message in item.messages {
|
for message in item.messages {
|
||||||
inner: for media in message.media {
|
inner: for media in message.media {
|
||||||
if let action = media as? TelegramMediaAction {
|
if let action = media as? TelegramMediaAction {
|
||||||
@ -411,6 +397,36 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
|||||||
} else {
|
} else {
|
||||||
callDuration = nil
|
callDuration = nil
|
||||||
}
|
}
|
||||||
|
} else if case let .conferenceCall(conferenceCall) = action.action {
|
||||||
|
isConference = true
|
||||||
|
|
||||||
|
isVideo = conferenceCall.flags.contains(.isVideo)
|
||||||
|
if message.flags.contains(.Incoming) {
|
||||||
|
hasIncoming = true
|
||||||
|
//TODO:localize
|
||||||
|
let missedTimeout: Int32
|
||||||
|
#if DEBUG
|
||||||
|
missedTimeout = 5
|
||||||
|
#else
|
||||||
|
missedTimeout = 30
|
||||||
|
#endif
|
||||||
|
let currentTime = Int32(Date().timeIntervalSince1970)
|
||||||
|
if conferenceCall.flags.contains(.isMissed) {
|
||||||
|
titleColor = item.presentationData.theme.list.itemDestructiveColor
|
||||||
|
conferenceIsDeclined = true
|
||||||
|
} else if message.timestamp < currentTime - missedTimeout {
|
||||||
|
titleColor = item.presentationData.theme.list.itemDestructiveColor
|
||||||
|
hasMissed = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hasOutgoing = true
|
||||||
|
}
|
||||||
|
if callDuration == nil && !hadDuration {
|
||||||
|
hadDuration = true
|
||||||
|
callDuration = conferenceCall.duration
|
||||||
|
} else {
|
||||||
|
callDuration = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break inner
|
break inner
|
||||||
}
|
}
|
||||||
@ -441,7 +457,18 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
|||||||
titleAttributedString = NSAttributedString(string: channel.title, font: titleFont, textColor: titleColor)
|
titleAttributedString = NSAttributedString(string: channel.title, font: titleFont, textColor: titleColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasMissed {
|
if isConference {
|
||||||
|
//TODO:localize
|
||||||
|
if conferenceIsDeclined {
|
||||||
|
statusAttributedString = NSAttributedString(string: "Declined Group Call", font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
||||||
|
} else if hasMissed {
|
||||||
|
statusAttributedString = NSAttributedString(string: "Missed Group Call", font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
||||||
|
} else {
|
||||||
|
statusAttributedString = NSAttributedString(string: "Group call", font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
statusAccessibilityString = statusAttributedString?.string ?? ""
|
||||||
|
} else if hasMissed {
|
||||||
statusAttributedString = NSAttributedString(string: item.presentationData.strings.Notification_CallMissedShort, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
statusAttributedString = NSAttributedString(string: item.presentationData.strings.Notification_CallMissedShort, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
||||||
statusAccessibilityString = isVideo ? item.presentationData.strings.Call_VoiceOver_VideoCallMissed : item.presentationData.strings.Call_VoiceOver_VoiceCallMissed
|
statusAccessibilityString = isVideo ? item.presentationData.strings.Call_VoiceOver_VideoCallMissed : item.presentationData.strings.Call_VoiceOver_VoiceCallMissed
|
||||||
} else if hasIncoming && hasOutgoing {
|
} else if hasIncoming && hasOutgoing {
|
||||||
|
@ -92,6 +92,7 @@ public final class CallListController: TelegramBaseController {
|
|||||||
|
|
||||||
private let createActionDisposable = MetaDisposable()
|
private let createActionDisposable = MetaDisposable()
|
||||||
private let clearDisposable = MetaDisposable()
|
private let clearDisposable = MetaDisposable()
|
||||||
|
private var createConferenceCallDisposable: Disposable?
|
||||||
|
|
||||||
public init(context: AccountContext, mode: CallListControllerMode) {
|
public init(context: AccountContext, mode: CallListControllerMode) {
|
||||||
self.context = context
|
self.context = context
|
||||||
@ -163,6 +164,7 @@ public final class CallListController: TelegramBaseController {
|
|||||||
self.presentationDataDisposable?.dispose()
|
self.presentationDataDisposable?.dispose()
|
||||||
self.peerViewDisposable.dispose()
|
self.peerViewDisposable.dispose()
|
||||||
self.clearDisposable.dispose()
|
self.clearDisposable.dispose()
|
||||||
|
self.createConferenceCallDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateThemeAndStrings() {
|
private func updateThemeAndStrings() {
|
||||||
@ -210,11 +212,16 @@ public final class CallListController: TelegramBaseController {
|
|||||||
guard !self.presentAccountFrozenInfoIfNeeded() else {
|
guard !self.presentAccountFrozenInfoIfNeeded() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let _ = (self.context.engine.calls.createConferenceCall()
|
if self.createConferenceCallDisposable != nil {
|
||||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] call in
|
return
|
||||||
|
}
|
||||||
|
self.createConferenceCallDisposable = (self.context.engine.calls.createConferenceCall()
|
||||||
|
|> deliverOnMainQueue).startStrict(next: { [weak self] call in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
self.createConferenceCallDisposable?.dispose()
|
||||||
|
self.createConferenceCallDisposable = nil
|
||||||
|
|
||||||
let openCall: () -> Void = { [weak self] in
|
let openCall: () -> Void = { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -235,7 +242,13 @@ public final class CallListController: TelegramBaseController {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let controller = InviteLinkInviteController(context: self.context, updatedPresentationData: nil, mode: .groupCall(link: call.link, isRecentlyCreated: true), parentNavigationController: self.navigationController as? NavigationController, completed: { [weak self] result in
|
let controller = InviteLinkInviteController(
|
||||||
|
context: self.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: self.context.account.peerId, date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil, requestedCount: nil, pricing: nil),
|
||||||
|
parentNavigationController: self.navigationController as? NavigationController,
|
||||||
|
completed: { [weak self] result in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -254,15 +267,26 @@ public final class CallListController: TelegramBaseController {
|
|||||||
openCall()
|
openCall()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
self.present(controller, in: .window(.root), with: nil)
|
self.present(controller, in: .window(.root), with: nil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
self.displayNode = CallListControllerNode(controller: self, context: self.context, mode: self.mode, presentationData: self.presentationData, call: { [weak self] peerId, isVideo in
|
self.displayNode = CallListControllerNode(controller: self, context: self.context, mode: self.mode, presentationData: self.presentationData, call: { [weak self] message in
|
||||||
if let strongSelf = self {
|
guard let self else {
|
||||||
strongSelf.call(peerId, isVideo: isVideo)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for media in message.media {
|
||||||
|
if let action = media as? TelegramMediaAction {
|
||||||
|
if case let .phoneCall(_, _, _, isVideo) = action.action {
|
||||||
|
self.call(message.id.peerId, isVideo: isVideo)
|
||||||
|
} else if case .conferenceCall = action.action {
|
||||||
|
self.openGroupCall(message: message)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, joinGroupCall: { [weak self] peerId, activeCall in
|
}, joinGroupCall: { [weak self] peerId, activeCall in
|
||||||
if let self {
|
if let self {
|
||||||
@ -573,6 +597,48 @@ public final class CallListController: TelegramBaseController {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func openGroupCall(message: EngineMessage) {
|
||||||
|
var action: TelegramMediaAction?
|
||||||
|
for media in message.media {
|
||||||
|
if let media = media as? TelegramMediaAction {
|
||||||
|
action = media
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
guard case let .conferenceCall(conferenceCall) = action?.action else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if conferenceCall.duration != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let currentGroupCallController = self.context.sharedContext as? VoiceChatController, case let .group(groupCall) = currentGroupCallController.call, let currentCallId = groupCall.callId, currentCallId == conferenceCall.callId {
|
||||||
|
self.context.sharedContext.navigateToCurrentCall()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let signal = self.context.engine.peers.joinCallInvitationInformation(messageId: message.id)
|
||||||
|
let _ = (signal
|
||||||
|
|> deliverOnMainQueue).startStandalone(next: { [weak self] resolvedCallLink in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.context.sharedContext.callManager?.joinConferenceCall(
|
||||||
|
accountContext: self.context,
|
||||||
|
initialCall: EngineGroupCallDescription(
|
||||||
|
id: resolvedCallLink.id,
|
||||||
|
accessHash: resolvedCallLink.accessHash,
|
||||||
|
title: nil,
|
||||||
|
scheduleTimestamp: nil,
|
||||||
|
subscribedToScheduled: false,
|
||||||
|
isStream: false
|
||||||
|
),
|
||||||
|
reference: .message(id: message.id),
|
||||||
|
beginWithVideo: conferenceCall.flags.contains(.isVideo)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
override public func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) {
|
override public func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) {
|
||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Calls_StartNewCall, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Calls_StartNewCall, icon: { theme in
|
||||||
|
@ -62,14 +62,14 @@ private extension EngineCallList.Item {
|
|||||||
|
|
||||||
final class CallListNodeInteraction {
|
final class CallListNodeInteraction {
|
||||||
let setMessageIdWithRevealedOptions: (EngineMessage.Id?, EngineMessage.Id?) -> Void
|
let setMessageIdWithRevealedOptions: (EngineMessage.Id?, EngineMessage.Id?) -> Void
|
||||||
let call: (EnginePeer.Id, Bool) -> Void
|
let call: (EngineMessage) -> Void
|
||||||
let openInfo: (EnginePeer.Id, [EngineMessage]) -> Void
|
let openInfo: (EnginePeer.Id, [EngineMessage]) -> Void
|
||||||
let delete: ([EngineMessage.Id]) -> Void
|
let delete: ([EngineMessage.Id]) -> Void
|
||||||
let updateShowCallsTab: (Bool) -> Void
|
let updateShowCallsTab: (Bool) -> Void
|
||||||
let openGroupCall: (EnginePeer.Id) -> Void
|
let openGroupCall: (EnginePeer.Id) -> Void
|
||||||
let createGroupCall: () -> Void
|
let createGroupCall: () -> Void
|
||||||
|
|
||||||
init(setMessageIdWithRevealedOptions: @escaping (EngineMessage.Id?, EngineMessage.Id?) -> Void, call: @escaping (EnginePeer.Id, Bool) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, delete: @escaping ([EngineMessage.Id]) -> Void, updateShowCallsTab: @escaping (Bool) -> Void, openGroupCall: @escaping (EnginePeer.Id) -> Void, createGroupCall: @escaping () -> Void) {
|
init(setMessageIdWithRevealedOptions: @escaping (EngineMessage.Id?, EngineMessage.Id?) -> Void, call: @escaping (EngineMessage) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, delete: @escaping ([EngineMessage.Id]) -> Void, updateShowCallsTab: @escaping (Bool) -> Void, openGroupCall: @escaping (EnginePeer.Id) -> Void, createGroupCall: @escaping () -> Void) {
|
||||||
self.setMessageIdWithRevealedOptions = setMessageIdWithRevealedOptions
|
self.setMessageIdWithRevealedOptions = setMessageIdWithRevealedOptions
|
||||||
self.call = call
|
self.call = call
|
||||||
self.openInfo = openInfo
|
self.openInfo = openInfo
|
||||||
@ -222,7 +222,7 @@ final class CallListControllerNode: ASDisplayNode {
|
|||||||
private let emptyButtonIconNode: ASImageNode
|
private let emptyButtonIconNode: ASImageNode
|
||||||
private let emptyButtonTextNode: ImmediateTextNode
|
private let emptyButtonTextNode: ImmediateTextNode
|
||||||
|
|
||||||
private let call: (EnginePeer.Id, Bool) -> Void
|
private let call: (EngineMessage) -> Void
|
||||||
private let joinGroupCall: (EnginePeer.Id, EngineGroupCallDescription) -> Void
|
private let joinGroupCall: (EnginePeer.Id, EngineGroupCallDescription) -> Void
|
||||||
private let createGroupCall: () -> Void
|
private let createGroupCall: () -> Void
|
||||||
private let openInfo: (EnginePeer.Id, [EngineMessage]) -> Void
|
private let openInfo: (EnginePeer.Id, [EngineMessage]) -> Void
|
||||||
@ -234,7 +234,7 @@ final class CallListControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
private var previousContentOffset: ListViewVisibleContentOffset?
|
private var previousContentOffset: ListViewVisibleContentOffset?
|
||||||
|
|
||||||
init(controller: CallListController, context: AccountContext, mode: CallListControllerMode, presentationData: PresentationData, call: @escaping (EnginePeer.Id, Bool) -> Void, joinGroupCall: @escaping (EnginePeer.Id, EngineGroupCallDescription) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, emptyStateUpdated: @escaping (Bool) -> Void, createGroupCall: @escaping () -> Void) {
|
init(controller: CallListController, context: AccountContext, mode: CallListControllerMode, presentationData: PresentationData, call: @escaping (EngineMessage) -> Void, joinGroupCall: @escaping (EnginePeer.Id, EngineGroupCallDescription) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, emptyStateUpdated: @escaping (Bool) -> Void, createGroupCall: @escaping () -> Void) {
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.context = context
|
self.context = context
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
@ -333,8 +333,8 @@ final class CallListControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, call: { [weak self] peerId, isVideo in
|
}, call: { [weak self] message in
|
||||||
self?.call(peerId, isVideo)
|
self?.call(message)
|
||||||
}, openInfo: { [weak self] peerId, messages in
|
}, openInfo: { [weak self] peerId, messages in
|
||||||
self?.openInfo(peerId, messages)
|
self?.openInfo(peerId, messages)
|
||||||
}, delete: { [weak self] messageIds in
|
}, delete: { [weak self] messageIds in
|
||||||
|
@ -154,14 +154,32 @@ private func preparedTransition(from fromEntries: [InviteLinkInviteEntry], to to
|
|||||||
return InviteLinkInviteTransaction(deletions: deletions, insertions: insertions, updates: updates, isLoading: isLoading)
|
return InviteLinkInviteTransaction(deletions: deletions, insertions: insertions, updates: updates, isLoading: isLoading)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func getBackgroundColor(theme: PresentationTheme) -> UIColor {
|
||||||
|
return theme.actionSheet.opaqueItemBackgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
public final class InviteLinkInviteController: ViewController {
|
public final class InviteLinkInviteController: ViewController {
|
||||||
private var controllerNode: Node {
|
private var controllerNode: Node {
|
||||||
return self.displayNode as! Node
|
return self.displayNode as! Node
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Mode {
|
public enum Mode {
|
||||||
|
public struct GroupCall {
|
||||||
|
public let callId: Int64
|
||||||
|
public let accessHash: Int64
|
||||||
|
public let isRecentlyCreated: Bool
|
||||||
|
public let canRevoke: Bool
|
||||||
|
|
||||||
|
public init(callId: Int64, accessHash: Int64, isRecentlyCreated: Bool, canRevoke: Bool) {
|
||||||
|
self.callId = callId
|
||||||
|
self.accessHash = accessHash
|
||||||
|
self.isRecentlyCreated = isRecentlyCreated
|
||||||
|
self.canRevoke = canRevoke
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case groupOrChannel(peerId: EnginePeer.Id)
|
case groupOrChannel(peerId: EnginePeer.Id)
|
||||||
case groupCall(link: String, isRecentlyCreated: Bool)
|
case groupCall(GroupCall)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum CompletionResult {
|
public enum CompletionResult {
|
||||||
@ -173,6 +191,7 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let mode: Mode
|
private let mode: Mode
|
||||||
|
private let initialInvite: ExportedInvitation?
|
||||||
private weak var parentNavigationController: NavigationController?
|
private weak var parentNavigationController: NavigationController?
|
||||||
|
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
@ -180,9 +199,10 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
|
|
||||||
fileprivate let completed: ((CompletionResult?) -> Void)?
|
fileprivate let completed: ((CompletionResult?) -> Void)?
|
||||||
|
|
||||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: Mode, parentNavigationController: NavigationController?, completed: ((CompletionResult?) -> Void)? = nil) {
|
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: Mode, initialInvite: ExportedInvitation?, parentNavigationController: NavigationController?, completed: ((CompletionResult?) -> Void)? = nil) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
self.initialInvite = initialInvite
|
||||||
self.parentNavigationController = parentNavigationController
|
self.parentNavigationController = parentNavigationController
|
||||||
self.completed = completed
|
self.completed = completed
|
||||||
|
|
||||||
@ -215,7 +235,7 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
self.displayNode = Node(context: self.context, presentationData: self.presentationData, mode: self.mode, controller: self)
|
self.displayNode = Node(context: self.context, presentationData: self.presentationData, mode: self.mode, controller: self, initialInvite: self.initialInvite)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var didAppearOnce: Bool = false
|
private var didAppearOnce: Bool = false
|
||||||
@ -296,7 +316,7 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
|
|
||||||
private var revokeDisposable = MetaDisposable()
|
private var revokeDisposable = MetaDisposable()
|
||||||
|
|
||||||
init(context: AccountContext, presentationData: PresentationData, mode: InviteLinkInviteController.Mode, controller: InviteLinkInviteController) {
|
init(context: AccountContext, presentationData: PresentationData, mode: InviteLinkInviteController.Mode, controller: InviteLinkInviteController, initialInvite: ExportedInvitation?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
|
||||||
@ -319,7 +339,7 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
self.headerNode.clipsToBounds = false
|
self.headerNode.clipsToBounds = false
|
||||||
|
|
||||||
self.headerBackgroundNode = ASDisplayNode()
|
self.headerBackgroundNode = ASDisplayNode()
|
||||||
self.headerBackgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
self.headerBackgroundNode.backgroundColor = getBackgroundColor(theme: self.presentationData.theme)
|
||||||
self.headerBackgroundNode.cornerRadius = 16.0
|
self.headerBackgroundNode.cornerRadius = 16.0
|
||||||
self.headerBackgroundNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
self.headerBackgroundNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||||
|
|
||||||
@ -338,7 +358,7 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
|
|
||||||
self.historyBackgroundContentNode = ASDisplayNode()
|
self.historyBackgroundContentNode = ASDisplayNode()
|
||||||
self.historyBackgroundContentNode.isLayerBacked = true
|
self.historyBackgroundContentNode.isLayerBacked = true
|
||||||
self.historyBackgroundContentNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
self.historyBackgroundContentNode.backgroundColor = getBackgroundColor(theme: self.presentationData.theme)
|
||||||
|
|
||||||
self.historyBackgroundNode.addSubnode(self.historyBackgroundContentNode)
|
self.historyBackgroundNode.addSubnode(self.historyBackgroundContentNode)
|
||||||
|
|
||||||
@ -354,7 +374,7 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
self.backgroundColor = nil
|
self.backgroundColor = nil
|
||||||
self.isOpaque = false
|
self.isOpaque = false
|
||||||
|
|
||||||
let mainInvitePromise = ValuePromise<ExportedInvitation?>(nil)
|
let mainInvitePromise = ValuePromise<ExportedInvitation?>(initialInvite)
|
||||||
|
|
||||||
self.interaction = InviteLinkInviteInteraction(context: context, mainLinkContextAction: { [weak self] invite, node, gesture in
|
self.interaction = InviteLinkInviteInteraction(context: context, mainLinkContextAction: { [weak self] invite, node, gesture in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -363,7 +383,6 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
guard let node = node as? ContextReferenceContentNode else {
|
guard let node = node as? ContextReferenceContentNode else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in
|
||||||
@ -381,7 +400,6 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
|
|
||||||
if case let .groupOrChannel(peerId) = self.mode {
|
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, f in
|
}, action: { [weak self] _, f in
|
||||||
@ -391,7 +409,8 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let invite = invite {
|
if let invite {
|
||||||
|
if case let .groupOrChannel(peerId) = self.mode {
|
||||||
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -404,12 +423,17 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
isGroup = true
|
isGroup = true
|
||||||
}
|
}
|
||||||
let updatedPresentationData = (strongSelf.presentationData, strongSelf.presentationDataPromise.get())
|
let updatedPresentationData = (strongSelf.presentationData, strongSelf.presentationDataPromise.get())
|
||||||
let controller = QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup))
|
let controller = QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, type: isGroup ? .group : .channel))
|
||||||
strongSelf.controller?.present(controller, in: .window(.root))
|
strongSelf.controller?.present(controller, in: .window(.root))
|
||||||
})
|
})
|
||||||
|
} else if case .groupCall = self.mode {
|
||||||
|
let controller = QrCodeScreen(context: context, updatedPresentationData: (self.presentationData, self.presentationDataPromise.get()), subject: .invite(invite: invite, type: .channel))
|
||||||
|
self.controller?.present(controller, in: .window(.root))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
|
|
||||||
|
if case let .groupOrChannel(peerId) = self.mode {
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
|
||||||
}, action: { [ weak self] _, f in
|
}, action: { [ weak self] _, f in
|
||||||
@ -454,7 +478,45 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
self?.controller?.present(controller, in: .window(.root))
|
self?.controller?.present(controller, in: .window(.root))
|
||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
|
} else if case let .groupCall(groupCall) = self.mode, groupCall.canRevoke {
|
||||||
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
|
||||||
|
}, action: { [ weak self] _, f in
|
||||||
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller = ActionSheetController(presentationData: presentationData)
|
||||||
|
let dismissAction: () -> Void = { [weak controller] in
|
||||||
|
controller?.dismissAnimated()
|
||||||
|
}
|
||||||
|
//TODO:localize
|
||||||
|
controller.setItemGroups([
|
||||||
|
ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetTextItem(title: "Revoke Link"),
|
||||||
|
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: { [weak self] in
|
||||||
|
dismissAction()
|
||||||
|
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let inviteLink = invite?.link {
|
||||||
|
let _ = (context.engine.calls.revokeConferenceInviteLink(reference: .id(id: groupCall.callId, accessHash: groupCall.accessHash), link: inviteLink) |> deliverOnMainQueue).start(next: { result in
|
||||||
|
mainInvitePromise.set(.link(link: result.listenerLink, 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))
|
||||||
|
})
|
||||||
|
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
self.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: presentationData.strings.InviteLink_InviteLinkRevoked), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||||
|
])
|
||||||
|
self.controller?.present(controller, in: .window(.root))
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
let contextController = ContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
let contextController = ContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||||
@ -597,15 +659,16 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
strongSelf.enqueueTransition(transition)
|
strongSelf.enqueueTransition(transition)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
case let .groupCall(link, isRecentlyCreated):
|
case let .groupCall(groupCall):
|
||||||
//TODO:release
|
// A workaround to skip the first run of the event cycle
|
||||||
let tempInfo: Signal<Void, NoError> = .single(Void()) |> delay(0.0, queue: .mainQueue())
|
let delayOfZero = Signal<Void, NoError>.single(()) |> delay(0.0, queue: .mainQueue())
|
||||||
|
|
||||||
self.disposable = (combineLatest(queue: .mainQueue(),
|
self.disposable = (combineLatest(queue: .mainQueue(),
|
||||||
self.presentationDataPromise.get(),
|
self.presentationDataPromise.get(),
|
||||||
tempInfo
|
mainInvitePromise.get(),
|
||||||
|
delayOfZero
|
||||||
)
|
)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData, _ in
|
|> deliverOnMainQueue).start(next: { [weak self] presentationData, mainInvite, _ in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -615,9 +678,9 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
let helpText: String = "Anyone on Telegram can join your call by following the link below."
|
let helpText: String = "Anyone on Telegram can join your call by following the link below."
|
||||||
entries.append(.header(title: "Call Link", text: helpText))
|
entries.append(.header(title: "Call Link", text: helpText))
|
||||||
|
|
||||||
let mainInvite: ExportedInvitation = .link(link: link, title: nil, isPermanent: true, requestApproval: false, isRevoked: false, adminId: self.context.account.peerId, date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil, requestedCount: nil, pricing: nil)
|
let mainInvite: ExportedInvitation = .link(link: mainInvite?.link ?? "", title: nil, isPermanent: true, requestApproval: false, isRevoked: false, adminId: self.context.account.peerId, date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil, requestedCount: nil, pricing: nil)
|
||||||
|
|
||||||
entries.append(.mainLink(invitation: mainInvite, isCall: true, isRecentlyCreated: isRecentlyCreated))
|
entries.append(.mainLink(invitation: mainInvite, isCall: true, isRecentlyCreated: groupCall.isRecentlyCreated))
|
||||||
|
|
||||||
let previousEntries = previousEntries.swap(entries)
|
let previousEntries = previousEntries.swap(entries)
|
||||||
|
|
||||||
@ -675,8 +738,8 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.presentationDataPromise.set(.single(presentationData))
|
self.presentationDataPromise.set(.single(presentationData))
|
||||||
|
|
||||||
self.historyBackgroundContentNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
self.historyBackgroundContentNode.backgroundColor = getBackgroundColor(theme: self.presentationData.theme)
|
||||||
self.headerBackgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
self.headerBackgroundNode.backgroundColor = getBackgroundColor(theme: self.presentationData.theme)
|
||||||
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.InviteLink_InviteLink, font: Font.bold(17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor)
|
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.InviteLink_InviteLink, font: Font.bold(17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor)
|
||||||
|
|
||||||
self.doneButtonIconNode.image = generateCloseButtonImage(backgroundColor: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05), foregroundColor: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.4))!
|
self.doneButtonIconNode.image = generateCloseButtonImage(backgroundColor: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05), foregroundColor: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.4))!
|
||||||
|
@ -530,7 +530,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
|
|||||||
} else {
|
} else {
|
||||||
isGroup = true
|
isGroup = true
|
||||||
}
|
}
|
||||||
presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup)), nil)
|
presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, type: isGroup ? .group : .channel)), nil)
|
||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
|
|
||||||
@ -719,7 +719,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
|
|||||||
isGroup = true
|
isGroup = true
|
||||||
}
|
}
|
||||||
Queue.mainQueue().after(0.2) {
|
Queue.mainQueue().after(0.2) {
|
||||||
presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup)), nil)
|
presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, type: isGroup ? .group : .channel)), nil)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
|
@ -755,7 +755,7 @@ public final class InviteLinkViewController: ViewController {
|
|||||||
isGroup = true
|
isGroup = true
|
||||||
}
|
}
|
||||||
let updatedPresentationData = (strongSelf.presentationData, parentController.presentationDataPromise.get())
|
let updatedPresentationData = (strongSelf.presentationData, parentController.presentationDataPromise.get())
|
||||||
strongSelf.controller?.present(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup)), in: .window(.root))
|
strongSelf.controller?.present(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, type: isGroup ? .group : .channel)), in: .window(.root))
|
||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
@ -655,7 +655,7 @@ public func channelMembersController(context: AccountContext, updatedPresentatio
|
|||||||
}, inviteViaLink: {
|
}, inviteViaLink: {
|
||||||
if let controller = getControllerImpl?() {
|
if let controller = getControllerImpl?() {
|
||||||
dismissInputImpl?()
|
dismissInputImpl?()
|
||||||
presentControllerImpl?(InviteLinkInviteController(context: context, updatedPresentationData: updatedPresentationData, mode: .groupOrChannel(peerId: peerId), parentNavigationController: controller.navigationController as? NavigationController), nil)
|
presentControllerImpl?(InviteLinkInviteController(context: context, updatedPresentationData: updatedPresentationData, mode: .groupOrChannel(peerId: peerId), initialInvite: nil, parentNavigationController: controller.navigationController as? NavigationController), nil)
|
||||||
}
|
}
|
||||||
}, updateHideMembers: { value in
|
}, updateHideMembers: { value in
|
||||||
let _ = context.engine.peers.updateChannelMembersHidden(peerId: peerId, value: value).start()
|
let _ = context.engine.peers.updateChannelMembersHidden(peerId: peerId, value: value).start()
|
||||||
|
@ -1609,7 +1609,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
|
|||||||
} else {
|
} else {
|
||||||
isGroup = true
|
isGroup = true
|
||||||
}
|
}
|
||||||
presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup)), nil)
|
presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, type: isGroup ? .group : .channel)), nil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -35,9 +35,15 @@ private func shareQrCode(context: AccountContext, link: String, ecl: String, vie
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class QrCodeScreen: ViewController {
|
public final class QrCodeScreen: ViewController {
|
||||||
|
public enum SubjectType {
|
||||||
|
case group
|
||||||
|
case channel
|
||||||
|
case groupCall
|
||||||
|
}
|
||||||
|
|
||||||
public enum Subject {
|
public enum Subject {
|
||||||
case peer(peer: EnginePeer)
|
case peer(peer: EnginePeer)
|
||||||
case invite(invite: ExportedInvitation, isGroup: Bool)
|
case invite(invite: ExportedInvitation, type: SubjectType)
|
||||||
case chatFolder(slug: String)
|
case chatFolder(slug: String)
|
||||||
|
|
||||||
var link: String {
|
var link: String {
|
||||||
@ -239,9 +245,17 @@ public final class QrCodeScreen: ViewController {
|
|||||||
let title: String
|
let title: String
|
||||||
let text: String
|
let text: String
|
||||||
switch subject {
|
switch subject {
|
||||||
case let .invite(_, isGroup):
|
case let .invite(_, type):
|
||||||
title = self.presentationData.strings.InviteLink_QRCode_Title
|
title = self.presentationData.strings.InviteLink_QRCode_Title
|
||||||
text = isGroup ? self.presentationData.strings.InviteLink_QRCode_Info : self.presentationData.strings.InviteLink_QRCode_InfoChannel
|
switch type {
|
||||||
|
case .group:
|
||||||
|
text = self.presentationData.strings.InviteLink_QRCode_Info
|
||||||
|
case .channel:
|
||||||
|
text = self.presentationData.strings.InviteLink_QRCode_InfoChannel
|
||||||
|
case .groupCall:
|
||||||
|
//TODO:localize
|
||||||
|
text = "Everyone on Telegram can scan this code to join your group call."
|
||||||
|
}
|
||||||
case .chatFolder:
|
case .chatFolder:
|
||||||
title = self.presentationData.strings.InviteLink_QRCodeFolder_Title
|
title = self.presentationData.strings.InviteLink_QRCodeFolder_Title
|
||||||
text = self.presentationData.strings.InviteLink_QRCodeFolder_Text
|
text = self.presentationData.strings.InviteLink_QRCodeFolder_Text
|
||||||
|
@ -161,7 +161,8 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
|
|||||||
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
|
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
|
||||||
isVideoEnabled: state.isVideoEnabled,
|
isVideoEnabled: state.isVideoEnabled,
|
||||||
unmutedVideoLimit: state.unmutedVideoLimit,
|
unmutedVideoLimit: state.unmutedVideoLimit,
|
||||||
isStream: state.isStream
|
isStream: state.isStream,
|
||||||
|
isCreator: state.isCreator
|
||||||
),
|
),
|
||||||
topParticipants: topParticipants,
|
topParticipants: topParticipants,
|
||||||
participantCount: state.totalCount,
|
participantCount: state.totalCount,
|
||||||
@ -888,6 +889,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
private let getDeviceAccessData: () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void)
|
private let getDeviceAccessData: () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void)
|
||||||
|
|
||||||
private(set) var initialCall: (description: EngineGroupCallDescription, reference: InternalGroupCallReference)?
|
private(set) var initialCall: (description: EngineGroupCallDescription, reference: InternalGroupCallReference)?
|
||||||
|
public var currentReference: InternalGroupCallReference?
|
||||||
public let internalId: CallSessionInternalId
|
public let internalId: CallSessionInternalId
|
||||||
public let peerId: EnginePeer.Id?
|
public let peerId: EnginePeer.Id?
|
||||||
private let isChannel: Bool
|
private let isChannel: Bool
|
||||||
@ -1208,6 +1210,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
self.getDeviceAccessData = getDeviceAccessData
|
self.getDeviceAccessData = getDeviceAccessData
|
||||||
|
|
||||||
self.initialCall = initialCall
|
self.initialCall = initialCall
|
||||||
|
self.currentReference = initialCall?.reference
|
||||||
self.callId = initialCall?.description.id
|
self.callId = initialCall?.description.id
|
||||||
|
|
||||||
self.internalId = internalId
|
self.internalId = internalId
|
||||||
@ -1963,7 +1966,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
defaultParticipantsAreMuted: callInfo.defaultParticipantsAreMuted ?? state.defaultParticipantsAreMuted,
|
defaultParticipantsAreMuted: callInfo.defaultParticipantsAreMuted ?? state.defaultParticipantsAreMuted,
|
||||||
isVideoEnabled: callInfo.isVideoEnabled,
|
isVideoEnabled: callInfo.isVideoEnabled,
|
||||||
unmutedVideoLimit: callInfo.unmutedVideoLimit,
|
unmutedVideoLimit: callInfo.unmutedVideoLimit,
|
||||||
isStream: callInfo.isStream
|
isStream: callInfo.isStream,
|
||||||
|
isCreator: callInfo.isCreator
|
||||||
)), audioSessionControl: self.audioSessionControl)
|
)), audioSessionControl: self.audioSessionControl)
|
||||||
} else {
|
} else {
|
||||||
self.summaryInfoState.set(.single(SummaryInfoState(info: GroupCallInfo(
|
self.summaryInfoState.set(.single(SummaryInfoState(info: GroupCallInfo(
|
||||||
@ -1979,7 +1983,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
|
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
|
||||||
isVideoEnabled: state.isVideoEnabled,
|
isVideoEnabled: state.isVideoEnabled,
|
||||||
unmutedVideoLimit: state.unmutedVideoLimit,
|
unmutedVideoLimit: state.unmutedVideoLimit,
|
||||||
isStream: callInfo.isStream
|
isStream: callInfo.isStream,
|
||||||
|
isCreator: callInfo.isCreator
|
||||||
))))
|
))))
|
||||||
|
|
||||||
self.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
self.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
||||||
@ -2301,6 +2306,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.currentReference = .id(id: joinCallResult.callInfo.id, accessHash: joinCallResult.callInfo.accessHash)
|
||||||
|
|
||||||
let clientParams = joinCallResult.jsonParams
|
let clientParams = joinCallResult.jsonParams
|
||||||
if let data = clientParams.data(using: .utf8), let dict = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] {
|
if let data = clientParams.data(using: .utf8), let dict = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] {
|
||||||
if let video = dict["video"] as? [String: Any] {
|
if let video = dict["video"] as? [String: Any] {
|
||||||
@ -2910,7 +2918,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
|
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
|
||||||
isVideoEnabled: state.isVideoEnabled,
|
isVideoEnabled: state.isVideoEnabled,
|
||||||
unmutedVideoLimit: state.unmutedVideoLimit,
|
unmutedVideoLimit: state.unmutedVideoLimit,
|
||||||
isStream: callInfo.isStream
|
isStream: callInfo.isStream,
|
||||||
|
isCreator: callInfo.isCreator
|
||||||
))))
|
))))
|
||||||
|
|
||||||
self.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
self.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
||||||
|
@ -25,6 +25,7 @@ import LegacyComponents
|
|||||||
import TooltipUI
|
import TooltipUI
|
||||||
import BlurredBackgroundComponent
|
import BlurredBackgroundComponent
|
||||||
import CallsEmoji
|
import CallsEmoji
|
||||||
|
import InviteLinksUI
|
||||||
|
|
||||||
extension VideoChatCall {
|
extension VideoChatCall {
|
||||||
var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> {
|
var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> {
|
||||||
@ -653,6 +654,51 @@ final class VideoChatScreenComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if groupCall.isConference {
|
||||||
|
guard let navigationController = self.environment?.controller()?.navigationController as? NavigationController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let currentReference = groupCall.currentReference, case let .id(callId, accessHash) = currentReference else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let callState = self.callState else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||||
|
let controller = InviteLinkInviteController(
|
||||||
|
context: groupCall.accountContext,
|
||||||
|
updatedPresentationData: (initial: presentationData, signal: .single(presentationData)),
|
||||||
|
mode: .groupCall(InviteLinkInviteController.Mode.GroupCall(
|
||||||
|
callId: callId,
|
||||||
|
accessHash: accessHash,
|
||||||
|
isRecentlyCreated: false,
|
||||||
|
canRevoke: callState.canManageCall
|
||||||
|
)),
|
||||||
|
initialInvite: .link(link: inviteLinks.listenerLink, title: nil, isPermanent: true, requestApproval: false, isRevoked: false, adminId: groupCall.accountContext.account.peerId, date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil, requestedCount: nil, pricing: nil),
|
||||||
|
parentNavigationController: navigationController,
|
||||||
|
completed: { [weak self] result in
|
||||||
|
guard let self, case let .group(groupCall) = self.currentCall else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let result {
|
||||||
|
switch result {
|
||||||
|
case .linkCopied:
|
||||||
|
//TODO:localize
|
||||||
|
let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
self.environment?.controller()?.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: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in
|
||||||
|
return false
|
||||||
|
}), in: .current)
|
||||||
|
case .openCall:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.environment?.controller()?.present(controller, in: .window(.root), with: nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let formatSendTitle: (String) -> String = { string in
|
let formatSendTitle: (String) -> String = { string in
|
||||||
var string = string
|
var string = string
|
||||||
if string.contains("[") && string.contains("]") {
|
if string.contains("[") && string.contains("]") {
|
||||||
|
@ -16,31 +16,6 @@ extension VideoChatScreenComponent.View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/*if groupCall.accountContext.sharedContext.immediateExperimentalUISettings.conferenceDebug {
|
|
||||||
guard let navigationController = self.environment?.controller()?.navigationController as? NavigationController else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
|
|
||||||
let controller = InviteLinkInviteController(context: groupCall.accountContext, updatedPresentationData: (initial: presentationData, signal: .single(presentationData)), mode: .groupCall(link: "https://t.me/call/+abbfbffll123", isRecentlyCreated: false), parentNavigationController: navigationController, completed: { [weak self] result in
|
|
||||||
guard let self, case let .group(groupCall) = self.currentCall else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if let result {
|
|
||||||
switch result {
|
|
||||||
case .linkCopied:
|
|
||||||
//TODO:localize
|
|
||||||
let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
self.environment?.controller()?.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: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in
|
|
||||||
return false
|
|
||||||
}), in: .current)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
self.environment?.controller()?.present(controller, in: .window(.root), with: nil)
|
|
||||||
return
|
|
||||||
}*/
|
|
||||||
|
|
||||||
if groupCall.isConference {
|
if groupCall.isConference {
|
||||||
var disablePeerIds: [EnginePeer.Id] = []
|
var disablePeerIds: [EnginePeer.Id] = []
|
||||||
disablePeerIds.append(groupCall.accountContext.account.peerId)
|
disablePeerIds.append(groupCall.accountContext.account.peerId)
|
||||||
|
@ -98,6 +98,11 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute],
|
|||||||
if incoming, let discardReason = discardReason, case .missed = discardReason {
|
if incoming, let discardReason = discardReason, case .missed = discardReason {
|
||||||
globalTags.insert(.MissedCalls)
|
globalTags.insert(.MissedCalls)
|
||||||
}
|
}
|
||||||
|
case let .conferenceCall(conferenceCall):
|
||||||
|
globalTags.insert(.Calls)
|
||||||
|
if incoming, conferenceCall.flags.contains(.isMissed) {
|
||||||
|
globalTags.insert(.MissedCalls)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -118,9 +123,6 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute],
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !incoming {
|
|
||||||
assert(true)
|
|
||||||
}
|
|
||||||
return (tags, globalTags)
|
return (tags, globalTags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +96,7 @@ public struct GroupCallInfo: Equatable {
|
|||||||
public var isVideoEnabled: Bool
|
public var isVideoEnabled: Bool
|
||||||
public var unmutedVideoLimit: Int
|
public var unmutedVideoLimit: Int
|
||||||
public var isStream: Bool
|
public var isStream: Bool
|
||||||
|
public var isCreator: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
id: Int64,
|
id: Int64,
|
||||||
@ -110,7 +111,8 @@ public struct GroupCallInfo: Equatable {
|
|||||||
defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?,
|
defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?,
|
||||||
isVideoEnabled: Bool,
|
isVideoEnabled: Bool,
|
||||||
unmutedVideoLimit: Int,
|
unmutedVideoLimit: Int,
|
||||||
isStream: Bool
|
isStream: Bool,
|
||||||
|
isCreator: Bool
|
||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.accessHash = accessHash
|
self.accessHash = accessHash
|
||||||
@ -125,6 +127,7 @@ public struct GroupCallInfo: Equatable {
|
|||||||
self.isVideoEnabled = isVideoEnabled
|
self.isVideoEnabled = isVideoEnabled
|
||||||
self.unmutedVideoLimit = unmutedVideoLimit
|
self.unmutedVideoLimit = unmutedVideoLimit
|
||||||
self.isStream = isStream
|
self.isStream = isStream
|
||||||
|
self.isCreator = isCreator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +153,8 @@ extension GroupCallInfo {
|
|||||||
defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: (flags & (1 << 1)) != 0, canChange: (flags & (1 << 2)) != 0),
|
defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: (flags & (1 << 1)) != 0, canChange: (flags & (1 << 2)) != 0),
|
||||||
isVideoEnabled: (flags & (1 << 9)) != 0,
|
isVideoEnabled: (flags & (1 << 9)) != 0,
|
||||||
unmutedVideoLimit: Int(unmutedVideoLimit),
|
unmutedVideoLimit: Int(unmutedVideoLimit),
|
||||||
isStream: (flags & (1 << 12)) != 0
|
isStream: (flags & (1 << 12)) != 0,
|
||||||
|
isCreator: (flags & (1 << 15)) != 0
|
||||||
)
|
)
|
||||||
case .groupCallDiscarded:
|
case .groupCallDiscarded:
|
||||||
return nil
|
return nil
|
||||||
@ -454,17 +458,17 @@ public enum GetGroupCallParticipantsError {
|
|||||||
func _internal_getGroupCallParticipants(account: Account, reference: InternalGroupCallReference, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> {
|
func _internal_getGroupCallParticipants(account: Account, reference: InternalGroupCallReference, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> {
|
||||||
let accountPeerId = account.peerId
|
let accountPeerId = account.peerId
|
||||||
|
|
||||||
let sortAscendingValue: Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool, Int, Bool), GetGroupCallParticipantsError>
|
let sortAscendingValue: Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool, Int, Bool, Bool), GetGroupCallParticipantsError>
|
||||||
|
|
||||||
sortAscendingValue = _internal_getCurrentGroupCall(account: account, reference: reference)
|
sortAscendingValue = _internal_getCurrentGroupCall(account: account, reference: reference)
|
||||||
|> mapError { _ -> GetGroupCallParticipantsError in
|
|> mapError { _ -> GetGroupCallParticipantsError in
|
||||||
return .generic
|
return .generic
|
||||||
}
|
}
|
||||||
|> mapToSignal { result -> Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool, Int, Bool), GetGroupCallParticipantsError> in
|
|> mapToSignal { result -> Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool, Int, Bool, Bool), GetGroupCallParticipantsError> in
|
||||||
guard let result = result else {
|
guard let result = result else {
|
||||||
return .fail(.generic)
|
return .fail(.generic)
|
||||||
}
|
}
|
||||||
return .single((sortAscending ?? result.info.sortAscending, result.info.scheduleTimestamp, result.info.subscribedToScheduled, result.info.defaultParticipantsAreMuted, result.info.isVideoEnabled, result.info.unmutedVideoLimit, result.info.isStream))
|
return .single((sortAscending ?? result.info.sortAscending, result.info.scheduleTimestamp, result.info.subscribedToScheduled, result.info.defaultParticipantsAreMuted, result.info.isVideoEnabled, result.info.unmutedVideoLimit, result.info.isStream, result.info.isCreator))
|
||||||
}
|
}
|
||||||
|
|
||||||
return combineLatest(
|
return combineLatest(
|
||||||
@ -481,7 +485,7 @@ func _internal_getGroupCallParticipants(account: Account, reference: InternalGro
|
|||||||
let version: Int32
|
let version: Int32
|
||||||
let nextParticipantsFetchOffset: String?
|
let nextParticipantsFetchOffset: String?
|
||||||
|
|
||||||
let (sortAscendingValue, scheduleTimestamp, subscribedToScheduled, defaultParticipantsAreMuted, isVideoEnabled, unmutedVideoLimit, isStream) = sortAscendingAndScheduleTimestamp
|
let (sortAscendingValue, scheduleTimestamp, subscribedToScheduled, defaultParticipantsAreMuted, isVideoEnabled, unmutedVideoLimit, isStream, isCreator) = sortAscendingAndScheduleTimestamp
|
||||||
|
|
||||||
switch result {
|
switch result {
|
||||||
case let .groupParticipants(count, participants, nextOffset, chats, users, apiVersion):
|
case let .groupParticipants(count, participants, nextOffset, chats, users, apiVersion):
|
||||||
@ -506,7 +510,7 @@ func _internal_getGroupCallParticipants(account: Account, reference: InternalGro
|
|||||||
participants: parsedParticipants,
|
participants: parsedParticipants,
|
||||||
nextParticipantsFetchOffset: nextParticipantsFetchOffset,
|
nextParticipantsFetchOffset: nextParticipantsFetchOffset,
|
||||||
adminIds: Set(),
|
adminIds: Set(),
|
||||||
isCreator: false,
|
isCreator: isCreator,
|
||||||
defaultParticipantsAreMuted: defaultParticipantsAreMuted ?? GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: false, canChange: false),
|
defaultParticipantsAreMuted: defaultParticipantsAreMuted ?? GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: false, canChange: false),
|
||||||
sortAscending: sortAscendingValue,
|
sortAscending: sortAscendingValue,
|
||||||
recordingStartTimestamp: nil,
|
recordingStartTimestamp: nil,
|
||||||
@ -2959,6 +2963,29 @@ func _internal_createConferenceCall(postbox: Postbox, network: Network, accountP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum RevokeConferenceInviteLinkError {
|
||||||
|
case generic
|
||||||
|
}
|
||||||
|
|
||||||
|
func _internal_revokeConferenceInviteLink(account: Account, reference: InternalGroupCallReference, link: String) -> Signal<GroupCallInviteLinks, RevokeConferenceInviteLinkError> {
|
||||||
|
return account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 1, call: reference.apiInputGroupCall, joinMuted: .boolFalse))
|
||||||
|
|> mapError { _ -> RevokeConferenceInviteLinkError in
|
||||||
|
return .generic
|
||||||
|
}
|
||||||
|
|> mapToSignal { result -> Signal<GroupCallInviteLinks, RevokeConferenceInviteLinkError> in
|
||||||
|
account.stateManager.addUpdates(result)
|
||||||
|
|
||||||
|
return _internal_groupCallInviteLinks(account: account, reference: reference, isConference: true)
|
||||||
|
|> castError(RevokeConferenceInviteLinkError.self)
|
||||||
|
|> mapToSignal { result -> Signal<GroupCallInviteLinks, RevokeConferenceInviteLinkError> in
|
||||||
|
guard let result = result else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
return .single(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum ConfirmAddConferenceParticipantError {
|
public enum ConfirmAddConferenceParticipantError {
|
||||||
case generic
|
case generic
|
||||||
}
|
}
|
||||||
|
@ -97,6 +97,10 @@ public extension TelegramEngine {
|
|||||||
return _internal_createConferenceCall(postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId)
|
return _internal_createConferenceCall(postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func revokeConferenceInviteLink(reference: InternalGroupCallReference, link: String) -> Signal<GroupCallInviteLinks, RevokeConferenceInviteLinkError> {
|
||||||
|
return _internal_revokeConferenceInviteLink(account: self.account, reference: reference, link: link)
|
||||||
|
}
|
||||||
|
|
||||||
public func pollConferenceCallBlockchain(reference: InternalGroupCallReference, subChainId: Int, offset: Int, limit: Int) -> Signal<(blocks: [Data], nextOffset: Int)?, NoError> {
|
public func pollConferenceCallBlockchain(reference: InternalGroupCallReference, subChainId: Int, offset: Int, limit: Int) -> Signal<(blocks: [Data], nextOffset: Int)?, NoError> {
|
||||||
return _internal_pollConferenceCallBlockchain(network: self.account.network, reference: reference, subChainId: subChainId, offset: offset, limit: limit)
|
return _internal_pollConferenceCallBlockchain(network: self.account.network, reference: reference, subChainId: subChainId, offset: offset, limit: limit)
|
||||||
}
|
}
|
||||||
|
@ -14124,7 +14124,7 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa
|
|||||||
|
|
||||||
createInviteLinkImpl = { [weak contactsController] in
|
createInviteLinkImpl = { [weak contactsController] in
|
||||||
contactsController?.view.window?.endEditing(true)
|
contactsController?.view.window?.endEditing(true)
|
||||||
contactsController?.present(InviteLinkInviteController(context: context, updatedPresentationData: updatedPresentationData, mode: .groupOrChannel(peerId: groupPeer.id), parentNavigationController: contactsController?.navigationController as? NavigationController), in: .window(.root))
|
contactsController?.present(InviteLinkInviteController(context: context, updatedPresentationData: updatedPresentationData, mode: .groupOrChannel(peerId: groupPeer.id), initialInvite: nil, parentNavigationController: contactsController?.navigationController as? NavigationController), in: .window(.root))
|
||||||
}
|
}
|
||||||
|
|
||||||
parentController?.push(contactsController)
|
parentController?.push(contactsController)
|
||||||
|
@ -402,6 +402,15 @@ func openResolvedUrlImpl(
|
|||||||
members: resolvedCallLink.members,
|
members: resolvedCallLink.members,
|
||||||
totalMemberCount: resolvedCallLink.totalMemberCount
|
totalMemberCount: resolvedCallLink.totalMemberCount
|
||||||
))))
|
))))
|
||||||
|
}, error: { _ in
|
||||||
|
var elevatedLayout = true
|
||||||
|
if case .chat = urlContext {
|
||||||
|
elevatedLayout = false
|
||||||
|
}
|
||||||
|
//TODO:localize
|
||||||
|
present(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: "This link is no longer active"), elevatedLayout: elevatedLayout, animateInAsReplacement: false, action: { _ in
|
||||||
|
return true
|
||||||
|
}), nil)
|
||||||
})
|
})
|
||||||
case let .localization(identifier):
|
case let .localization(identifier):
|
||||||
dismissInput()
|
dismissInput()
|
||||||
@ -788,6 +797,7 @@ func openResolvedUrlImpl(
|
|||||||
}
|
}
|
||||||
if let currentState = starsContext.currentState, currentState.balance >= StarsAmount(value: amount, nanos: 0) {
|
if let currentState = starsContext.currentState, currentState.balance >= StarsAmount(value: amount, nanos: 0) {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
//TODO:localize
|
||||||
let controller = UndoOverlayController(
|
let controller = UndoOverlayController(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
content: .universal(
|
content: .universal(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user