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

This commit is contained in:
Mikhail Filimonov 2025-04-01 20:01:32 +04:00
commit 0a67b254f8
24 changed files with 652 additions and 136 deletions

View File

@ -22,6 +22,7 @@ internal:
- export PATH=/opt/homebrew/opt/ruby/bin:$PATH
- export PATH=`gem environment gemdir`/bin:$PATH
- python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="build-system/appcenter-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=adhoc --configuration=release_arm64
- python3 -u build-system/Make/DeployToFirebase.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/firebase-configurations/firebase-internal.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip"
- python3 -u build-system/Make/DeployToAppCenter.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/appcenter-configurations/appcenter-internal.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip"
environment:
name: internal

View File

@ -0,0 +1,72 @@
import os
import sys
import argparse
import json
from BuildEnvironment import check_run_system
def deploy_to_firebase(args):
if not os.path.exists(args.configuration):
print('{} does not exist'.format(args.configuration))
sys.exit(1)
if not os.path.exists(args.ipa):
print('{} does not exist'.format(args.ipa))
sys.exit(1)
if args.dsyms is not None and not os.path.exists(args.dsyms):
print('{} does not exist'.format(args.dsyms))
sys.exit(1)
with open(args.configuration) as file:
configuration_dict = json.load(file)
required_keys = [
'app_id',
'group',
]
for key in required_keys:
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)
if __name__ == '__main__':
parser = argparse.ArgumentParser(prog='deploy-firebase')
parser.add_argument(
'--configuration',
required=True,
help='Path to configuration json.'
)
parser.add_argument(
'--ipa',
required=True,
help='Path to IPA.'
)
parser.add_argument(
'--dsyms',
required=False,
help='Path to DSYMs.zip.'
)
parser.add_argument(
'--debug',
action='store_true',
help='Enable debug output for firebase deploy.'
)
if len(sys.argv) < 2:
parser.print_help()
sys.exit(1)
args = parser.parse_args()
deploy_to_firebase(args)

View File

@ -111,6 +111,7 @@ public final class ContactSelectionControllerParams {
public let requirePhoneNumbers: Bool
public let allowChannelsInSearch: Bool
public let confirmation: (ContactListPeer) -> Signal<Bool, NoError>
public let isPeerEnabled: (ContactListPeer) -> Bool
public let openProfile: ((EnginePeer) -> Void)?
public let sendMessage: ((EnginePeer) -> Void)?
@ -127,6 +128,7 @@ public final class ContactSelectionControllerParams {
requirePhoneNumbers: Bool = false,
allowChannelsInSearch: Bool = false,
confirmation: @escaping (ContactListPeer) -> Signal<Bool, NoError> = { _ in .single(true) },
isPeerEnabled: @escaping (ContactListPeer) -> Bool = { _ in true },
openProfile: ((EnginePeer) -> Void)? = nil,
sendMessage: ((EnginePeer) -> Void)? = nil
) {
@ -142,6 +144,7 @@ public final class ContactSelectionControllerParams {
self.requirePhoneNumbers = requirePhoneNumbers
self.allowChannelsInSearch = allowChannelsInSearch
self.confirmation = confirmation
self.isPeerEnabled = isPeerEnabled
self.openProfile = openProfile
self.sendMessage = sendMessage
}

View File

@ -550,10 +550,6 @@ public enum PresentationCurrentCall: Equatable {
}
}
public enum JoinConferenceCallMode {
case joining
}
public protocol PresentationCallManager: AnyObject {
var currentCallSignal: Signal<PresentationCall?, NoError> { get }
var currentGroupCallSignal: Signal<VideoChatCall?, NoError> { get }
@ -568,6 +564,6 @@ public protocol PresentationCallManager: AnyObject {
accountContext: AccountContext,
initialCall: EngineGroupCallDescription,
reference: InternalGroupCallReference,
mode: JoinConferenceCallMode
beginWithVideo: Bool
)
}

View File

@ -231,7 +231,7 @@ public final class CallListController: TelegramBaseController {
isStream: false
),
reference: .id(id: call.callInfo.id, accessHash: call.callInfo.accessHash),
mode: .joining
beginWithVideo: false
)
}

View File

