mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Conference calls
This commit is contained in:
parent
5af756488a
commit
cb83bc1b67
@ -77,7 +77,7 @@ public enum ContactMultiselectionControllerMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case groupCreation
|
case groupCreation(isCall: Bool)
|
||||||
case peerSelection(searchChatList: Bool, searchGroups: Bool, searchChannels: Bool)
|
case peerSelection(searchChatList: Bool, searchGroups: Bool, searchChannels: Bool)
|
||||||
case channelCreation
|
case channelCreation
|
||||||
case chatSelection(ChatSelection)
|
case chatSelection(ChatSelection)
|
||||||
@ -110,7 +110,23 @@ public final class ContactMultiselectionControllerParams {
|
|||||||
public let openProfile: ((EnginePeer) -> Void)?
|
public let openProfile: ((EnginePeer) -> Void)?
|
||||||
public let sendMessage: ((EnginePeer) -> Void)?
|
public let sendMessage: ((EnginePeer) -> Void)?
|
||||||
|
|
||||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, title: String? = nil, mode: ContactMultiselectionControllerMode, options: Signal<[ContactListAdditionalOption], NoError> = .single([]), filters: [ContactListFilter] = [.excludeSelf], onlyWriteable: Bool = false, isGroupInvitation: Bool = false, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)? = nil, alwaysEnabled: Bool = false, limit: Int32? = nil, reachedLimit: ((Int32) -> Void)? = nil, openProfile: ((EnginePeer) -> Void)? = nil, sendMessage: ((EnginePeer) -> Void)? = nil) {
|
public init(
|
||||||
|
context: AccountContext,
|
||||||
|
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
|
||||||
|
title: String? = nil,
|
||||||
|
mode: ContactMultiselectionControllerMode,
|
||||||
|
options: Signal<[ContactListAdditionalOption], NoError> = .single([]),
|
||||||
|
filters: [ContactListFilter] = [.excludeSelf],
|
||||||
|
onlyWriteable: Bool = false,
|
||||||
|
isGroupInvitation: Bool = false,
|
||||||
|
isPeerEnabled: ((EnginePeer) -> Bool)? = nil,
|
||||||
|
attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)? = nil,
|
||||||
|
alwaysEnabled: Bool = false,
|
||||||
|
limit: Int32? = nil,
|
||||||
|
reachedLimit: ((Int32) -> Void)? = nil,
|
||||||
|
openProfile: ((EnginePeer) -> Void)? = nil,
|
||||||
|
sendMessage: ((EnginePeer) -> Void)? = nil
|
||||||
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.updatedPresentationData = updatedPresentationData
|
self.updatedPresentationData = updatedPresentationData
|
||||||
self.title = title
|
self.title = title
|
||||||
|
@ -569,6 +569,7 @@ public protocol PresentationCallManager: AnyObject {
|
|||||||
accountContext: AccountContext,
|
accountContext: AccountContext,
|
||||||
initialCall: EngineGroupCallDescription,
|
initialCall: EngineGroupCallDescription,
|
||||||
reference: InternalGroupCallReference,
|
reference: InternalGroupCallReference,
|
||||||
beginWithVideo: Bool
|
beginWithVideo: Bool,
|
||||||
|
invitePeerIds: [EnginePeer.Id]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -208,7 +208,7 @@ public final class CallListController: TelegramBaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createGroupCall() {
|
private func createGroupCall(peerIds: [EnginePeer.Id], completion: (() -> Void)? = nil) {
|
||||||
self.view.endEditing(true)
|
self.view.endEditing(true)
|
||||||
|
|
||||||
guard !self.presentAccountFrozenInfoIfNeeded() else {
|
guard !self.presentAccountFrozenInfoIfNeeded() else {
|
||||||
@ -274,38 +274,44 @@ public final class CallListController: TelegramBaseController {
|
|||||||
isStream: false
|
isStream: false
|
||||||
),
|
),
|
||||||
reference: .id(id: call.callInfo.id, accessHash: call.callInfo.accessHash),
|
reference: .id(id: call.callInfo.id, accessHash: call.callInfo.accessHash),
|
||||||
beginWithVideo: false
|
beginWithVideo: false,
|
||||||
|
invitePeerIds: peerIds
|
||||||
)
|
)
|
||||||
|
completion?()
|
||||||
}
|
}
|
||||||
|
|
||||||
let controller = InviteLinkInviteController(
|
if !peerIds.isEmpty {
|
||||||
context: self.context,
|
openCall()
|
||||||
updatedPresentationData: nil,
|
} else {
|
||||||
mode: .groupCall(InviteLinkInviteController.Mode.GroupCall(callId: call.callInfo.id, accessHash: call.callInfo.accessHash, isRecentlyCreated: true, canRevoke: true)),
|
let controller = InviteLinkInviteController(
|
||||||
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),
|
context: self.context,
|
||||||
parentNavigationController: self.navigationController as? NavigationController,
|
updatedPresentationData: nil,
|
||||||
completed: { [weak self] result in
|
mode: .groupCall(InviteLinkInviteController.Mode.GroupCall(callId: call.callInfo.id, accessHash: call.callInfo.accessHash, isRecentlyCreated: true, canRevoke: true)),
|
||||||
guard let self else {
|
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),
|
||||||
return
|
parentNavigationController: self.navigationController as? NavigationController,
|
||||||
}
|
completed: { [weak self] result in
|
||||||
if let result {
|
guard let self else {
|
||||||
switch result {
|
return
|
||||||
case .linkCopied:
|
}
|
||||||
//TODO:localize
|
if let result {
|
||||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
switch result {
|
||||||
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
|
case .linkCopied:
|
||||||
if case .undo = action {
|
//TODO:localize
|
||||||
openCall()
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
}
|
self.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: "Call link copied.", customUndoText: "View Call", timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in
|
||||||
return false
|
if case .undo = action {
|
||||||
}), in: .window(.root))
|
openCall()
|
||||||
case .openCall:
|
}
|
||||||
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)
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,7 +401,7 @@ public final class CallListController: TelegramBaseController {
|
|||||||
}
|
}
|
||||||
}, createGroupCall: { [weak self] in
|
}, createGroupCall: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.createGroupCall()
|
strongSelf.createGroupCall(peerIds: [])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -508,21 +514,69 @@ public final class CallListController: TelegramBaseController {
|
|||||||
guard !self.presentAccountFrozenInfoIfNeeded() else {
|
guard !self.presentAccountFrozenInfoIfNeeded() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let controller = self.context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams(context: self.context, title: { $0.Calls_NewCall }, displayCallIcons: true))
|
|
||||||
|
//TODO:localize
|
||||||
|
let options = [ContactListAdditionalOption(title: "New Call Link", icon: .generic(PresentationResourcesItemList.linkIcon(presentationData.theme)!), action: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.createGroupCall(peerIds: [])
|
||||||
|
}, clearHighlightAutomatically: true)]
|
||||||
|
|
||||||
|
let controller = self.context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(
|
||||||
|
context: self.context,
|
||||||
|
title: self.presentationData.strings.Calls_NewCall,
|
||||||
|
mode: .groupCreation(isCall: true),
|
||||||
|
options: .single(options),
|
||||||
|
filters: [.excludeSelf],
|
||||||
|
onlyWriteable: true,
|
||||||
|
isGroupInvitation: false,
|
||||||
|
isPeerEnabled: nil,
|
||||||
|
attemptDisabledItemSelection: nil,
|
||||||
|
alwaysEnabled: false,
|
||||||
|
limit: nil,
|
||||||
|
reachedLimit: nil,
|
||||||
|
openProfile: nil,
|
||||||
|
sendMessage: nil
|
||||||
|
))
|
||||||
controller.navigationPresentation = .modal
|
controller.navigationPresentation = .modal
|
||||||
self.createActionDisposable.set((controller.result
|
if let navigationController = self.context.sharedContext.mainWindow?.viewController as? NavigationController {
|
||||||
|
navigationController.pushViewController(controller)
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = (controller.result
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak controller, weak self] result in
|
|> deliverOnMainQueue).startStandalone(next: { [weak controller, weak self] result in
|
||||||
controller?.dismissSearch()
|
guard let self else {
|
||||||
if let strongSelf = self, let (contactPeers, action, _, _, _, _) = result, let contactPeer = contactPeers.first, case let .peer(peer, _, _) = contactPeer {
|
controller?.dismiss()
|
||||||
strongSelf.call(peer.id, isVideo: action == .videoCall, began: {
|
return
|
||||||
|
}
|
||||||
|
guard case let .result(rawPeerIds, _) = result else {
|
||||||
|
controller?.dismiss()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let peerIds = rawPeerIds.compactMap { id -> EnginePeer.Id? in
|
||||||
|
if case let .peer(id) = id {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if peerIds.isEmpty {
|
||||||
|
controller?.dismiss()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if peerIds.count == 1 {
|
||||||
|
//TODO:release isVideo
|
||||||
|
controller?.dismiss()
|
||||||
|
self.call(peerIds[0], isVideo: false, began: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let _ = (strongSelf.context.sharedContext.hasOngoingCall.get()
|
let _ = (strongSelf.context.sharedContext.hasOngoingCall.get()
|
||||||
|> filter { $0 }
|
|> filter { $0 }
|
||||||
|> timeout(1.0, queue: Queue.mainQueue(), alternate: .single(true))
|
|> timeout(1.0, queue: Queue.mainQueue(), alternate: .single(true))
|
||||||
|> delay(0.5, queue: Queue.mainQueue())
|
|> delay(0.5, queue: Queue.mainQueue())
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).startStandalone(next: { _ in
|
|> deliverOnMainQueue).startStandalone(next: { [weak self] _ in
|
||||||
if let _ = self, let controller = controller, let navigationController = controller.navigationController as? NavigationController {
|
if let _ = self, let controller = controller, let navigationController = controller.navigationController as? NavigationController {
|
||||||
if navigationController.viewControllers.last === controller {
|
if navigationController.viewControllers.last === controller {
|
||||||
let _ = navigationController.popViewController(animated: true)
|
let _ = navigationController.popViewController(animated: true)
|
||||||
@ -531,11 +585,12 @@ public final class CallListController: TelegramBaseController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
self.createGroupCall(peerIds: peerIds, completion: {
|
||||||
|
controller?.dismiss()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}))
|
})
|
||||||
if let navigationController = self.context.sharedContext.mainWindow?.viewController as? NavigationController {
|
|
||||||
navigationController.pushViewController(controller)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentAccountFrozenInfoIfNeeded(delay: Bool = false) -> Bool {
|
private func presentAccountFrozenInfoIfNeeded(delay: Bool = false) -> Bool {
|
||||||
@ -670,7 +725,8 @@ public final class CallListController: TelegramBaseController {
|
|||||||
isStream: false
|
isStream: false
|
||||||
),
|
),
|
||||||
reference: .message(id: message.id),
|
reference: .message(id: message.id),
|
||||||
beginWithVideo: conferenceCall.flags.contains(.isVideo)
|
beginWithVideo: conferenceCall.flags.contains(.isVideo),
|
||||||
|
invitePeerIds: []
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,9 @@ import TelegramPresentationData
|
|||||||
|
|
||||||
public struct CounterControllerTitle: Equatable {
|
public struct CounterControllerTitle: Equatable {
|
||||||
public var title: String
|
public var title: String
|
||||||
public var counter: String
|
public var counter: String?
|
||||||
|
|
||||||
public init(title: String, counter: String) {
|
public init(title: String, counter: String?) {
|
||||||
self.title = title
|
self.title = title
|
||||||
self.counter = counter
|
self.counter = counter
|
||||||
}
|
}
|
||||||
@ -18,7 +18,7 @@ public final class CounterControllerTitleView: UIView {
|
|||||||
private let titleNode: ImmediateTextNode
|
private let titleNode: ImmediateTextNode
|
||||||
private let subtitleNode: ImmediateTextNode
|
private let subtitleNode: ImmediateTextNode
|
||||||
|
|
||||||
public var title: CounterControllerTitle = CounterControllerTitle(title: "", counter: "") {
|
public var title: CounterControllerTitle = CounterControllerTitle(title: "", counter: nil) {
|
||||||
didSet {
|
didSet {
|
||||||
if self.title != oldValue {
|
if self.title != oldValue {
|
||||||
self.update()
|
self.update()
|
||||||
@ -59,7 +59,7 @@ public final class CounterControllerTitleView: UIView {
|
|||||||
let primaryTextColor = self.primaryTextColor ?? self.theme.rootController.navigationBar.primaryTextColor
|
let primaryTextColor = self.primaryTextColor ?? self.theme.rootController.navigationBar.primaryTextColor
|
||||||
let secondaryTextColor = self.secondaryTextColor ?? self.theme.rootController.navigationBar.secondaryTextColor
|
let secondaryTextColor = self.secondaryTextColor ?? self.theme.rootController.navigationBar.secondaryTextColor
|
||||||
self.titleNode.attributedText = NSAttributedString(string: self.title.title, font: Font.semibold(17.0), textColor: primaryTextColor)
|
self.titleNode.attributedText = NSAttributedString(string: self.title.title, font: Font.semibold(17.0), textColor: primaryTextColor)
|
||||||
self.subtitleNode.attributedText = NSAttributedString(string: self.title.counter, font: Font.with(size: 13.0, traits: .monospacedNumbers), textColor: secondaryTextColor)
|
self.subtitleNode.attributedText = NSAttributedString(string: self.title.counter ?? "", font: Font.with(size: 13.0, traits: .monospacedNumbers), textColor: secondaryTextColor)
|
||||||
|
|
||||||
self.accessibilityLabel = self.title.title
|
self.accessibilityLabel = self.title.title
|
||||||
self.accessibilityValue = self.title.counter
|
self.accessibilityValue = self.title.counter
|
||||||
@ -103,7 +103,13 @@ public final class CounterControllerTitleView: UIView {
|
|||||||
|
|
||||||
let titleSize = self.titleNode.updateLayout(CGSize(width: max(1.0, size.width), height: size.height))
|
let titleSize = self.titleNode.updateLayout(CGSize(width: max(1.0, size.width), height: size.height))
|
||||||
let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: max(1.0, size.width), height: size.height))
|
let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: max(1.0, size.width), height: size.height))
|
||||||
let combinedHeight = titleSize.height + subtitleSize.height + spacing
|
|
||||||
|
let combinedHeight: CGFloat
|
||||||
|
if self.title.counter != nil {
|
||||||
|
combinedHeight = titleSize.height + subtitleSize.height + spacing
|
||||||
|
} else {
|
||||||
|
combinedHeight = titleSize.height
|
||||||
|
}
|
||||||
|
|
||||||
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0)), size: titleSize)
|
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0)), size: titleSize)
|
||||||
self.titleNode.frame = titleFrame
|
self.titleNode.frame = titleFrame
|
||||||
|
@ -427,7 +427,7 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
strongSelf.controller?.present(controller, in: .window(.root))
|
strongSelf.controller?.present(controller, in: .window(.root))
|
||||||
})
|
})
|
||||||
} else if case .groupCall = self.mode {
|
} else if case .groupCall = self.mode {
|
||||||
let controller = QrCodeScreen(context: context, updatedPresentationData: (self.presentationData, self.presentationDataPromise.get()), subject: .invite(invite: invite, type: .channel))
|
let controller = QrCodeScreen(context: context, updatedPresentationData: (self.presentationData, self.presentationDataPromise.get()), subject: .invite(invite: invite, type: .groupCall))
|
||||||
self.controller?.present(controller, in: .window(.root))
|
self.controller?.present(controller, in: .window(.root))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1083,7 +1083,8 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
|||||||
accountContext: AccountContext,
|
accountContext: AccountContext,
|
||||||
initialCall: EngineGroupCallDescription,
|
initialCall: EngineGroupCallDescription,
|
||||||
reference: InternalGroupCallReference,
|
reference: InternalGroupCallReference,
|
||||||
beginWithVideo: Bool
|
beginWithVideo: Bool,
|
||||||
|
invitePeerIds: [EnginePeer.Id]
|
||||||
) {
|
) {
|
||||||
let keyPair: TelegramKeyPair
|
let keyPair: TelegramKeyPair
|
||||||
guard let keyPairValue = TelegramE2EEncryptionProviderImpl.shared.generateKeyPair() else {
|
guard let keyPairValue = TelegramE2EEncryptionProviderImpl.shared.generateKeyPair() else {
|
||||||
@ -1109,6 +1110,9 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
|||||||
beginWithVideo: beginWithVideo,
|
beginWithVideo: beginWithVideo,
|
||||||
sharedAudioContext: nil
|
sharedAudioContext: nil
|
||||||
)
|
)
|
||||||
|
for peerId in invitePeerIds {
|
||||||
|
let _ = call.invitePeer(peerId, isVideo: beginWithVideo)
|
||||||
|
}
|
||||||
self.updateCurrentGroupCall(.group(call))
|
self.updateCurrentGroupCall(.group(call))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ public struct UserLimitsConfiguration: Equatable {
|
|||||||
public var maxGiveawayCountriesCount: Int32
|
public var maxGiveawayCountriesCount: Int32
|
||||||
public var maxGiveawayPeriodSeconds: Int32
|
public var maxGiveawayPeriodSeconds: Int32
|
||||||
public var maxChannelRecommendationsCount: Int32
|
public var maxChannelRecommendationsCount: Int32
|
||||||
|
public var maxConferenceParticipantCount: Int32
|
||||||
|
|
||||||
public static var defaultValue: UserLimitsConfiguration {
|
public static var defaultValue: UserLimitsConfiguration {
|
||||||
return UserLimitsConfiguration(
|
return UserLimitsConfiguration(
|
||||||
@ -56,7 +57,8 @@ public struct UserLimitsConfiguration: Equatable {
|
|||||||
maxGiveawayChannelsCount: 10,
|
maxGiveawayChannelsCount: 10,
|
||||||
maxGiveawayCountriesCount: 10,
|
maxGiveawayCountriesCount: 10,
|
||||||
maxGiveawayPeriodSeconds: 86400 * 31,
|
maxGiveawayPeriodSeconds: 86400 * 31,
|
||||||
maxChannelRecommendationsCount: 10
|
maxChannelRecommendationsCount: 10,
|
||||||
|
maxConferenceParticipantCount: 100
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +88,8 @@ public struct UserLimitsConfiguration: Equatable {
|
|||||||
maxGiveawayChannelsCount: Int32,
|
maxGiveawayChannelsCount: Int32,
|
||||||
maxGiveawayCountriesCount: Int32,
|
maxGiveawayCountriesCount: Int32,
|
||||||
maxGiveawayPeriodSeconds: Int32,
|
maxGiveawayPeriodSeconds: Int32,
|
||||||
maxChannelRecommendationsCount: Int32
|
maxChannelRecommendationsCount: Int32,
|
||||||
|
maxConferenceParticipantCount: Int32
|
||||||
) {
|
) {
|
||||||
self.maxPinnedChatCount = maxPinnedChatCount
|
self.maxPinnedChatCount = maxPinnedChatCount
|
||||||
self.maxPinnedSavedChatCount = maxPinnedSavedChatCount
|
self.maxPinnedSavedChatCount = maxPinnedSavedChatCount
|
||||||
@ -114,6 +117,7 @@ public struct UserLimitsConfiguration: Equatable {
|
|||||||
self.maxGiveawayCountriesCount = maxGiveawayCountriesCount
|
self.maxGiveawayCountriesCount = maxGiveawayCountriesCount
|
||||||
self.maxGiveawayPeriodSeconds = maxGiveawayPeriodSeconds
|
self.maxGiveawayPeriodSeconds = maxGiveawayPeriodSeconds
|
||||||
self.maxChannelRecommendationsCount = maxChannelRecommendationsCount
|
self.maxChannelRecommendationsCount = maxChannelRecommendationsCount
|
||||||
|
self.maxConferenceParticipantCount = maxConferenceParticipantCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,5 +171,6 @@ extension UserLimitsConfiguration {
|
|||||||
self.maxGiveawayCountriesCount = getGeneralValue("giveaway_countries_max", orElse: defaultValue.maxGiveawayCountriesCount)
|
self.maxGiveawayCountriesCount = getGeneralValue("giveaway_countries_max", orElse: defaultValue.maxGiveawayCountriesCount)
|
||||||
self.maxGiveawayPeriodSeconds = getGeneralValue("giveaway_period_max", orElse: defaultValue.maxGiveawayPeriodSeconds)
|
self.maxGiveawayPeriodSeconds = getGeneralValue("giveaway_period_max", orElse: defaultValue.maxGiveawayPeriodSeconds)
|
||||||
self.maxChannelRecommendationsCount = getValue("recommended_channels_limit", orElse: defaultValue.maxChannelRecommendationsCount)
|
self.maxChannelRecommendationsCount = getValue("recommended_channels_limit", orElse: defaultValue.maxChannelRecommendationsCount)
|
||||||
|
self.maxConferenceParticipantCount = getGeneralValue("conference_call_size_limit", orElse: defaultValue.maxConferenceParticipantCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,7 @@ public enum EngineConfiguration {
|
|||||||
public let maxGiveawayCountriesCount: Int32
|
public let maxGiveawayCountriesCount: Int32
|
||||||
public let maxGiveawayPeriodSeconds: Int32
|
public let maxGiveawayPeriodSeconds: Int32
|
||||||
public let maxChannelRecommendationsCount: Int32
|
public let maxChannelRecommendationsCount: Int32
|
||||||
|
public let maxConferenceParticipantCount: Int32
|
||||||
|
|
||||||
public static var defaultValue: UserLimits {
|
public static var defaultValue: UserLimits {
|
||||||
return UserLimits(UserLimitsConfiguration.defaultValue)
|
return UserLimits(UserLimitsConfiguration.defaultValue)
|
||||||
@ -93,7 +94,8 @@ public enum EngineConfiguration {
|
|||||||
maxGiveawayChannelsCount: Int32,
|
maxGiveawayChannelsCount: Int32,
|
||||||
maxGiveawayCountriesCount: Int32,
|
maxGiveawayCountriesCount: Int32,
|
||||||
maxGiveawayPeriodSeconds: Int32,
|
maxGiveawayPeriodSeconds: Int32,
|
||||||
maxChannelRecommendationsCount: Int32
|
maxChannelRecommendationsCount: Int32,
|
||||||
|
maxConferenceParticipantCount: Int32
|
||||||
) {
|
) {
|
||||||
self.maxPinnedChatCount = maxPinnedChatCount
|
self.maxPinnedChatCount = maxPinnedChatCount
|
||||||
self.maxPinnedSavedChatCount = maxPinnedSavedChatCount
|
self.maxPinnedSavedChatCount = maxPinnedSavedChatCount
|
||||||
@ -121,6 +123,7 @@ public enum EngineConfiguration {
|
|||||||
self.maxGiveawayCountriesCount = maxGiveawayCountriesCount
|
self.maxGiveawayCountriesCount = maxGiveawayCountriesCount
|
||||||
self.maxGiveawayPeriodSeconds = maxGiveawayPeriodSeconds
|
self.maxGiveawayPeriodSeconds = maxGiveawayPeriodSeconds
|
||||||
self.maxChannelRecommendationsCount = maxChannelRecommendationsCount
|
self.maxChannelRecommendationsCount = maxChannelRecommendationsCount
|
||||||
|
self.maxConferenceParticipantCount = maxConferenceParticipantCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,7 +186,8 @@ public extension EngineConfiguration.UserLimits {
|
|||||||
maxGiveawayChannelsCount: userLimitsConfiguration.maxGiveawayChannelsCount,
|
maxGiveawayChannelsCount: userLimitsConfiguration.maxGiveawayChannelsCount,
|
||||||
maxGiveawayCountriesCount: userLimitsConfiguration.maxGiveawayCountriesCount,
|
maxGiveawayCountriesCount: userLimitsConfiguration.maxGiveawayCountriesCount,
|
||||||
maxGiveawayPeriodSeconds: userLimitsConfiguration.maxGiveawayPeriodSeconds,
|
maxGiveawayPeriodSeconds: userLimitsConfiguration.maxGiveawayPeriodSeconds,
|
||||||
maxChannelRecommendationsCount: userLimitsConfiguration.maxChannelRecommendationsCount
|
maxChannelRecommendationsCount: userLimitsConfiguration.maxChannelRecommendationsCount,
|
||||||
|
maxConferenceParticipantCount: userLimitsConfiguration.maxConferenceParticipantCount
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -469,6 +469,9 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/Settings/AccountFreezeInfoScreen",
|
"//submodules/TelegramUI/Components/Settings/AccountFreezeInfoScreen",
|
||||||
"//submodules/TelegramUI/Components/JoinSubjectScreen",
|
"//submodules/TelegramUI/Components/JoinSubjectScreen",
|
||||||
"//submodules/TelegramUI/Components/Chat/QuickShareScreen",
|
"//submodules/TelegramUI/Components/Chat/QuickShareScreen",
|
||||||
|
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||||
|
"//submodules/Components/BlurredBackgroundComponent",
|
||||||
|
"//submodules/TelegramUI/Components/CheckComponent",
|
||||||
"//third-party/recaptcha:RecaptchaEnterprise",
|
"//third-party/recaptcha:RecaptchaEnterprise",
|
||||||
] + select({
|
] + select({
|
||||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||||
|
@ -405,7 +405,8 @@ private final class JoinSubjectScreenComponent: Component {
|
|||||||
isStream: false
|
isStream: false
|
||||||
),
|
),
|
||||||
reference: .link(slug: groupCall.slug),
|
reference: .link(slug: groupCall.slug),
|
||||||
beginWithVideo: false
|
beginWithVideo: false,
|
||||||
|
invitePeerIds: []
|
||||||
)
|
)
|
||||||
|
|
||||||
self.environment?.controller()?.dismiss()
|
self.environment?.controller()?.dismiss()
|
||||||
|
@ -2919,7 +2919,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
isStream: false
|
isStream: false
|
||||||
),
|
),
|
||||||
reference: .message(id: message.id),
|
reference: .message(id: message.id),
|
||||||
beginWithVideo: conferenceCall.flags.contains(.isVideo)
|
beginWithVideo: conferenceCall.flags.contains(.isVideo),
|
||||||
|
invitePeerIds: []
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}, longTap: { [weak self] action, params in
|
}, longTap: { [weak self] action, params in
|
||||||
|
@ -125,7 +125,7 @@ public class ComposeControllerImpl: ViewController, ComposeController {
|
|||||||
|
|
||||||
self.contactsNode.openCreateNewGroup = { [weak self] in
|
self.contactsNode.openCreateNewGroup = { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let controller = strongSelf.context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: strongSelf.context, mode: .groupCreation, onlyWriteable: true))
|
let controller = strongSelf.context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: strongSelf.context, mode: .groupCreation(isCall: false), onlyWriteable: true))
|
||||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller, completion: { [weak self] in
|
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller, completion: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
|
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
|
||||||
|
@ -241,8 +241,13 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch self.mode {
|
switch self.mode {
|
||||||
case .groupCreation:
|
case let .groupCreation(isCall):
|
||||||
let maxCount: Int32 = self.limitsConfiguration?.maxSupergroupMemberCount ?? 5000
|
let maxCount: Int32
|
||||||
|
if isCall {
|
||||||
|
maxCount = self.context.userLimits.maxConferenceParticipantCount
|
||||||
|
} else {
|
||||||
|
maxCount = self.limitsConfiguration?.maxSupergroupMemberCount ?? 5000
|
||||||
|
}
|
||||||
let count: Int
|
let count: Int
|
||||||
switch self.contactsNode.contentNode {
|
switch self.contactsNode.contentNode {
|
||||||
case let .contacts(contactsNode):
|
case let .contacts(contactsNode):
|
||||||
@ -250,8 +255,12 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
|||||||
case let .chats(chatsNode):
|
case let .chats(chatsNode):
|
||||||
count = chatsNode.currentState.selectedPeerIds.count
|
count = chatsNode.currentState.selectedPeerIds.count
|
||||||
}
|
}
|
||||||
self.titleView.title = CounterControllerTitle(title: self.params.title ?? self.presentationData.strings.Compose_NewGroupTitle, counter: "\(count)/\(maxCount)")
|
if isCall && count == 0 {
|
||||||
if self.rightNavigationButton == nil {
|
self.titleView.title = CounterControllerTitle(title: self.params.title ?? self.presentationData.strings.Compose_NewGroupTitle, counter: nil)
|
||||||
|
} else {
|
||||||
|
self.titleView.title = CounterControllerTitle(title: self.params.title ?? self.presentationData.strings.Compose_NewGroupTitle, counter: "\(count)/\(maxCount)")
|
||||||
|
}
|
||||||
|
if self.rightNavigationButton == nil && !isCall {
|
||||||
let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed))
|
let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed))
|
||||||
self.rightNavigationButton = rightNavigationButton
|
self.rightNavigationButton = rightNavigationButton
|
||||||
self.navigationItem.rightBarButtonItem = self.rightNavigationButton
|
self.navigationItem.rightBarButtonItem = self.rightNavigationButton
|
||||||
@ -349,6 +358,10 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
|||||||
if updatedState.selectedPeerIndices[.peer(peer.id)] == nil {
|
if updatedState.selectedPeerIndices[.peer(peer.id)] == nil {
|
||||||
removedTokenId = peer.id
|
removedTokenId = peer.id
|
||||||
} else {
|
} else {
|
||||||
|
var selectedPeerMap = updatedState.selectedPeerMap
|
||||||
|
selectedPeerMap[.peer(peer.id)] = .peer(peer: peer, isGlobal: false, participantCount: nil)
|
||||||
|
updatedState = updatedState.withSelectedPeerMap(selectedPeerMap)
|
||||||
|
|
||||||
if updatedState.selectedPeerIndices.count >= maxRegularCount {
|
if updatedState.selectedPeerIndices.count >= maxRegularCount {
|
||||||
displayCountAlert = true
|
displayCountAlert = true
|
||||||
updatedState = updatedState.withToggledPeerId(.peer(peer.id))
|
updatedState = updatedState.withToggledPeerId(.peer(peer.id))
|
||||||
@ -538,8 +551,13 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
switch strongSelf.mode {
|
switch strongSelf.mode {
|
||||||
case .groupCreation:
|
case let .groupCreation(isCall):
|
||||||
let maxCount: Int32 = strongSelf.limitsConfiguration?.maxSupergroupMemberCount ?? 5000
|
let maxCount: Int32
|
||||||
|
if isCall {
|
||||||
|
maxCount = strongSelf.context.userLimits.maxConferenceParticipantCount
|
||||||
|
} else {
|
||||||
|
maxCount = strongSelf.limitsConfiguration?.maxSupergroupMemberCount ?? 5000
|
||||||
|
}
|
||||||
strongSelf.titleView.title = CounterControllerTitle(title: strongSelf.presentationData.strings.Compose_NewGroupTitle, counter: "\(updatedCount)/\(maxCount)")
|
strongSelf.titleView.title = CounterControllerTitle(title: strongSelf.presentationData.strings.Compose_NewGroupTitle, counter: "\(updatedCount)/\(maxCount)")
|
||||||
case .premiumGifting:
|
case .premiumGifting:
|
||||||
let maxCount: Int32 = strongSelf.limit ?? 10
|
let maxCount: Int32 = strongSelf.limit ?? 10
|
||||||
|
@ -14,6 +14,7 @@ import MultiAnimationRenderer
|
|||||||
import EditableTokenListNode
|
import EditableTokenListNode
|
||||||
import SolidRoundedButtonNode
|
import SolidRoundedButtonNode
|
||||||
import ContextUI
|
import ContextUI
|
||||||
|
import ComponentFlow
|
||||||
|
|
||||||
private struct SearchResultEntry: Identifiable {
|
private struct SearchResultEntry: Identifiable {
|
||||||
let index: Int
|
let index: Int
|
||||||
@ -53,6 +54,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
|||||||
var searchResultsNode: ContactListNode?
|
var searchResultsNode: ContactListNode?
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
private let mode: ContactMultiselectionControllerMode
|
||||||
|
|
||||||
private var containerLayout: (ContainerViewLayout, CGFloat, CGFloat)?
|
private var containerLayout: (ContainerViewLayout, CGFloat, CGFloat)?
|
||||||
|
|
||||||
@ -81,12 +83,15 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
|||||||
private let isPeerEnabled: ((EnginePeer) -> Bool)?
|
private let isPeerEnabled: ((EnginePeer) -> Bool)?
|
||||||
private let onlyWriteable: Bool
|
private let onlyWriteable: Bool
|
||||||
private let isGroupInvitation: Bool
|
private let isGroupInvitation: Bool
|
||||||
|
|
||||||
|
private var bottomPanel: ComponentView<Empty>?
|
||||||
|
|
||||||
init(navigationBar: NavigationBar?, context: AccountContext, presentationData: PresentationData, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mode: ContactMultiselectionControllerMode, isPeerEnabled: ((EnginePeer) -> Bool)?, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?, options: Signal<[ContactListAdditionalOption], NoError>, filters: [ContactListFilter], onlyWriteable: Bool, isGroupInvitation: Bool, limit: Int32?, reachedSelectionLimit: ((Int32) -> Void)?, present: @escaping (ViewController, Any?) -> Void) {
|
init(navigationBar: NavigationBar?, context: AccountContext, presentationData: PresentationData, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mode: ContactMultiselectionControllerMode, isPeerEnabled: ((EnginePeer) -> Bool)?, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?, options: Signal<[ContactListAdditionalOption], NoError>, filters: [ContactListFilter], onlyWriteable: Bool, isGroupInvitation: Bool, limit: Int32?, reachedSelectionLimit: ((Int32) -> Void)?, present: @escaping (ViewController, Any?) -> Void) {
|
||||||
self.navigationBar = navigationBar
|
self.navigationBar = navigationBar
|
||||||
|
|
||||||
self.context = context
|
self.context = context
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
|
self.mode = mode
|
||||||
|
|
||||||
self.animationCache = context.animationCache
|
self.animationCache = context.animationCache
|
||||||
self.animationRenderer = context.animationRenderer
|
self.animationRenderer = context.animationRenderer
|
||||||
@ -120,6 +125,17 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
|||||||
self.footerPanelNode = FooterPanelNode(theme: self.presentationData.theme, strings: self.presentationData.strings, action: {
|
self.footerPanelNode = FooterPanelNode(theme: self.presentationData.theme, strings: self.presentationData.strings, action: {
|
||||||
proceedImpl?()
|
proceedImpl?()
|
||||||
})
|
})
|
||||||
|
case let .groupCreation(isCall):
|
||||||
|
if isCall {
|
||||||
|
//TODO:localize
|
||||||
|
placeholder = "Search for contacts or usernames"
|
||||||
|
self.footerPanelNode = FooterPanelNode(theme: self.presentationData.theme, strings: self.presentationData.strings, action: {
|
||||||
|
proceedImpl?()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
placeholder = self.presentationData.strings.Compose_TokenListPlaceholder
|
||||||
|
self.footerPanelNode = nil
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
placeholder = self.presentationData.strings.Compose_TokenListPlaceholder
|
placeholder = self.presentationData.strings.Compose_TokenListPlaceholder
|
||||||
self.footerPanelNode = nil
|
self.footerPanelNode = nil
|
||||||
@ -462,7 +478,24 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
|||||||
if case let .contacts(contactListNode) = self.contentNode {
|
if case let .contacts(contactListNode) = self.contentNode {
|
||||||
count = contactListNode.selectionState?.selectedPeerIndices.count ?? 0
|
count = contactListNode.selectionState?.selectedPeerIndices.count ?? 0
|
||||||
}
|
}
|
||||||
footerPanelNode.count = count
|
if case let .groupCreation(isCall) = self.mode, isCall {
|
||||||
|
//TODO:localize
|
||||||
|
if count == 0 {
|
||||||
|
// Don't set anything to prevent state update
|
||||||
|
} else if count <= 1 {
|
||||||
|
let callTitle: String
|
||||||
|
if case let .contacts(contactListNode) = self.contentNode, let peer = contactListNode.selectedPeers.first, case let .peer(peer, _, _) = peer {
|
||||||
|
callTitle = "Call \(EnginePeer(peer).compactDisplayTitle)"
|
||||||
|
} else {
|
||||||
|
callTitle = "Call"
|
||||||
|
}
|
||||||
|
footerPanelNode.content = FooterPanelNode.Content(title: callTitle, badge: "")
|
||||||
|
} else {
|
||||||
|
footerPanelNode.content = FooterPanelNode.Content(title: "Call", badge: "\(count)")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
footerPanelNode.content = FooterPanelNode.Content(title: self.presentationData.strings.Premium_Gift_ContactSelection_Proceed, badge: count == 0 ? "" : "\(count)")
|
||||||
|
}
|
||||||
let panelHeight = footerPanelNode.updateLayout(width: layout.size.width, sideInset: layout.safeInsets.left, bottomInset: headerInsets.bottom, transition: transition)
|
let panelHeight = footerPanelNode.updateLayout(width: layout.size.width, sideInset: layout.safeInsets.left, bottomInset: headerInsets.bottom, transition: transition)
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
transition.updateFrame(node: footerPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: CGSize(width: layout.size.width, height: panelHeight)))
|
transition.updateFrame(node: footerPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: CGSize(width: layout.size.width, height: panelHeight)))
|
||||||
@ -509,6 +542,16 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
|
|
||||||
private final class FooterPanelNode: ASDisplayNode {
|
private final class FooterPanelNode: ASDisplayNode {
|
||||||
|
struct Content: Equatable {
|
||||||
|
let title: String
|
||||||
|
let badge: String
|
||||||
|
|
||||||
|
init(title: String, badge: String) {
|
||||||
|
self.title = title
|
||||||
|
self.badge = badge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private let theme: PresentationTheme
|
private let theme: PresentationTheme
|
||||||
private let strings: PresentationStrings
|
private let strings: PresentationStrings
|
||||||
|
|
||||||
@ -517,11 +560,11 @@ private final class FooterPanelNode: ASDisplayNode {
|
|||||||
|
|
||||||
private var validLayout: (CGFloat, CGFloat, CGFloat)?
|
private var validLayout: (CGFloat, CGFloat, CGFloat)?
|
||||||
|
|
||||||
var count: Int = 0 {
|
var content: Content {
|
||||||
didSet {
|
didSet {
|
||||||
if self.count != oldValue && self.count > 0 {
|
if self.content != oldValue {
|
||||||
self.button.title = self.strings.Premium_Gift_ContactSelection_Proceed
|
self.button.title = content.title
|
||||||
self.button.badge = "\(self.count)"
|
self.button.badge = content.badge.isEmpty ? nil : content.badge
|
||||||
|
|
||||||
if let (width, sideInset, bottomInset) = self.validLayout {
|
if let (width, sideInset, bottomInset) = self.validLayout {
|
||||||
let _ = self.updateLayout(width: width, sideInset: sideInset, bottomInset: bottomInset, transition: .immediate)
|
let _ = self.updateLayout(width: width, sideInset: sideInset, bottomInset: bottomInset, transition: .immediate)
|
||||||
@ -538,6 +581,8 @@ private final class FooterPanelNode: ASDisplayNode {
|
|||||||
self.separatorNode.backgroundColor = theme.rootController.navigationBar.separatorColor
|
self.separatorNode.backgroundColor = theme.rootController.navigationBar.separatorColor
|
||||||
|
|
||||||
self.button = SolidRoundedButtonView(theme: SolidRoundedButtonTheme(theme: theme), height: 48.0, cornerRadius: 10.0)
|
self.button = SolidRoundedButtonView(theme: SolidRoundedButtonTheme(theme: theme), height: 48.0, cornerRadius: 10.0)
|
||||||
|
|
||||||
|
self.content = Content(title: self.strings.Premium_Gift_ContactSelection_Proceed, badge: "")
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user