Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Mikhail Filimonov 2025-04-03 08:52:07 +04:00
commit b78817526e
25 changed files with 426 additions and 182 deletions

View File

@ -11,25 +11,26 @@ def is_apple_silicon():
return False
def get_clean_env():
def get_clean_env(use_clean_env=True):
clean_env = os.environ.copy()
clean_env['PATH'] = '/usr/bin:/bin:/usr/sbin:/sbin'
if use_clean_env:
clean_env['PATH'] = '/usr/bin:/bin:/usr/sbin:/sbin'
return clean_env
def resolve_executable(program):
def resolve_executable(program, use_clean_env=True):
def is_executable(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
for path in get_clean_env()["PATH"].split(os.pathsep):
for path in get_clean_env(use_clean_env=use_clean_env)["PATH"].split(os.pathsep):
executable_file = os.path.join(path, program)
if is_executable(executable_file):
return executable_file
return None
def run_executable_with_output(path, arguments, decode=True, input=None, stderr_to_stdout=True, print_command=False, check_result=False):
executable_path = resolve_executable(path)
def run_executable_with_output(path, arguments, use_clean_env=True, decode=True, input=None, stderr_to_stdout=True, print_command=False, check_result=False):
executable_path = resolve_executable(path, use_clean_env=use_clean_env)
if executable_path is None:
raise Exception('Could not resolve {} to a valid executable file'.format(path))
@ -45,7 +46,7 @@ def run_executable_with_output(path, arguments, decode=True, input=None, stderr_
stdout=subprocess.PIPE,
stderr=stderr_assignment,
stdin=subprocess.PIPE,
env=get_clean_env()
env=get_clean_env(use_clean_env=use_clean_env)
)
if input is not None:
output_data, _ = process.communicate(input=input)

View File

@ -2,8 +2,9 @@ import os
import sys
import argparse
import json
import re
from BuildEnvironment import check_run_system
from BuildEnvironment import run_executable_with_output
def deploy_to_firebase(args):
if not os.path.exists(args.configuration):
@ -26,18 +27,26 @@ def deploy_to_firebase(args):
if key not in configuration_dict:
print('Configuration at {} does not contain {}'.format(args.configuration, key))
sys.exit(1)
debug_flag = "--debug" if args.debug else ""
command = 'firebase appdistribution:distribute --app {app_id} --groups "{group}" {debug_flag}'.format(
app_id=configuration_dict['app_id'],
group=configuration_dict['group'],
debug_flag=debug_flag
)
command += ' "{ipa_path}"'.format(ipa_path=args.ipa)
check_run_system(command)
firebase_arguments = [
'appdistribution:distribute',
'--app', configuration_dict['app_id'],
'--groups', configuration_dict['group'],
args.ipa
]
output = run_executable_with_output(
'firebase',
firebase_arguments,
use_clean_env=False,
check_result=True
)
sharing_link_match = re.search(r'Share this release with testers who have access: (https://\S+)', output)
if sharing_link_match:
print(f"Sharing link: {sharing_link_match.group(1)}")
else:
print("No sharing link found in the output.")
if __name__ == '__main__':
parser = argparse.ArgumentParser(prog='deploy-firebase')

View File

@ -423,6 +423,7 @@ public protocol PresentationGroupCall: AnyObject {
var internalId: CallSessionInternalId { get }
var peerId: EnginePeer.Id? { get }
var callId: Int64? { get }
var currentReference: InternalGroupCallReference? { get }
var hasVideo: Bool { get }
var hasScreencast: Bool { get }

View File

@ -138,16 +138,7 @@ class CallListCallItem: ListViewItem {
func selected(listView: ListView) {
listView.clearHighlightAnimated(true)
var isVideo = false
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)
self.interaction.call(self.topMessage)
}
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 {
return false
}
var isVideo = false
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)
item.interaction.call(item.topMessage)
return true
}
}
@ -390,6 +373,9 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
var hadDuration = false
var callDuration: Int32?
var isConference = false
var conferenceIsDeclined = false
for message in item.messages {
inner: for media in message.media {
if let action = media as? TelegramMediaAction {
@ -411,6 +397,36 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
} else {
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
}
@ -441,7 +457,18 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
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)
statusAccessibilityString = isVideo ? item.presentationData.strings.Call_VoiceOver_VideoCallMissed : item.presentationData.strings.Call_VoiceOver_VoiceCallMissed
} else if hasIncoming && hasOutgoing {

View File

@ -92,6 +92,7 @@ public final class CallListController: TelegramBaseController {
private let createActionDisposable = MetaDisposable()
private let clearDisposable = MetaDisposable()
private var createConferenceCallDisposable: Disposable?
public init(context: AccountContext, mode: CallListControllerMode) {
self.context = context
@ -163,6 +164,7 @@ public final class CallListController: TelegramBaseController {
self.presentationDataDisposable?.dispose()
self.peerViewDisposable.dispose()
self.clearDisposable.dispose()
self.createConferenceCallDisposable?.dispose()
}
private func updateThemeAndStrings() {
@ -210,11 +212,16 @@ public final class CallListController: TelegramBaseController {
guard !self.presentAccountFrozenInfoIfNeeded() else {
return
}
let _ = (self.context.engine.calls.createConferenceCall()
|> deliverOnMainQueue).startStandalone(next: { [weak self] call in
if self.createConferenceCallDisposable != nil {
return
}
self.createConferenceCallDisposable = (self.context.engine.calls.createConferenceCall()
|> deliverOnMainQueue).startStrict(next: { [weak self] call in
guard let self else {
return
}
self.createConferenceCallDisposable?.dispose()
self.createConferenceCallDisposable = nil
let openCall: () -> Void = { [weak self] in
guard let self else {
@ -235,34 +242,51 @@ 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
guard let self else {
return
}
if let result {
switch result {
case .linkCopied:
//TODO:localize
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.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()
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 {
return
}
if let result {
switch result {
case .linkCopied:
//TODO:localize
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.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()
}
}
}
})
)
self.present(controller, in: .window(.root), with: nil)
})
}
override public func loadDisplayNode() {
self.displayNode = CallListControllerNode(controller: self, context: self.context, mode: self.mode, presentationData: self.presentationData, call: { [weak self] peerId, isVideo in
if let strongSelf = self {
strongSelf.call(peerId, isVideo: isVideo)
self.displayNode = CallListControllerNode(controller: self, context: self.context, mode: self.mode, presentationData: self.presentationData, call: { [weak self] message in
guard let self else {
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
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) {
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Calls_StartNewCall, icon: { theme in

View File

@ -62,14 +62,14 @@ private extension EngineCallList.Item {
final class CallListNodeInteraction {
let setMessageIdWithRevealedOptions: (EngineMessage.Id?, EngineMessage.Id?) -> Void
let call: (EnginePeer.Id, Bool) -> Void
let call: (EngineMessage) -> Void
let openInfo: (EnginePeer.Id, [EngineMessage]) -> Void
let delete: ([EngineMessage.Id]) -> Void
let updateShowCallsTab: (Bool) -> Void
let openGroupCall: (EnginePeer.Id) -> 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.call = call
self.openInfo = openInfo
@ -222,7 +222,7 @@ final class CallListControllerNode: ASDisplayNode {
private let emptyButtonIconNode: ASImageNode
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 createGroupCall: () -> Void
private let openInfo: (EnginePeer.Id, [EngineMessage]) -> Void
@ -234,7 +234,7 @@ final class CallListControllerNode: ASDisplayNode {
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.context = context
self.mode = mode
@ -333,8 +333,8 @@ final class CallListControllerNode: ASDisplayNode {
}
}
}
}, call: { [weak self] peerId, isVideo in
self?.call(peerId, isVideo)
}, call: { [weak self] message in
self?.call(message)
}, openInfo: { [weak self] peerId, messages in
self?.openInfo(peerId, messages)
}, delete: { [weak self] messageIds in
@ -519,10 +519,7 @@ final class CallListControllerNode: ASDisplayNode {
let canCreateGroupCall = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App())
|> map { configuration -> Bool in
var isConferencePossible = false
if context.sharedContext.immediateExperimentalUISettings.conferenceDebug {
isConferencePossible = true
}
var isConferencePossible = true
if let data = configuration.data, let value = data["ios_enable_conference"] as? Double {
isConferencePossible = value != 0.0
}

View File

@ -100,7 +100,6 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case enableReactionOverrides(Bool)
case compressedEmojiCache(Bool)
case storiesJpegExperiment(Bool)
case conferenceDebug(Bool)
case checkSerializedData(Bool)
case enableQuickReactionSwitch(Bool)
case disableReloginTokens(Bool)
@ -134,7 +133,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.web.rawValue
case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
return DebugControllerSection.experiments.rawValue
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .compressedEmojiCache, .storiesJpegExperiment, .conferenceDebug, .checkSerializedData, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2, .experimentalCallMute, .playerV2, .devRequests, .fakeAds, .enableLocalTranslation:
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .compressedEmojiCache, .storiesJpegExperiment, .checkSerializedData, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2, .experimentalCallMute, .playerV2, .devRequests, .fakeAds, .enableLocalTranslation:
return DebugControllerSection.experiments.rawValue
case .logTranslationRecognition, .resetTranslationStates:
return DebugControllerSection.translation.rawValue
@ -243,8 +242,6 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 47
case .disableReloginTokens:
return 48
case .conferenceDebug:
return 49
case .checkSerializedData:
return 50
case .enableQuickReactionSwitch:
@ -1311,16 +1308,6 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
}).start()
})
case let .conferenceDebug(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Conference Debug", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
settings.conferenceDebug = value
return PreferencesEntry(settings)
})
}).start()
})
case let .checkSerializedData(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Check Serialized Data", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
@ -1552,7 +1539,6 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.storiesJpegExperiment(experimentalSettings.storiesJpegExperiment))
entries.append(.disableReloginTokens(experimentalSettings.disableReloginTokens))
entries.append(.conferenceDebug(experimentalSettings.conferenceDebug))
entries.append(.checkSerializedData(experimentalSettings.checkSerializedData))
entries.append(.enableQuickReactionSwitch(!experimentalSettings.disableQuickReaction))
entries.append(.liveStreamV2(experimentalSettings.liveStreamV2))

View File

@ -154,14 +154,32 @@ private func preparedTransition(from fromEntries: [InviteLinkInviteEntry], to to
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 {
private var controllerNode: Node {
return self.displayNode as! Node
}
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 groupCall(link: String, isRecentlyCreated: Bool)
case groupCall(GroupCall)
}
public enum CompletionResult {
@ -173,6 +191,7 @@ public final class InviteLinkInviteController: ViewController {
private let context: AccountContext
private let mode: Mode
private let initialInvite: ExportedInvitation?
private weak var parentNavigationController: NavigationController?
private var presentationData: PresentationData
@ -180,9 +199,10 @@ public final class InviteLinkInviteController: ViewController {
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.mode = mode
self.initialInvite = initialInvite
self.parentNavigationController = parentNavigationController
self.completed = completed
@ -215,7 +235,7 @@ public final class InviteLinkInviteController: ViewController {
}
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
@ -296,7 +316,7 @@ public final class InviteLinkInviteController: ViewController {
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.mode = mode
@ -319,7 +339,7 @@ public final class InviteLinkInviteController: ViewController {
self.headerNode.clipsToBounds = false
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.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
@ -338,7 +358,7 @@ public final class InviteLinkInviteController: ViewController {
self.historyBackgroundContentNode = ASDisplayNode()
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)
@ -354,7 +374,7 @@ public final class InviteLinkInviteController: ViewController {
self.backgroundColor = nil
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
guard let self else {
@ -363,7 +383,6 @@ public final class InviteLinkInviteController: ViewController {
guard let node = node as? ContextReferenceContentNode else {
return
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in
@ -381,17 +400,17 @@ public final class InviteLinkInviteController: ViewController {
}
})))
if case let .groupOrChannel(peerId) = self.mode {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
guard let self else {
return
}
if let invite = invite {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
guard let self else {
return
}
if let invite {
if case let .groupOrChannel(peerId) = self.mode {
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let strongSelf = self else {
@ -404,12 +423,17 @@ public final class InviteLinkInviteController: ViewController {
isGroup = true
}
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))
})
} 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
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { [ weak self] _, f in
@ -454,7 +478,45 @@ public final class InviteLinkInviteController: ViewController {
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)
@ -597,15 +659,16 @@ public final class InviteLinkInviteController: ViewController {
strongSelf.enqueueTransition(transition)
}
})
case let .groupCall(link, isRecentlyCreated):
//TODO:release
let tempInfo: Signal<Void, NoError> = .single(Void()) |> delay(0.0, queue: .mainQueue())
case let .groupCall(groupCall):
// A workaround to skip the first run of the event cycle
let delayOfZero = Signal<Void, NoError>.single(()) |> delay(0.0, queue: .mainQueue())
self.disposable = (combineLatest(queue: .mainQueue(),
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 {
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."
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)
@ -675,8 +738,8 @@ public final class InviteLinkInviteController: ViewController {
self.presentationData = presentationData
self.presentationDataPromise.set(.single(presentationData))
self.historyBackgroundContentNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.headerBackgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.historyBackgroundContentNode.backgroundColor = getBackgroundColor(theme: self.presentationData.theme)
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.doneButtonIconNode.image = generateCloseButtonImage(backgroundColor: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05), foregroundColor: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.4))!

View File

@ -530,7 +530,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
} else {
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
}
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)
}
})
})))

View File

@ -755,7 +755,7 @@ public final class InviteLinkViewController: ViewController {
isGroup = true
}
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))
})
})))
}