@ -692,6 +692,9 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
public func updateIsHighlighted(transition: ContainedViewLayoutTransition) {
var reallyHighlighted = self.isHighlighted
if let item = self.item, !item.enabled {
reallyHighlighted = false
}
let highlightProgress: CGFloat = self.item?.itemHighlighting?.progress ?? 1.0
if let item = self.item {
switch item.peer {
@ -1649,6 +1652,9 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
actionButtonNode.setImage(actionButton.image, for: .normal)
transition.updateFrame(node: actionButtonNode, frame: CGRect(origin: CGPoint(x: revealOffset + params.width - params.rightInset - 12.0 - actionButtonImage.size.width - offset, y: floor((nodeLayout.contentSize.height - actionButtonImage.size.height) / 2.0)), size: actionButtonImage.size))
actionButtonNode.isEnabled = item.enabled
actionButtonNode.alpha = item.enabled ? 1.0 : 0.4
offset += actionButtonImage.size.width + 12.0
}
}

View File

@ -486,7 +486,7 @@ public final class CallController: ViewController {
var disablePeerIds: [EnginePeer.Id] = []
disablePeerIds.append(self.call.context.account.peerId)
disablePeerIds.append(self.call.peerId)
let controller = CallController.openConferenceAddParticipant(context: self.call.context, disablePeerIds: disablePeerIds, completion: { [weak self] peers in
let controller = CallController.openConferenceAddParticipant(context: self.call.context, disablePeerIds: disablePeerIds, shareLink: nil, completion: { [weak self] peers in
guard let self else {
return
}
@ -497,15 +497,18 @@ public final class CallController: ViewController {
self.push(controller)
}
static func openConferenceAddParticipant(context: AccountContext, disablePeerIds: [EnginePeer.Id], completion: @escaping ([(id: EnginePeer.Id, isVideo: Bool)]) -> Void) -> ViewController {
static func openConferenceAddParticipant(context: AccountContext, disablePeerIds: [EnginePeer.Id], shareLink: (() -> Void)?, completion: @escaping ([(id: EnginePeer.Id, isVideo: Bool)]) -> Void) -> ViewController {
//TODO:localize
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme)
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme)
var options: [ContactListAdditionalOption] = []
//TODO:localize
options.append(ContactListAdditionalOption(title: "Share Call Link", icon: .generic(UIImage(bundleImageName: "Contact List/LinkActionIcon")!), action: {
//TODO:release
}, clearHighlightAutomatically: false))
var openShareLinkImpl: (() -> Void)?
if shareLink != nil {
//TODO:localize
options.append(ContactListAdditionalOption(title: "Share Call Link", icon: .generic(UIImage(bundleImageName: "Contact List/LinkActionIcon")!), action: {
openShareLinkImpl?()
}, clearHighlightAutomatically: false))
}
let controller = context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams(
context: context,
@ -534,9 +537,32 @@ public final class CallController: ViewController {
default:
return .single(false)
}
},
isPeerEnabled: { peer in
switch peer {
case let .peer(peer, _, _):
let peer = EnginePeer(peer)
guard case let .user(user) = peer else {
return false
}
if disablePeerIds.contains(user.id) {
return false
}
if user.botInfo != nil {
return false
}
return true
default:
return false
}
}
))
openShareLinkImpl = { [weak controller] in
controller?.dismiss()
shareLink?()
}
controller.navigationPresentation = .modal
let _ = (controller.result |> take(1) |> deliverOnMainQueue).startStandalone(next: { [weak controller] result in
guard let result, let peer = result.0.first, case let .peer(peer, _, _) = peer else {

View File

@ -1026,6 +1026,7 @@ public final class PresentationCallImpl: PresentationCall {
keyPair: keyPair,
conferenceSourceId: self.internalId,
isConference: true,
beginWithVideo: false,
sharedAudioContext: self.sharedAudioContext
)
self.conferenceCallImpl = conferenceCall

View File

@ -850,6 +850,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
keyPair: nil,
conferenceSourceId: nil,
isConference: false,
beginWithVideo: false,
sharedAudioContext: nil
)
call.schedule(timestamp: timestamp)
@ -1076,6 +1077,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
keyPair: nil,
conferenceSourceId: nil,
isConference: false,
beginWithVideo: false,
sharedAudioContext: nil
)
self.updateCurrentGroupCall(.group(call))
@ -1085,16 +1087,13 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
accountContext: AccountContext,
initialCall: EngineGroupCallDescription,
reference: InternalGroupCallReference,
mode: JoinConferenceCallMode
beginWithVideo: Bool
) {
let keyPair: TelegramKeyPair
switch mode {
case .joining:
guard let keyPairValue = TelegramE2EEncryptionProviderImpl.shared.generateKeyPair() else {
return
}
keyPair = keyPairValue
guard let keyPairValue = TelegramE2EEncryptionProviderImpl.shared.generateKeyPair() else {
return
}
keyPair = keyPairValue
let call = PresentationGroupCallImpl(
accountContext: accountContext,
@ -1111,6 +1110,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
keyPair: keyPair,
conferenceSourceId: nil,
isConference: true,
beginWithVideo: beginWithVideo,
sharedAudioContext: nil
)
self.updateCurrentGroupCall(.group(call))

View File

@ -621,54 +621,98 @@ private final class PendingConferenceInvitationContext {
case ringing
}
private let callSessionManager: CallSessionManager
private let engine: TelegramEngine
private var requestDisposable: Disposable?
private var stateDisposable: Disposable?
private var internalId: CallSessionInternalId?
private(set) var messageId: EngineMessage.Id?
private var hadMessage: Bool = false
private var didNotifyEnded: Bool = false
init(callSessionManager: CallSessionManager, groupCall: GroupCallReference, peerId: PeerId, onStateUpdated: @escaping (State) -> Void, onEnded: @escaping (Bool) -> Void) {
self.callSessionManager = callSessionManager
preconditionFailure()
/*self.requestDisposable = (callSessionManager.request(peerId: peerId, isVideo: false, enableVideo: true, conferenceCall: (groupCall, encryptionKey))
|> deliverOnMainQueue).startStrict(next: { [weak self] internalId in
init(engine: TelegramEngine, reference: InternalGroupCallReference, peerId: PeerId, isVideo: Bool, onStateUpdated: @escaping (State) -> Void, onEnded: @escaping (Bool) -> Void) {
self.engine = engine
self.requestDisposable = (engine.calls.inviteConferenceCallParticipant(reference: reference, peerId: peerId, isVideo: isVideo).startStrict(next: { [weak self] messageId in
guard let self else {
return
}
self.internalId = internalId
guard let messageId else {
if !self.didNotifyEnded {
self.didNotifyEnded = true
onEnded(false)
}
return
}
self.messageId = messageId
self.stateDisposable = (self.callSessionManager.callState(internalId: internalId)
|> deliverOnMainQueue).startStrict(next: { [weak self] state in
onStateUpdated(.ringing)
let timeout: Double = 30.0
let timerSignal = Signal<Void, NoError>.single(Void()) |> then(
Signal<Void, NoError>.single(Void())
|> delay(1.0, queue: .mainQueue())
) |> restart
let startTime = CFAbsoluteTimeGetCurrent()
self.stateDisposable = (combineLatest(queue: .mainQueue(),
engine.data.subscribe(
TelegramEngine.EngineData.Item.Messages.Message(id: messageId)
),
timerSignal
)
|> deliverOnMainQueue).startStrict(next: { [weak self] message, _ in
guard let self else {
return
}
switch state.state {
case let .requesting(ringing, _):
if ringing {
onStateUpdated(.ringing)
if let message {
self.hadMessage = true
if message.timestamp + Int32(timeout) <= Int32(Date().timeIntervalSince1970) {
if !self.didNotifyEnded {
self.didNotifyEnded = true
onEnded(false)
}
} else {
var isActive = false
var isAccepted = false
var foundAction: TelegramMediaAction?
for media in message.media {
if let action = media as? TelegramMediaAction {
foundAction = action
break
}
}
if let action = foundAction, case let .conferenceCall(conferenceCall) = action.action {
if conferenceCall.flags.contains(.isMissed) || conferenceCall.duration != nil {
} else {
if conferenceCall.flags.contains(.isActive) {
isAccepted = true
} else {
isActive = true
}
}
}
if !isActive {
if !self.didNotifyEnded {
self.didNotifyEnded = true
onEnded(isAccepted)
}
}
}
case let .dropping(reason), let .terminated(_, reason, _):
if !self.didNotifyEnded {
self.didNotifyEnded = true
onEnded(reason == .ended(.switchedToConference))
} else {
if self.hadMessage || CFAbsoluteTimeGetCurrent() > startTime + 1.0 {
if !self.didNotifyEnded {
self.didNotifyEnded = true
onEnded(false)
}
}
default:
break
}
})
})*/
}))
}
deinit {
self.requestDisposable?.dispose()
self.stateDisposable?.dispose()
if let internalId = self.internalId {
self.callSessionManager.drop(internalId: internalId, reason: .hangUp, debugLog: .single(nil))
}
}
}
@ -1121,6 +1165,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
private let sharedAudioContext: SharedCallAudioContext?
public let isConference: Bool
private let beginWithVideo: Bool
private let conferenceSourceId: CallSessionInternalId?
public var conferenceSource: CallSessionInternalId? {
@ -1153,6 +1198,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
keyPair: TelegramKeyPair?,
conferenceSourceId: CallSessionInternalId?,
isConference: Bool,
beginWithVideo: Bool,
sharedAudioContext: SharedCallAudioContext?
) {
self.account = accountContext.account
@ -1183,6 +1229,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.isStream = isStream
self.conferenceSourceId = conferenceSourceId
self.isConference = isConference
self.beginWithVideo = beginWithVideo
self.keyPair = keyPair
if let keyPair, let initialCall {
@ -1490,6 +1537,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
strongSelf.screencastBufferClientContext = IpcGroupCallBufferBroadcastContext(basePath: basePath)
})*/
if beginWithVideo {
self.requestVideo()
}
}
deinit {
@ -3844,22 +3895,17 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
return false
}
//TODO:release
let _ = self.accountContext.engine.calls.inviteConferenceCallParticipant(callId: initialCall.description.id, accessHash: initialCall.description.accessHash, peerId: peerId, isVideo: isVideo).start()
return false
/*guard let initialCall = self.initialCall else {
return false
}
if conferenceInvitationContexts[peerId] != nil {
if self.conferenceInvitationContexts[peerId] != nil {
return false
}
var onStateUpdated: ((PendingConferenceInvitationContext.State) -> Void)?
var onEnded: ((Bool) -> Void)?
var didEndAlready = false
let invitationContext = PendingConferenceInvitationContext(
callSessionManager: self.accountContext.account.callSessionManager,
groupCall: GroupCallReference(id: initialCall.id, accessHash: initialCall.accessHash),
engine: self.accountContext.engine,
reference: initialCall.reference,
peerId: peerId,
isVideo: isVideo,
onStateUpdated: { state in
onStateUpdated?(state)
},
@ -3906,7 +3952,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
}
return false*/
return false
} else {
guard let callInfo = self.internalState.callInfo, !self.invitedPeersValue.contains(where: { $0.id == peerId }) else {
return false
@ -3933,6 +3979,13 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
var updatedInvitedPeers = self.invitedPeersValue
updatedInvitedPeers.removeAll(where: { $0.id == peerId})
self.invitedPeersValue = updatedInvitedPeers
if let conferenceInvitationContext = self.conferenceInvitationContexts[peerId] {
self.conferenceInvitationContexts.removeValue(forKey: peerId)
if let messageId = conferenceInvitationContext.messageId {
self.accountContext.engine.account.callSessionManager.dropOutgoingConferenceRequest(messageId: messageId)
}
}
}
public func updateTitle(_ title: String) {

View File

@ -5,6 +5,179 @@ import ComponentFlow
import MultilineTextComponent
import BalancedTextComponent
import TelegramPresentationData
import CallsEmoji
private final class EmojiItemComponent: Component {
let emoji: String?
init(emoji: String?) {
self.emoji = emoji
}
static func ==(lhs: EmojiItemComponent, rhs: EmojiItemComponent) -> Bool {
if lhs.emoji != rhs.emoji {
return false
}
return true
}
final class View: UIView {
private let measureEmojiView = ComponentView<Empty>()
private var pendingContainerView: UIView?
private var pendingEmojiViews: [ComponentView<Empty>] = []
private var emojiView: ComponentView<Empty>?
private var component: EmojiItemComponent?
private weak var state: EmptyComponentState?
private var pendingEmojiValues: [String]?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
}
func update(component: EmojiItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let size = self.measureEmojiView.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "👍", font: Font.regular(40.0), textColor: .white))
)),
environment: {},
containerSize: CGSize(width: 200.0, height: 200.0)
)
let borderEmoji = 2
let numEmoji = borderEmoji * 2 + 3
if let emoji = component.emoji {
let emojiView: ComponentView<Empty>
var emojiViewTransition = transition
if let current = self.emojiView {
emojiView = current
} else {
emojiViewTransition = .immediate
emojiView = ComponentView()
self.emojiView = emojiView
}
let emojiSize = emojiView.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: emoji, font: Font.regular(40.0), textColor: .white))
)),
environment: {},
containerSize: CGSize(width: 200.0, height: 200.0)
)
let emojiFrame = CGRect(origin: CGPoint(x: floor((size.width - emojiSize.width) * 0.5), y: floor((size.height - emojiSize.height) * 0.5)), size: emojiSize)
if let emojiComponentView = emojiView.view {
if emojiComponentView.superview == nil {
self.addSubview(emojiComponentView)
}
emojiViewTransition.setFrame(view: emojiComponentView, frame: emojiFrame)
}
self.pendingEmojiValues = nil
} else {
if let emojiView = self.emojiView {
self.emojiView = nil
emojiView.view?.removeFromSuperview()
}
if self.pendingEmojiValues?.count != numEmoji {
var pendingEmojiValuesValue: [String] = []
for _ in 0 ..< numEmoji - borderEmoji - 1 {
pendingEmojiValuesValue.append(randomCallsEmoji() ?? "👍")
}
for i in 0 ..< borderEmoji + 1 {
pendingEmojiValuesValue.append(pendingEmojiValuesValue[i])
}
self.pendingEmojiValues = pendingEmojiValuesValue
}
}
if let pendingEmojiValues, pendingEmojiValues.count == numEmoji {
let pendingContainerView: UIView
if let current = self.pendingContainerView {
pendingContainerView = current
} else {
pendingContainerView = UIView()
self.pendingContainerView = pendingContainerView
}
for i in 0 ..< numEmoji {
let pendingEmojiView: ComponentView<Empty>
if self.pendingEmojiViews.count > i {
pendingEmojiView = self.pendingEmojiViews[i]
} else {
pendingEmojiView = ComponentView()
self.pendingEmojiViews.append(pendingEmojiView)
}
let pendingEmojiViewSize = pendingEmojiView.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: pendingEmojiValues[i], font: Font.regular(40.0), textColor: .white))
)),
environment: {},
containerSize: CGSize(width: 200.0, height: 200.0)
)
if let pendingEmojiComponentView = pendingEmojiView.view {
if pendingEmojiComponentView.superview == nil {
pendingContainerView.addSubview(pendingEmojiComponentView)
}
pendingEmojiComponentView.frame = CGRect(origin: CGPoint(x: 0.0, y: CGFloat(i) * size.height), size: pendingEmojiViewSize)
}
}
pendingContainerView.frame = CGRect(origin: CGPoint(), size: size)
if pendingContainerView.superview == nil {
self.addSubview(pendingContainerView)
let animation = CABasicAnimation(keyPath: "sublayerTransform.translation.y")
//animation.duration = 4.2
animation.duration = 0.2
animation.fromValue = -CGFloat(numEmoji - borderEmoji) * size.height
animation.toValue = CGFloat(borderEmoji - 3) * size.height
animation.timingFunction = CAMediaTimingFunction(name: .linear)
animation.autoreverses = false
animation.repeatCount = .infinity
pendingContainerView.layer.add(animation, forKey: "offsetCycle")
}
} else if let pendingContainerView = self.pendingContainerView {
self.pendingContainerView = nil
pendingContainerView.removeFromSuperview()
for emojiView in self.pendingEmojiViews {
emojiView.view?.removeFromSuperview()
}
self.pendingEmojiViews.removeAll()
}
//self.layer.borderColor = UIColor.red.cgColor
//self.layer.borderWidth = 4.0
return size
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
final class VideoChatEncryptionKeyComponent: Component {
let theme: PresentationTheme
@ -119,7 +292,7 @@ final class VideoChatEncryptionKeyComponent: Component {
let expandedButtonTopInset: CGFloat = 12.0
let expandedButtonBottomInset: CGFloat = 13.0
let emojiItemSizes = (0 ..< component.emoji.count).map { i -> CGSize in
let emojiItemSizes = (0 ..< 4).map { i -> CGSize in
let emojiItem: ComponentView<Empty>
if self.emojiItems.count > i {
emojiItem = self.emojiItems[i]
@ -128,9 +301,9 @@ final class VideoChatEncryptionKeyComponent: Component {
self.emojiItems.append(emojiItem)
}
return emojiItem.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.emoji[i], font: Font.regular(40.0), textColor: .white))
transition: transition,
component: AnyComponent(EmojiItemComponent(
emoji: i < component.emoji.count ? component.emoji[i] : nil
)),
environment: {},
containerSize: CGSize(width: 200.0, height: 200.0)

View File

@ -7,16 +7,24 @@ import TelegramPresentationData
import BundleIconComponent
final class VideoChatListInviteComponent: Component {
enum Icon {
case addUser
case link
}
let title: String
let icon: Icon
let theme: PresentationTheme
let action: () -> Void
init(
title: String,
icon: Icon,
theme: PresentationTheme,
action: @escaping () -> Void
) {
self.title = title
self.icon = icon
self.theme = theme
self.action = action
}
@ -25,6 +33,9 @@ final class VideoChatListInviteComponent: Component {
if lhs.title != rhs.title {
return false
}
if lhs.icon != rhs.icon {
return false
}
if lhs.theme !== rhs.theme {
return false
}
@ -116,10 +127,17 @@ final class VideoChatListInviteComponent: Component {
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
}
let iconName: String
switch component.icon {
case .addUser:
iconName = "Chat/Context Menu/AddUser"
case .link:
iconName = "Chat/Context Menu/Link"
}
let iconSize = self.icon.update(
transition: .immediate,
component: AnyComponent(BundleIconComponent(
name: "Chat/Context Menu/AddUser",
name: iconName,
tintColor: component.theme.list.itemAccentColor
)),
environment: {},

View File

@ -39,23 +39,33 @@ final class VideoChatParticipantsComponent: Component {
}
final class Participants: Equatable {
enum InviteType {
case invite
enum InviteType: Equatable {
case invite(isMultipleUsers: Bool)
case shareLink
}
struct InviteOption: Equatable {
let id: Int
let type: InviteType
init(id: Int, type: InviteType) {
self.id = id
self.type = type
}
}
let myPeerId: EnginePeer.Id
let participants: [GroupCallParticipantsContext.Participant]
let totalCount: Int
let loadMoreToken: String?
let inviteType: InviteType?
let inviteOptions: [InviteOption]
init(myPeerId: EnginePeer.Id, participants: [GroupCallParticipantsContext.Participant], totalCount: Int, loadMoreToken: String?, inviteType: InviteType?) {
init(myPeerId: EnginePeer.Id, participants: [GroupCallParticipantsContext.Participant], totalCount: Int, loadMoreToken: String?, inviteOptions: [InviteOption]) {
self.myPeerId = myPeerId
self.participants = participants
self.totalCount = totalCount
self.loadMoreToken = loadMoreToken
self.inviteType = inviteType
self.inviteOptions = inviteOptions
}
static func ==(lhs: Participants, rhs: Participants) -> Bool {
@ -74,7 +84,7 @@ final class VideoChatParticipantsComponent: Component {
if lhs.loadMoreToken != rhs.loadMoreToken {
return false
}
if lhs.inviteType != rhs.inviteType {
if lhs.inviteOptions != rhs.inviteOptions {
return false
}
return true
@ -142,7 +152,7 @@ final class VideoChatParticipantsComponent: Component {
let updateMainParticipant: (VideoParticipantKey?, Bool?) -> Void
let updateIsMainParticipantPinned: (Bool) -> Void
let updateIsExpandedUIHidden: (Bool) -> Void
let openInviteMembers: () -> Void
let openInviteMembers: (Participants.InviteType) -> Void
let visibleParticipantsUpdated: (Set<EnginePeer.Id>) -> Void
init(
@ -162,7 +172,7 @@ final class VideoChatParticipantsComponent: Component {
updateMainParticipant: @escaping (VideoParticipantKey?, Bool?) -> Void,
updateIsMainParticipantPinned: @escaping (Bool) -> Void,
updateIsExpandedUIHidden: @escaping (Bool) -> Void,
openInviteMembers: @escaping () -> Void,
openInviteMembers: @escaping (Participants.InviteType) -> Void,
visibleParticipantsUpdated: @escaping (Set<EnginePeer.Id>) -> Void
) {
self.call = call
@ -379,14 +389,14 @@ final class VideoChatParticipantsComponent: Component {
let sideInset: CGFloat
let itemCount: Int
let itemHeight: CGFloat
let trailingItemHeight: CGFloat
let trailingItemHeights: [CGFloat]
init(containerSize: CGSize, sideInset: CGFloat, itemCount: Int, itemHeight: CGFloat, trailingItemHeight: CGFloat) {
init(containerSize: CGSize, sideInset: CGFloat, itemCount: Int, itemHeight: CGFloat, trailingItemHeights: [CGFloat]) {
self.containerSize = containerSize
self.sideInset = sideInset
self.itemCount = itemCount
self.itemHeight = itemHeight
self.trailingItemHeight = trailingItemHeight
self.trailingItemHeights = trailingItemHeights
}
func frame(at index: Int) -> CGRect {
@ -394,8 +404,15 @@ final class VideoChatParticipantsComponent: Component {
return frame
}
func trailingItemFrame() -> CGRect {
return CGRect(origin: CGPoint(x: self.sideInset, y: CGFloat(self.itemCount) * self.itemHeight), size: CGSize(width: self.containerSize.width - self.sideInset * 2.0, height: self.trailingItemHeight))
func trailingItemFrame(index: Int) -> CGRect {
if index < 0 || index >= self.trailingItemHeights.count {
return CGRect()
}
var prefixHeight: CGFloat = 0.0
for i in 0 ..< index {
prefixHeight += self.trailingItemHeights[i]
}
return CGRect(origin: CGPoint(x: self.sideInset, y: CGFloat(self.itemCount) * self.itemHeight + prefixHeight), size: CGSize(width: self.containerSize.width - self.sideInset * 2.0, height: self.trailingItemHeights[index]))
}
func contentHeight() -> CGFloat {
@ -403,7 +420,9 @@ final class VideoChatParticipantsComponent: Component {
if self.itemCount != 0 {
result = self.frame(at: self.itemCount - 1).maxY
}
result += self.trailingItemHeight
for height in self.trailingItemHeights {
result += height
}
return result
}
@ -439,7 +458,7 @@ final class VideoChatParticipantsComponent: Component {
let scrollClippingFrame: CGRect
let separateVideoScrollClippingFrame: CGRect
init(containerSize: CGSize, layout: Layout, isUIHidden: Bool, expandedInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, gridItemCount: Int, listItemCount: Int, listItemHeight: CGFloat, listTrailingItemHeight: CGFloat) {
init(containerSize: CGSize, layout: Layout, isUIHidden: Bool, expandedInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, gridItemCount: Int, listItemCount: Int, listItemHeight: CGFloat, listTrailingItemHeights: [CGFloat]) {
self.containerSize = containerSize
self.layout = layout
self.isUIHidden = isUIHidden
@ -465,7 +484,7 @@ final class VideoChatParticipantsComponent: Component {
}
self.grid = Grid(containerSize: CGSize(width: gridWidth, height: gridContainerHeight), sideInset: gridSideInset, itemCount: gridItemCount, isDedicatedColumn: layout.videoColumn != nil)
self.list = List(containerSize: CGSize(width: listWidth, height: containerSize.height), sideInset: layout.mainColumn.insets.left, itemCount: listItemCount, itemHeight: listItemHeight, trailingItemHeight: listTrailingItemHeight)
self.list = List(containerSize: CGSize(width: listWidth, height: containerSize.height), sideInset: layout.mainColumn.insets.left, itemCount: listItemCount, itemHeight: listItemHeight, trailingItemHeights: listTrailingItemHeights)
self.spacing = 4.0
if let videoColumn = layout.videoColumn, !isUIHidden && !layout.isMainColumnHidden {
@ -568,8 +587,8 @@ final class VideoChatParticipantsComponent: Component {
}
}
func listTrailingItemFrame() -> CGRect {
return self.list.trailingItemFrame()
func listTrailingItemFrame(index: Int) -> CGRect {
return self.list.trailingItemFrame(index: index)
}
}
@ -641,7 +660,7 @@ final class VideoChatParticipantsComponent: Component {
private var listParticipants: [GroupCallParticipantsContext.Participant] = []
private let measureListItemView = ComponentView<Empty>()
private let inviteListItemView = ComponentView<Empty>()
private var inviteListItemViews: [Int: ComponentView<Empty>] = [:]
private var gridItemViews: [VideoParticipantKey: GridItem] = [:]
private let gridItemViewContainer: UIView
@ -1270,7 +1289,7 @@ final class VideoChatParticipantsComponent: Component {
case .requesting:
subtitle = PeerListItemComponent.Subtitle(text: "requesting...", color: .neutral)
case .ringing:
subtitle = PeerListItemComponent.Subtitle(text: "ringing...", color: .neutral)
subtitle = PeerListItemComponent.Subtitle(text: "invited", color: .neutral)
}
peerItemComponent = PeerListItemComponent(
@ -1381,11 +1400,15 @@ final class VideoChatParticipantsComponent: Component {
self.listItemViews.removeValue(forKey: itemId)
}
do {
var trailingItemIndex = 0
for inviteOption in component.participants?.inviteOptions ?? [] {
guard let itemView = self.inviteListItemViews[inviteOption.id] else {
continue
}
var itemTransition = transition
let itemView = self.inviteListItemView
let itemFrame = itemLayout.listTrailingItemFrame()
let itemFrame = itemLayout.listTrailingItemFrame(index: trailingItemIndex)
trailingItemIndex += 1
if let itemComponentView = itemView.view {
if itemComponentView.superview == nil {
@ -1395,6 +1418,17 @@ final class VideoChatParticipantsComponent: Component {
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
}
}
var removeInviteListItemIds: [Int] = []
for (id, itemView) in self.inviteListItemViews {
if let participants = component.participants, participants.inviteOptions.contains(where: { $0.id == id }) {
} else {
removeInviteListItemIds.append(id)
itemView.view?.removeFromSuperview()
}
}
for id in removeInviteListItemIds {
self.inviteListItemViews.removeValue(forKey: id)
}
transition.setScale(view: self.gridItemViewContainer, scale: gridIsEmpty ? 0.001 : 1.0)
transition.setPosition(view: self.gridItemViewContainer, position: CGPoint(x: itemLayout.gridItemContainerFrame().midX, y: itemLayout.gridItemContainerFrame().minY))
@ -1748,32 +1782,51 @@ final class VideoChatParticipantsComponent: Component {
containerSize: CGSize(width: availableSize.width, height: 1000.0)
)
let inviteText: String
if let participants = component.participants, let inviteType = participants.inviteType {
switch inviteType {
case .invite:
inviteText = component.strings.VoiceChat_InviteMember
var inviteListItemSizes: [CGSize] = []
for (inviteOption) in component.participants?.inviteOptions ?? [] {
let inviteText: String
let iconType: VideoChatListInviteComponent.Icon
switch inviteOption.type {
case let .invite(isMultiple):
//TODO:localize
if isMultiple {
inviteText = component.strings.VoiceChat_InviteMember
} else {
inviteText = "Add Member"
}
iconType = .addUser
case .shareLink:
inviteText = component.strings.VoiceChat_Share
iconType = .link
}
} else {
inviteText = component.strings.VoiceChat_InviteMember
}
let inviteListItemSize = self.inviteListItemView.update(
transition: transition,
component: AnyComponent(VideoChatListInviteComponent(
title: inviteText,
theme: component.theme,
action: { [weak self] in
guard let self, let component = self.component else {
return
let inviteListItemView: ComponentView<Empty>
var inviteListItemTransition = transition
if let current = self.inviteListItemViews[inviteOption.id] {
inviteListItemView = current
} else {
inviteListItemView = ComponentView()
self.inviteListItemViews[inviteOption.id] = inviteListItemView
inviteListItemTransition = inviteListItemTransition.withAnimation(.none)
}
inviteListItemSizes.append(inviteListItemView.update(
transition: inviteListItemTransition,
component: AnyComponent(VideoChatListInviteComponent(
title: inviteText,
icon: iconType,
theme: component.theme,
action: { [weak self] in
guard let self, let component = self.component else {
return
}
component.openInviteMembers(inviteOption.type)
}
component.openInviteMembers()
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: 1000.0)
)
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: 1000.0)
))
}
var gridParticipants: [VideoParticipant] = []
var listParticipants: [GroupCallParticipantsContext.Participant] = []
@ -1824,7 +1877,7 @@ final class VideoChatParticipantsComponent: Component {
gridItemCount: gridParticipants.count,
listItemCount: listParticipants.count + component.invitedPeers.count,
listItemHeight: measureListItemSize.height,
listTrailingItemHeight: inviteListItemSize.height
listTrailingItemHeights: inviteListItemSizes.map(\.height)
)
self.itemLayout = itemLayout

View File

@ -1759,12 +1759,19 @@ final class VideoChatScreenComponent: Component {
}
}
}
var inviteType: VideoChatParticipantsComponent.Participants.InviteType?
if canInvite {
if inviteIsLink {
inviteType = .shareLink
} else {
inviteType = .invite
var inviteOptions: [VideoChatParticipantsComponent.Participants.InviteOption] = []
if case let .group(groupCall) = self.currentCall, groupCall.isConference {
inviteOptions.append(VideoChatParticipantsComponent.Participants.InviteOption(id: 0, type: .invite(isMultipleUsers: false)))
inviteOptions.append(VideoChatParticipantsComponent.Participants.InviteOption(id: 1, type: .shareLink))
} else {
if canInvite {
let inviteType: VideoChatParticipantsComponent.Participants.InviteType
if inviteIsLink {
inviteType = .shareLink
} else {
inviteType = .invite(isMultipleUsers: false)
}
inviteOptions.append(VideoChatParticipantsComponent.Participants.InviteOption(id: 0, type: inviteType))
}
}
@ -1773,7 +1780,7 @@ final class VideoChatScreenComponent: Component {
participants: members.participants,
totalCount: members.totalCount,
loadMoreToken: members.loadMoreToken,
inviteType: inviteType
inviteOptions: inviteOptions
)
}
@ -2038,7 +2045,13 @@ final class VideoChatScreenComponent: Component {
}
var encryptionKeyFrame: CGRect?
if let encryptionKeyEmoji = self.encryptionKeyEmoji {
var isConference = false
if case let .group(groupCall) = self.currentCall {
isConference = groupCall.isConference
} else if case .conferenceSource = self.currentCall {
isConference = true
}
if isConference {
navigationHeight -= 2.0
let encryptionKey: ComponentView<Empty>
var encryptionKeyTransition = transition
@ -2055,7 +2068,7 @@ final class VideoChatScreenComponent: Component {
component: AnyComponent(VideoChatEncryptionKeyComponent(
theme: environment.theme,
strings: environment.strings,
emoji: encryptionKeyEmoji,
emoji: self.encryptionKeyEmoji ?? [],
isExpanded: self.isEncryptionKeyExpanded,
tapAction: { [weak self] in
guard let self else {
@ -2326,11 +2339,18 @@ final class VideoChatScreenComponent: Component {
self.state?.updated(transition: .spring(duration: 0.4))
}
},
openInviteMembers: { [weak self] in
openInviteMembers: { [weak self] type in
guard let self else {
return
}
self.openInviteMembers()
if case .shareLink = type {
guard let inviteLinks = self.inviteLinks else {
return
}
self.presentShare(inviteLinks)
} else {
self.openInviteMembers()
}
},
visibleParticipantsUpdated: { [weak self] visibleParticipants in
guard let self else {

View File

@ -51,7 +51,15 @@ extension VideoChatScreenComponent.View {
}
}
}
let controller = CallController.openConferenceAddParticipant(context: groupCall.accountContext, disablePeerIds: disablePeerIds, completion: { [weak self] peerIds in
let controller = CallController.openConferenceAddParticipant(context: groupCall.accountContext, disablePeerIds: disablePeerIds, shareLink: { [weak self] in
guard let self else {
return
}
guard let inviteLinks = self.inviteLinks else {
return
}
self.presentShare(inviteLinks)
}, completion: { [weak self] peerIds in
guard let self, case let .group(groupCall) = self.currentCall else {
return
}
@ -80,7 +88,7 @@ extension VideoChatScreenComponent.View {
if inviteIsLink {
inviteType = .shareLink
} else {
inviteType = .invite
inviteType = .invite(isMultipleUsers: true)
}
}

View File

@ -718,6 +718,23 @@ private final class CallSessionManagerContext {
}
}
func dropOutgoingConferenceRequest(messageId: MessageId) {
let addUpdates = self.addUpdates
let rejectSignal = self.network.request(Api.functions.phone.declineConferenceCallInvite(msgId: messageId.id))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> mapToSignal { updates -> Signal<Never, NoError> in
if let updates {
addUpdates(updates)
}
return .complete()
}
self.rejectConferenceInvitationDisposables.add(rejectSignal.startStrict())
}
func drop(internalId: CallSessionInternalId, reason: DropCallReason, debugLog: Signal<String?, NoError>) {
for (id, context) in self.incomingConferenceInvitationContexts {
if context.internalId == internalId {
@ -1383,6 +1400,12 @@ public final class CallSessionManager {
}
}
public func dropOutgoingConferenceRequest(messageId: MessageId) {
self.withContext { context in
context.dropOutgoingConferenceRequest(messageId: messageId)
}
}
func drop(stableId: CallSessionStableId, reason: DropCallReason) {
self.withContext { context in
context.drop(stableId: stableId, reason: reason)

View File

@ -831,11 +831,11 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
}
}
func _internal_inviteConferenceCallParticipant(account: Account, callId: Int64, accessHash: Int64, peerId: EnginePeer.Id, isVideo: Bool) -> Signal<Never, NoError> {
func _internal_inviteConferenceCallParticipant(account: Account, reference: InternalGroupCallReference, peerId: EnginePeer.Id, isVideo: Bool) -> Signal<MessageId?, NoError> {
return account.postbox.transaction { transaction -> Api.InputUser? in
return transaction.getPeer(peerId).flatMap(apiInputUser)
}
|> mapToSignal { inputPeer -> Signal<Never, NoError> in
|> mapToSignal { inputPeer -> Signal<MessageId?, NoError> in
guard let inputPeer else {
return .complete()
}
@ -844,16 +844,19 @@ func _internal_inviteConferenceCallParticipant(account: Account, callId: Int64,
if isVideo {
flags |= 1 << 0
}
return account.network.request(Api.functions.phone.inviteConferenceCallParticipant(flags: flags, call: .inputGroupCall(id: callId, accessHash: accessHash), userId: inputPeer))
return account.network.request(Api.functions.phone.inviteConferenceCallParticipant(flags: flags, call: reference.apiInputGroupCall, userId: inputPeer))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Never, NoError> in
|> mapToSignal { result -> Signal<MessageId?, NoError> in
if let result {
account.stateManager.addUpdates(result)
if let message = result.messageIds.first {
return .single(message)
}
}
return .complete()
return .single(nil)
}
}
}

View File

@ -105,8 +105,8 @@ public extension TelegramEngine {
return _internal_sendConferenceCallBroadcast(account: self.account, callId: callId, accessHash: accessHash, block: block)
}
public func inviteConferenceCallParticipant(callId: Int64, accessHash: Int64, peerId: EnginePeer.Id, isVideo: Bool) -> Signal<Never, NoError> {
return _internal_inviteConferenceCallParticipant(account: self.account, callId: callId, accessHash: accessHash, peerId: peerId, isVideo: isVideo)
public func inviteConferenceCallParticipant(reference: InternalGroupCallReference, peerId: EnginePeer.Id, isVideo: Bool) -> Signal<EngineMessage.Id?, NoError> {
return _internal_inviteConferenceCallParticipant(account: self.account, reference: reference, peerId: peerId, isVideo: isVideo)
}
public func removeGroupCallBlockchainParticipants(callId: Int64, accessHash: Int64, participantIds: [Int64], block: Data) -> Signal<RemoveGroupCallBlockchainParticipantsResult, NoError> {

View File

@ -10,6 +10,7 @@ swift_library(
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/TelegramCore",

View File

@ -9,6 +9,7 @@ import AppBundle
import ChatMessageBubbleContentNode
import ChatMessageItemCommon
import ChatMessageDateAndStatusNode
import SwiftSignalKit
private let titleFont: UIFont = Font.medium(16.0)
private let labelFont: UIFont = Font.regular(13.0)
@ -25,6 +26,8 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
private let iconNode: ASImageNode
private let buttonNode: HighlightableButtonNode
private var activeConferenceUpdateTimer: SwiftSignalKit.Timer?
required public init() {
self.titleNode = TextNode()
self.labelNode = TextNode()
@ -57,6 +60,10 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
self.buttonNode.addTarget(self, action: #selector(self.callButtonPressed), forControlEvents: .touchUpInside)
}
deinit {
self.activeConferenceUpdateTimer?.invalidate()
}
override public func accessibilityActivate() -> Bool {
self.callButtonPressed()
return true
@ -90,6 +97,8 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
var callDuration: Int32?
var callSuccessful = true
var isVideo = false
var hasCallButton = true
var updateConferenceTimerEndTimeout: Int32?
for media in item.message.media {
if let action = media as? TelegramMediaAction, case let .phoneCall(_, discardReason, duration, isVideoValue) = action.action {
isVideo = isVideoValue
@ -124,10 +133,31 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
}
break
} else if let action = media as? TelegramMediaAction, case let .conferenceCall(conferenceCall) = action.action {
isVideo = false
isVideo = conferenceCall.flags.contains(.isVideo)
callDuration = conferenceCall.duration
//TODO:localize
titleString = "Group Call"
let missedTimeout: Int32
#if DEBUG
missedTimeout = 5
#else
missedTimeout = 30
#endif
let currentTime = Int32(Date().timeIntervalSince1970)
if conferenceCall.flags.contains(.isMissed) {
titleString = "Declined Group Call"
} else if item.message.timestamp < currentTime - missedTimeout {
titleString = "Missed Group Call"
} else if conferenceCall.duration != nil {
titleString = "Cancelled Group Call"
hasCallButton = true
} else {
if incoming {
titleString = "Incoming Group Call"
} else {
titleString = "Outgoing Group Call"
}
updateConferenceTimerEndTimeout = (item.message.timestamp + missedTimeout) - currentTime
}
break
}
}
@ -211,7 +241,9 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom
boundingSize.width += 54.0
if hasCallButton {
boundingSize.width += 54.0
}
return (boundingSize.width, { boundingWidth in
return (boundingSize, { [weak self] animation, _, _ in
@ -234,6 +266,22 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
if let buttonImage = buttonImage {
strongSelf.buttonNode.setImage(buttonImage, for: [])
strongSelf.buttonNode.frame = CGRect(origin: CGPoint(x: boundingWidth - buttonImage.size.width - 8.0, y: 15.0), size: buttonImage.size)
strongSelf.buttonNode.isHidden = !hasCallButton
}
if let activeConferenceUpdateTimer = strongSelf.activeConferenceUpdateTimer {
activeConferenceUpdateTimer.invalidate()
strongSelf.activeConferenceUpdateTimer = nil
}
if let updateConferenceTimerEndTimeout, updateConferenceTimerEndTimeout >= 0 {
strongSelf.activeConferenceUpdateTimer?.invalidate()
strongSelf.activeConferenceUpdateTimer = SwiftSignalKit.Timer(timeout: Double(updateConferenceTimerEndTimeout) + 0.5, repeat: false, completion: { [weak strongSelf] in
guard let strongSelf else {
return
}
strongSelf.requestInlineUpdate?()
}, queue: .mainQueue())
strongSelf.activeConferenceUpdateTimer?.start()
}
}
})
@ -270,6 +318,10 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
}
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
if self.buttonNode.isHidden {
return ChatMessageBubbleContentTapAction(content: .none)
}
if self.buttonNode.frame.contains(point) {
return ChatMessageBubbleContentTapAction(content: .ignore)
} else if self.bounds.contains(point), let item = self.item {

View File

@ -405,7 +405,7 @@ private final class JoinSubjectScreenComponent: Component {
isStream: false
),
reference: .link(slug: groupCall.slug),
mode: .joining
beginWithVideo: false
)
self.environment?.controller()?.dismiss()

View File

@ -2908,7 +2908,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
isStream: false
),
reference: .message(id: message.id),
mode: .joining
beginWithVideo: conferenceCall.flags.contains(.isVideo)
)
})
}, longTap: { [weak self] action, params in

View File

@ -60,6 +60,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
}
private let confirmation: (ContactListPeer) -> Signal<Bool, NoError>
private let isPeerEnabled: (ContactListPeer) -> Bool
var dismissed: (() -> Void)?
var presentScheduleTimePicker: (@escaping (Int32) -> Void) -> Void = { _ in }
@ -107,6 +108,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
self.displayDeviceContacts = params.displayDeviceContacts
self.displayCallIcons = params.displayCallIcons
self.confirmation = params.confirmation
self.isPeerEnabled = params.isPeerEnabled
self.multipleSelection = params.multipleSelection
self.requirePhoneNumbers = params.requirePhoneNumbers
self.allowChannelsInSearch = params.allowChannelsInSearch
@ -218,7 +220,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
}
override func loadDisplayNode() {
self.displayNode = ContactSelectionControllerNode(context: self.context, mode: self.mode, presentationData: self.presentationData, options: self.options, displayDeviceContacts: self.displayDeviceContacts, displayCallIcons: self.displayCallIcons, multipleSelection: self.multipleSelection, requirePhoneNumbers: self.requirePhoneNumbers, allowChannelsInSearch: self.allowChannelsInSearch)
self.displayNode = ContactSelectionControllerNode(context: self.context, mode: self.mode, presentationData: self.presentationData, options: self.options, displayDeviceContacts: self.displayDeviceContacts, displayCallIcons: self.displayCallIcons, multipleSelection: self.multipleSelection, requirePhoneNumbers: self.requirePhoneNumbers, allowChannelsInSearch: self.allowChannelsInSearch, isPeerEnabled: self.isPeerEnabled)
self._ready.set(self.contactsNode.contactListNode.ready)
self.contactsNode.navigationBar = self.navigationBar

View File

@ -44,6 +44,8 @@ final class ContactSelectionControllerNode: ASDisplayNode {
var cancelSearch: (() -> Void)?
var openPeerMore: ((ContactListPeer, ASDisplayNode?, ContextGesture?) -> Void)?
let isPeerEnabled: (ContactListPeer) -> Bool
var presentationData: PresentationData {
didSet {
self.presentationDataPromise.set(.single(self.presentationData))
@ -57,12 +59,13 @@ final class ContactSelectionControllerNode: ASDisplayNode {
var searchContainerNode: ContactsSearchContainerNode?
init(context: AccountContext, mode: ContactSelectionControllerMode, presentationData: PresentationData, options: Signal<[ContactListAdditionalOption], NoError>, displayDeviceContacts: Bool, displayCallIcons: Bool, multipleSelection: Bool, requirePhoneNumbers: Bool, allowChannelsInSearch: Bool) {
init(context: AccountContext, mode: ContactSelectionControllerMode, presentationData: PresentationData, options: Signal<[ContactListAdditionalOption], NoError>, displayDeviceContacts: Bool, displayCallIcons: Bool, multipleSelection: Bool, requirePhoneNumbers: Bool, allowChannelsInSearch: Bool, isPeerEnabled: @escaping (ContactListPeer) -> Bool) {
self.context = context
self.presentationData = presentationData
self.displayDeviceContacts = displayDeviceContacts
self.displayCallIcons = displayCallIcons
self.allowChannelsInSearch = allowChannelsInSearch
self.isPeerEnabled = isPeerEnabled
var excludeSelf = true
@ -124,7 +127,9 @@ final class ContactSelectionControllerNode: ASDisplayNode {
}
var contextActionImpl: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?
self.contactListNode = ContactListNode(context: context, updatedPresentationData: (presentationData, self.presentationDataPromise.get()), presentation: presentation, filters: filters, onlyWriteable: false, isGroupInvitation: false, displayCallIcons: displayCallIcons, contextAction: multipleSelection ? { peer, node, gesture, _, _ in
self.contactListNode = ContactListNode(context: context, updatedPresentationData: (presentationData, self.presentationDataPromise.get()), presentation: presentation, filters: filters, onlyWriteable: false, isGroupInvitation: false, isPeerEnabled: { peer in
return isPeerEnabled(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil))
}, displayCallIcons: displayCallIcons, contextAction: multipleSelection ? { peer, node, gesture, _, _ in
contextActionImpl?(peer, node, gesture, nil)
} : nil, multipleSelection: multipleSelection)