mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-01 07:57:01 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
b78817526e
@ -11,25 +11,26 @@ def is_apple_silicon():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_clean_env():
|
def get_clean_env(use_clean_env=True):
|
||||||
clean_env = os.environ.copy()
|
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
|
return clean_env
|
||||||
|
|
||||||
|
|
||||||
def resolve_executable(program):
|
def resolve_executable(program, use_clean_env=True):
|
||||||
def is_executable(fpath):
|
def is_executable(fpath):
|
||||||
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
|
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)
|
executable_file = os.path.join(path, program)
|
||||||
if is_executable(executable_file):
|
if is_executable(executable_file):
|
||||||
return executable_file
|
return executable_file
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def run_executable_with_output(path, arguments, decode=True, input=None, stderr_to_stdout=True, print_command=False, check_result=False):
|
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)
|
executable_path = resolve_executable(path, use_clean_env=use_clean_env)
|
||||||
if executable_path is None:
|
if executable_path is None:
|
||||||
raise Exception('Could not resolve {} to a valid executable file'.format(path))
|
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,
|
stdout=subprocess.PIPE,
|
||||||
stderr=stderr_assignment,
|
stderr=stderr_assignment,
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
env=get_clean_env()
|
env=get_clean_env(use_clean_env=use_clean_env)
|
||||||
)
|
)
|
||||||
if input is not None:
|
if input is not None:
|
||||||
output_data, _ = process.communicate(input=input)
|
output_data, _ = process.communicate(input=input)
|
||||||
|
@ -2,8 +2,9 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
from BuildEnvironment import check_run_system
|
from BuildEnvironment import run_executable_with_output
|
||||||
|
|
||||||
def deploy_to_firebase(args):
|
def deploy_to_firebase(args):
|
||||||
if not os.path.exists(args.configuration):
|
if not os.path.exists(args.configuration):
|
||||||
@ -26,18 +27,26 @@ def deploy_to_firebase(args):
|
|||||||
if key not in configuration_dict:
|
if key not in configuration_dict:
|
||||||
print('Configuration at {} does not contain {}'.format(args.configuration, key))
|
print('Configuration at {} does not contain {}'.format(args.configuration, key))
|
||||||
sys.exit(1)
|
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__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser(prog='deploy-firebase')
|
parser = argparse.ArgumentParser(prog='deploy-firebase')
|
||||||
|
@ -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,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
|
let controller = InviteLinkInviteController(
|
||||||
guard let self else {
|
context: self.context,
|
||||||
return
|
updatedPresentationData: nil,
|
||||||
}
|
mode: .groupCall(InviteLinkInviteController.Mode.GroupCall(callId: call.callInfo.id, accessHash: call.callInfo.accessHash, isRecentlyCreated: true, canRevoke: true)),
|
||||||
if let result {
|
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),
|
||||||
switch result {
|
parentNavigationController: self.navigationController as? NavigationController,
|
||||||
case .linkCopied:
|
completed: { [weak self] result in
|
||||||
//TODO:localize
|
guard let self else {
|
||||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
return
|
||||||
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 {
|
if let result {
|
||||||
openCall()
|
switch result {
|
||||||
}
|
case .linkCopied:
|
||||||
return false
|
//TODO:localize
|
||||||
}), in: .window(.root))
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
case .openCall:
|
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
|
||||||
openCall()
|
if case .undo = action {
|
||||||
|
openCall()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}), in: .window(.root))
|
||||||
|
case .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
|
||||||
@ -519,10 +519,7 @@ final class CallListControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
let canCreateGroupCall = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App())
|
let canCreateGroupCall = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App())
|
||||||
|> map { configuration -> Bool in
|
|> map { configuration -> Bool in
|
||||||
var isConferencePossible = false
|
var isConferencePossible = true
|
||||||
if context.sharedContext.immediateExperimentalUISettings.conferenceDebug {
|
|
||||||
isConferencePossible = true
|
|
||||||
}
|
|
||||||
if let data = configuration.data, let value = data["ios_enable_conference"] as? Double {
|
if let data = configuration.data, let value = data["ios_enable_conference"] as? Double {
|
||||||
isConferencePossible = value != 0.0
|
isConferencePossible = value != 0.0
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,6 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
case enableReactionOverrides(Bool)
|
case enableReactionOverrides(Bool)
|
||||||
case compressedEmojiCache(Bool)
|
case compressedEmojiCache(Bool)
|
||||||
case storiesJpegExperiment(Bool)
|
case storiesJpegExperiment(Bool)
|
||||||
case conferenceDebug(Bool)
|
|
||||||
case checkSerializedData(Bool)
|
case checkSerializedData(Bool)
|
||||||
case enableQuickReactionSwitch(Bool)
|
case enableQuickReactionSwitch(Bool)
|
||||||
case disableReloginTokens(Bool)
|
case disableReloginTokens(Bool)
|
||||||
@ -134,7 +133,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return DebugControllerSection.web.rawValue
|
return DebugControllerSection.web.rawValue
|
||||||
case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
|
case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
|
||||||
return DebugControllerSection.experiments.rawValue
|
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
|
return DebugControllerSection.experiments.rawValue
|
||||||
case .logTranslationRecognition, .resetTranslationStates:
|
case .logTranslationRecognition, .resetTranslationStates:
|
||||||
return DebugControllerSection.translation.rawValue
|
return DebugControllerSection.translation.rawValue
|
||||||
@ -243,8 +242,6 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return 47
|
return 47
|
||||||
case .disableReloginTokens:
|
case .disableReloginTokens:
|
||||||
return 48
|
return 48
|
||||||
case .conferenceDebug:
|
|
||||||
return 49
|
|
||||||
case .checkSerializedData:
|
case .checkSerializedData:
|
||||||
return 50
|
return 50
|
||||||
case .enableQuickReactionSwitch:
|
case .enableQuickReactionSwitch:
|
||||||
@ -1311,16 +1308,6 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
})
|
})
|
||||||
}).start()
|
}).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):
|
case let .checkSerializedData(value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, title: "Check Serialized Data", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
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
|
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||||
@ -1552,7 +1539,6 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
|||||||
entries.append(.storiesJpegExperiment(experimentalSettings.storiesJpegExperiment))
|
entries.append(.storiesJpegExperiment(experimentalSettings.storiesJpegExperiment))
|
||||||
entries.append(.disableReloginTokens(experimentalSettings.disableReloginTokens))
|
entries.append(.disableReloginTokens(experimentalSettings.disableReloginTokens))
|
||||||
|
|
||||||
entries.append(.conferenceDebug(experimentalSettings.conferenceDebug))
|
|
||||||
entries.append(.checkSerializedData(experimentalSettings.checkSerializedData))
|
entries.append(.checkSerializedData(experimentalSettings.checkSerializedData))
|
||||||
entries.append(.enableQuickReactionSwitch(!experimentalSettings.disableQuickReaction))
|
entries.append(.enableQuickReactionSwitch(!experimentalSettings.disableQuickReaction))
|
||||||
entries.append(.liveStreamV2(experimentalSettings.liveStreamV2))
|
entries.append(.liveStreamV2(experimentalSettings.liveStreamV2))
|
||||||
|
@ -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,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
|
||||||
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
|
f(.dismissWithoutContent)
|
||||||
f(.dismissWithoutContent)
|
|
||||||
|
guard let self else {
|
||||||
guard let self else {
|
return
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
if let invite {
|
||||||
if let invite = 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
|
||||||
|
@ -167,10 +167,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
self.conferenceAddParticipant?()
|
self.conferenceAddParticipant?()
|
||||||
}
|
}
|
||||||
|
|
||||||
var isConferencePossible = false
|
var isConferencePossible = true
|
||||||
if self.call.context.sharedContext.immediateExperimentalUISettings.conferenceDebug {
|
|
||||||
isConferencePossible = true
|
|
||||||
}
|
|
||||||
if let data = self.call.context.currentAppConfiguration.with({ $0 }).data, let value = data["ios_enable_conference"] as? Double {
|
if let data = self.call.context.currentAppConfiguration.with({ $0 }).data, let value = data["ios_enable_conference"] as? Double {
|
||||||
isConferencePossible = value != 0.0
|
isConferencePossible = value != 0.0
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
@ -2988,6 +2997,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
|
|
||||||
result.append(OngoingGroupCallContext.MediaChannelDescription(
|
result.append(OngoingGroupCallContext.MediaChannelDescription(
|
||||||
kind: .audio,
|
kind: .audio,
|
||||||
|
peerId: participant.peer.id.id._internalGetInt64Value(),
|
||||||
audioSsrc: audioSsrc,
|
audioSsrc: audioSsrc,
|
||||||
videoDescription: nil
|
videoDescription: nil
|
||||||
))
|
))
|
||||||
@ -2999,6 +3009,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
|
|
||||||
result.append(OngoingGroupCallContext.MediaChannelDescription(
|
result.append(OngoingGroupCallContext.MediaChannelDescription(
|
||||||
kind: .audio,
|
kind: .audio,
|
||||||
|
peerId: participant.peer.id.id._internalGetInt64Value(),
|
||||||
audioSsrc: screencastSsrc,
|
audioSsrc: screencastSsrc,
|
||||||
videoDescription: nil
|
videoDescription: nil
|
||||||
))
|
))
|
||||||
|
@ -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> {
|
||||||
@ -652,6 +653,51 @@ final class VideoChatScreenComponent: Component {
|
|||||||
guard case let .group(groupCall) = self.currentCall else {
|
guard case let .group(groupCall) = self.currentCall else {
|
||||||
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
|
||||||
@ -705,7 +751,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init)
|
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 {
|
guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -93,13 +93,18 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute],
|
|||||||
tags.insert(.webPage)
|
tags.insert(.webPage)
|
||||||
} else if let action = attachment as? TelegramMediaAction {
|
} else if let action = attachment as? TelegramMediaAction {
|
||||||
switch action.action {
|
switch action.action {
|
||||||
case let .phoneCall(_, discardReason, _, _):
|
case let .phoneCall(_, discardReason, _, _):
|
||||||
globalTags.insert(.Calls)
|
globalTags.insert(.Calls)
|
||||||
if incoming, let discardReason = discardReason, case .missed = discardReason {
|
if incoming, let discardReason = discardReason, case .missed = discardReason {
|
||||||
globalTags.insert(.MissedCalls)
|
globalTags.insert(.MissedCalls)
|
||||||
}
|
}
|
||||||
default:
|
case let .conferenceCall(conferenceCall):
|
||||||
break
|
globalTags.insert(.Calls)
|
||||||
|
if incoming, conferenceCall.flags.contains(.isMissed) {
|
||||||
|
globalTags.insert(.MissedCalls)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
} else if let location = attachment as? TelegramMediaMap, location.liveBroadcastingTimeout != nil {
|
} else if let location = attachment as? TelegramMediaMap, location.liveBroadcastingTimeout != nil {
|
||||||
tags.insert(.liveLocation)
|
tags.insert(.liveLocation)
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,10 @@ public extension TelegramEngine {
|
|||||||
public func createConferenceCall() -> Signal<EngineCreatedGroupCall, CreateConferenceCallError> {
|
public func createConferenceCall() -> Signal<EngineCreatedGroupCall, CreateConferenceCallError> {
|
||||||
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(
|
||||||
|
@ -270,11 +270,13 @@ public final class OngoingGroupCallContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public var kind: Kind
|
public var kind: Kind
|
||||||
|
public var peerId: Int64
|
||||||
public var audioSsrc: UInt32
|
public var audioSsrc: UInt32
|
||||||
public var videoDescription: String?
|
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.kind = kind
|
||||||
|
self.peerId = peerId
|
||||||
self.audioSsrc = audioSsrc
|
self.audioSsrc = audioSsrc
|
||||||
self.videoDescription = videoDescription
|
self.videoDescription = videoDescription
|
||||||
}
|
}
|
||||||
@ -575,6 +577,7 @@ public final class OngoingGroupCallContext {
|
|||||||
}
|
}
|
||||||
return OngoingGroupCallMediaChannelDescription(
|
return OngoingGroupCallMediaChannelDescription(
|
||||||
type: mappedType,
|
type: mappedType,
|
||||||
|
peerId: channel.peerId,
|
||||||
audioSsrc: channel.audioSsrc,
|
audioSsrc: channel.audioSsrc,
|
||||||
videoDescription: channel.videoDescription
|
videoDescription: channel.videoDescription
|
||||||
)
|
)
|
||||||
@ -688,6 +691,7 @@ public final class OngoingGroupCallContext {
|
|||||||
}
|
}
|
||||||
return OngoingGroupCallMediaChannelDescription(
|
return OngoingGroupCallMediaChannelDescription(
|
||||||
type: mappedType,
|
type: mappedType,
|
||||||
|
peerId: channel.peerId,
|
||||||
audioSsrc: channel.audioSsrc,
|
audioSsrc: channel.audioSsrc,
|
||||||
videoDescription: channel.videoDescription
|
videoDescription: channel.videoDescription
|
||||||
)
|
)
|
||||||
|
@ -332,10 +332,12 @@ typedef NS_ENUM(int32_t, OngoingGroupCallMediaChannelType) {
|
|||||||
@interface OngoingGroupCallMediaChannelDescription : NSObject
|
@interface OngoingGroupCallMediaChannelDescription : NSObject
|
||||||
|
|
||||||
@property (nonatomic, readonly) OngoingGroupCallMediaChannelType type;
|
@property (nonatomic, readonly) OngoingGroupCallMediaChannelType type;
|
||||||
|
@property (nonatomic, readonly) uint64_t peerId;
|
||||||
@property (nonatomic, readonly) uint32_t audioSsrc;
|
@property (nonatomic, readonly) uint32_t audioSsrc;
|
||||||
@property (nonatomic, strong, readonly) NSString * _Nullable videoDescription;
|
@property (nonatomic, strong, readonly) NSString * _Nullable videoDescription;
|
||||||
|
|
||||||
- (instancetype _Nonnull)initWithType:(OngoingGroupCallMediaChannelType)type
|
- (instancetype _Nonnull)initWithType:(OngoingGroupCallMediaChannelType)type
|
||||||
|
peerId:(int64_t)peerId
|
||||||
audioSsrc:(uint32_t)audioSsrc
|
audioSsrc:(uint32_t)audioSsrc
|
||||||
videoDescription:(NSString * _Nullable)videoDescription;
|
videoDescription:(NSString * _Nullable)videoDescription;
|
||||||
|
|
||||||
|
@ -3029,11 +3029,13 @@ encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, bool))encryp
|
|||||||
@implementation OngoingGroupCallMediaChannelDescription
|
@implementation OngoingGroupCallMediaChannelDescription
|
||||||
|
|
||||||
- (instancetype _Nonnull)initWithType:(OngoingGroupCallMediaChannelType)type
|
- (instancetype _Nonnull)initWithType:(OngoingGroupCallMediaChannelType)type
|
||||||
audioSsrc:(uint32_t)audioSsrc
|
peerId:(int64_t)peerId
|
||||||
videoDescription:(NSString * _Nullable)videoDescription {
|
audioSsrc:(uint32_t)audioSsrc
|
||||||
|
videoDescription:(NSString * _Nullable)videoDescription {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (self != nil) {
|
if (self != nil) {
|
||||||
_type = type;
|
_type = type;
|
||||||
|
_peerId = peerId;
|
||||||
_audioSsrc = audioSsrc;
|
_audioSsrc = audioSsrc;
|
||||||
_videoDescription = videoDescription;
|
_videoDescription = videoDescription;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user