View File

@ -655,7 +655,7 @@ public func channelMembersController(context: AccountContext, updatedPresentatio
}, inviteViaLink: {
if let controller = getControllerImpl?() {
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
let _ = context.engine.peers.updateChannelMembersHidden(peerId: peerId, value: value).start()

View File

@ -1609,7 +1609,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
} else {
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)
})
}
})

View File

@ -35,9 +35,15 @@ private func shareQrCode(context: AccountContext, link: String, ecl: String, vie
}
public final class QrCodeScreen: ViewController {
public enum SubjectType {
case group
case channel
case groupCall
}
public enum Subject {
case peer(peer: EnginePeer)
case invite(invite: ExportedInvitation, isGroup: Bool)
case invite(invite: ExportedInvitation, type: SubjectType)
case chatFolder(slug: String)
var link: String {
@ -239,9 +245,17 @@ public final class QrCodeScreen: ViewController {
let title: String
let text: String
switch subject {
case let .invite(_, isGroup):
case let .invite(_, type):
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:
title = self.presentationData.strings.InviteLink_QRCodeFolder_Title
text = self.presentationData.strings.InviteLink_QRCodeFolder_Text

View File

@ -167,10 +167,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
self.conferenceAddParticipant?()
}
var isConferencePossible = false
if self.call.context.sharedContext.immediateExperimentalUISettings.conferenceDebug {
isConferencePossible = true
}
var isConferencePossible = true
if let data = self.call.context.currentAppConfiguration.with({ $0 }).data, let value = data["ios_enable_conference"] as? Double {
isConferencePossible = value != 0.0
}

View File

@ -161,7 +161,8 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
isVideoEnabled: state.isVideoEnabled,
unmutedVideoLimit: state.unmutedVideoLimit,
isStream: state.isStream
isStream: state.isStream,
isCreator: state.isCreator
),
topParticipants: topParticipants,
participantCount: state.totalCount,
@ -888,6 +889,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
private let getDeviceAccessData: () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void)
private(set) var initialCall: (description: EngineGroupCallDescription, reference: InternalGroupCallReference)?
public var currentReference: InternalGroupCallReference?
public let internalId: CallSessionInternalId
public let peerId: EnginePeer.Id?
private let isChannel: Bool
@ -1208,6 +1210,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.getDeviceAccessData = getDeviceAccessData
self.initialCall = initialCall
self.currentReference = initialCall?.reference
self.callId = initialCall?.description.id
self.internalId = internalId
@ -1963,7 +1966,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
defaultParticipantsAreMuted: callInfo.defaultParticipantsAreMuted ?? state.defaultParticipantsAreMuted,
isVideoEnabled: callInfo.isVideoEnabled,
unmutedVideoLimit: callInfo.unmutedVideoLimit,
isStream: callInfo.isStream
isStream: callInfo.isStream,
isCreator: callInfo.isCreator
)), audioSessionControl: self.audioSessionControl)
} else {
self.summaryInfoState.set(.single(SummaryInfoState(info: GroupCallInfo(
@ -1979,7 +1983,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
isVideoEnabled: state.isVideoEnabled,
unmutedVideoLimit: state.unmutedVideoLimit,
isStream: callInfo.isStream
isStream: callInfo.isStream,
isCreator: callInfo.isCreator
))))
self.summaryParticipantsState.set(.single(SummaryParticipantsState(
@ -2301,6 +2306,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
guard let self else {
return
}
self.currentReference = .id(id: joinCallResult.callInfo.id, accessHash: joinCallResult.callInfo.accessHash)
let clientParams = joinCallResult.jsonParams
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] {
@ -2910,7 +2918,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
isVideoEnabled: state.isVideoEnabled,
unmutedVideoLimit: state.unmutedVideoLimit,
isStream: callInfo.isStream
isStream: callInfo.isStream,
isCreator: callInfo.isCreator
))))
self.summaryParticipantsState.set(.single(SummaryParticipantsState(
@ -2988,6 +2997,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
result.append(OngoingGroupCallContext.MediaChannelDescription(
kind: .audio,
peerId: participant.peer.id.id._internalGetInt64Value(),
audioSsrc: audioSsrc,
videoDescription: nil
))
@ -2999,6 +3009,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
result.append(OngoingGroupCallContext.MediaChannelDescription(
kind: .audio,
peerId: participant.peer.id.id._internalGetInt64Value(),
audioSsrc: screencastSsrc,
videoDescription: nil
))

View File

@ -25,6 +25,7 @@ import LegacyComponents
import TooltipUI
import BlurredBackgroundComponent
import CallsEmoji
import InviteLinksUI
extension VideoChatCall {
var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> {
@ -652,6 +653,51 @@ final class VideoChatScreenComponent: Component {
guard case let .group(groupCall) = self.currentCall else {
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
var string = string
@ -705,7 +751,7 @@ final class VideoChatScreenComponent: Component {
peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init)
)
)
|> deliverOnMainQueue).start(next: { [weak self] peerList in
|> deliverOnMainQueue).start(next: { [weak self] peerList in
guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else {
return
}

View File

@ -16,31 +16,6 @@ extension VideoChatScreenComponent.View {
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 {
var disablePeerIds: [EnginePeer.Id] = []
disablePeerIds.append(groupCall.accountContext.account.peerId)

View File

@ -93,13 +93,18 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute],
tags.insert(.webPage)
} else if let action = attachment as? TelegramMediaAction {
switch action.action {
case let .phoneCall(_, discardReason, _, _):
globalTags.insert(.Calls)
if incoming, let discardReason = discardReason, case .missed = discardReason {
globalTags.insert(.MissedCalls)
}
default:
break
case let .phoneCall(_, discardReason, _, _):
globalTags.insert(.Calls)
if incoming, let discardReason = discardReason, case .missed = discardReason {
globalTags.insert(.MissedCalls)
}
case let .conferenceCall(conferenceCall):
globalTags.insert(.Calls)
if incoming, conferenceCall.flags.contains(.isMissed) {
globalTags.insert(.MissedCalls)
}
default:
break
}
} else if let location = attachment as? TelegramMediaMap, location.liveBroadcastingTimeout != nil {
tags.insert(.liveLocation)
@ -118,9 +123,6 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute],
}
}
if !incoming {
assert(true)
}
return (tags, globalTags)
}

View File

@ -96,6 +96,7 @@ public struct GroupCallInfo: Equatable {
public var isVideoEnabled: Bool
public var unmutedVideoLimit: Int
public var isStream: Bool
public var isCreator: Bool
public init(
id: Int64,
@ -110,7 +111,8 @@ public struct GroupCallInfo: Equatable {
defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?,
isVideoEnabled: Bool,
unmutedVideoLimit: Int,
isStream: Bool
isStream: Bool,
isCreator: Bool
) {
self.id = id
self.accessHash = accessHash
@ -125,6 +127,7 @@ public struct GroupCallInfo: Equatable {
self.isVideoEnabled = isVideoEnabled
self.unmutedVideoLimit = unmutedVideoLimit
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),
isVideoEnabled: (flags & (1 << 9)) != 0,
unmutedVideoLimit: Int(unmutedVideoLimit),
isStream: (flags & (1 << 12)) != 0
isStream: (flags & (1 << 12)) != 0,
isCreator: (flags & (1 << 15)) != 0
)
case .groupCallDiscarded:
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> {
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)
|> mapError { _ -> GetGroupCallParticipantsError in
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 {
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(
@ -481,7 +485,7 @@ func _internal_getGroupCallParticipants(account: Account, reference: InternalGro
let version: Int32
let nextParticipantsFetchOffset: String?
let (sortAscendingValue, scheduleTimestamp, subscribedToScheduled, defaultParticipantsAreMuted, isVideoEnabled, unmutedVideoLimit, isStream) = sortAscendingAndScheduleTimestamp
let (sortAscendingValue, scheduleTimestamp, subscribedToScheduled, defaultParticipantsAreMuted, isVideoEnabled, unmutedVideoLimit, isStream, isCreator) = sortAscendingAndScheduleTimestamp
switch result {
case let .groupParticipants(count, participants, nextOffset, chats, users, apiVersion):
@ -506,7 +510,7 @@ func _internal_getGroupCallParticipants(account: Account, reference: InternalGro
participants: parsedParticipants,
nextParticipantsFetchOffset: nextParticipantsFetchOffset,
adminIds: Set(),
isCreator: false,
isCreator: isCreator,
defaultParticipantsAreMuted: defaultParticipantsAreMuted ?? GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: false, canChange: false),
sortAscending: sortAscendingValue,
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 {
case generic
}

View File

@ -96,6 +96,10 @@ public extension TelegramEngine {
public func createConferenceCall() -> Signal<EngineCreatedGroupCall, CreateConferenceCallError> {
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> {
return _internal_pollConferenceCallBlockchain(network: self.account.network, reference: reference, subChainId: subChainId, offset: offset, limit: limit)

View File

@ -14124,7 +14124,7 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa
createInviteLinkImpl = { [weak contactsController] in
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)

View File

@ -402,6 +402,15 @@ func openResolvedUrlImpl(
members: resolvedCallLink.members,
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):
dismissInput()
@ -788,6 +797,7 @@ func openResolvedUrlImpl(
}
if let currentState = starsContext.currentState, currentState.balance >= StarsAmount(value: amount, nanos: 0) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
//TODO:localize
let controller = UndoOverlayController(
presentationData: presentationData,
content: .universal(

View File

@ -270,11 +270,13 @@ public final class OngoingGroupCallContext {
}
public var kind: Kind
public var peerId: Int64
public var audioSsrc: UInt32
public var videoDescription: String?
public init(kind: Kind, audioSsrc: UInt32, videoDescription: String?) {
public init(kind: Kind, peerId: Int64, audioSsrc: UInt32, videoDescription: String?) {
self.kind = kind
self.peerId = peerId
self.audioSsrc = audioSsrc
self.videoDescription = videoDescription
}
@ -575,6 +577,7 @@ public final class OngoingGroupCallContext {
}
return OngoingGroupCallMediaChannelDescription(
type: mappedType,
peerId: channel.peerId,
audioSsrc: channel.audioSsrc,
videoDescription: channel.videoDescription
)
@ -688,6 +691,7 @@ public final class OngoingGroupCallContext {
}
return OngoingGroupCallMediaChannelDescription(
type: mappedType,
peerId: channel.peerId,
audioSsrc: channel.audioSsrc,
videoDescription: channel.videoDescription
)

View File

@ -332,10 +332,12 @@ typedef NS_ENUM(int32_t, OngoingGroupCallMediaChannelType) {
@interface OngoingGroupCallMediaChannelDescription : NSObject
@property (nonatomic, readonly) OngoingGroupCallMediaChannelType type;
@property (nonatomic, readonly) uint64_t peerId;
@property (nonatomic, readonly) uint32_t audioSsrc;
@property (nonatomic, strong, readonly) NSString * _Nullable videoDescription;
- (instancetype _Nonnull)initWithType:(OngoingGroupCallMediaChannelType)type
peerId:(int64_t)peerId
audioSsrc:(uint32_t)audioSsrc
videoDescription:(NSString * _Nullable)videoDescription;

View File

@ -3029,11 +3029,13 @@ encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, bool))encryp
@implementation OngoingGroupCallMediaChannelDescription
- (instancetype _Nonnull)initWithType:(OngoingGroupCallMediaChannelType)type
audioSsrc:(uint32_t)audioSsrc
videoDescription:(NSString * _Nullable)videoDescription {
peerId:(int64_t)peerId
audioSsrc:(uint32_t)audioSsrc
videoDescription:(NSString * _Nullable)videoDescription {
self = [super init];
if (self != nil) {
_type = type;
_peerId = peerId;
_audioSsrc = audioSsrc;
_videoDescription = videoDescription;
}