Group invitation sheet

This commit is contained in:
Isaac 2024-03-26 15:57:00 +04:00
parent 4a8a14cb9f
commit 38bc517122
21 changed files with 427 additions and 366 deletions

View File

@ -96,19 +96,21 @@ public final class ContactMultiselectionControllerParams {
public let options: [ContactListAdditionalOption] public let options: [ContactListAdditionalOption]
public let filters: [ContactListFilter] public let filters: [ContactListFilter]
public let onlyWriteable: Bool public let onlyWriteable: Bool
public let isGroupInvitation: Bool
public let isPeerEnabled: ((EnginePeer) -> Bool)? public let isPeerEnabled: ((EnginePeer) -> Bool)?
public let attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)? public let attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?
public let alwaysEnabled: Bool public let alwaysEnabled: Bool
public let limit: Int32? public let limit: Int32?
public let reachedLimit: ((Int32) -> Void)? public let reachedLimit: ((Int32) -> Void)?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf], onlyWriteable: Bool = false, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)? = nil, alwaysEnabled: Bool = false, limit: Int32? = nil, reachedLimit: ((Int32) -> Void)? = nil) { public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], 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) {
self.context = context self.context = context
self.updatedPresentationData = updatedPresentationData self.updatedPresentationData = updatedPresentationData
self.mode = mode self.mode = mode
self.options = options self.options = options
self.filters = filters self.filters = filters
self.onlyWriteable = onlyWriteable self.onlyWriteable = onlyWriteable
self.isGroupInvitation = isGroupInvitation
self.isPeerEnabled = isPeerEnabled self.isPeerEnabled = isPeerEnabled
self.attemptDisabledItemSelection = attemptDisabledItemSelection self.attemptDisabledItemSelection = attemptDisabledItemSelection
self.alwaysEnabled = alwaysEnabled self.alwaysEnabled = alwaysEnabled

View File

@ -1059,7 +1059,7 @@ public final class ContactListNode: ASDisplayNode {
private let isPeerEnabled: ((EnginePeer) -> Bool)? private let isPeerEnabled: ((EnginePeer) -> Bool)?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, presentation: Signal<ContactListPresentation, NoError>, filters: [ContactListFilter] = [.excludeSelf], onlyWriteable: Bool, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, selectionState: ContactListNodeGroupSelectionState? = nil, displayPermissionPlaceholder: Bool = true, displaySortOptions: Bool = false, displayCallIcons: Bool = false, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?, Bool) -> Void)? = nil, isSearch: Bool = false, multipleSelection: Bool = false) { public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, presentation: Signal<ContactListPresentation, NoError>, filters: [ContactListFilter] = [.excludeSelf], onlyWriteable: Bool, isGroupInvitation: Bool, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, selectionState: ContactListNodeGroupSelectionState? = nil, displayPermissionPlaceholder: Bool = true, displaySortOptions: Bool = false, displayCallIcons: Bool = false, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?, Bool) -> Void)? = nil, isSearch: Bool = false, multipleSelection: Bool = false) {
self.context = context self.context = context
self.filters = filters self.filters = filters
self.displayPermissionPlaceholder = displayPermissionPlaceholder self.displayPermissionPlaceholder = displayPermissionPlaceholder
@ -1383,7 +1383,7 @@ public final class ContactListNode: ASDisplayNode {
}) })
let peerRequiresPremiumForMessaging: Signal<[EnginePeer.Id: Bool], NoError> let peerRequiresPremiumForMessaging: Signal<[EnginePeer.Id: Bool], NoError>
if onlyWriteable { if onlyWriteable && !isGroupInvitation {
peerRequiresPremiumForMessaging = foundPeers.get() peerRequiresPremiumForMessaging = foundPeers.get()
|> map { foundPeers -> Set<EnginePeer.Id> in |> map { foundPeers -> Set<EnginePeer.Id> in
var result = Set<EnginePeer.Id>() var result = Set<EnginePeer.Id>()

View File

@ -114,7 +114,7 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
var contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?, Bool) -> Void)? var contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?, Bool) -> Void)?
self.contactListNode = ContactListNode(context: context, presentation: presentation, onlyWriteable: false, displaySortOptions: true, contextAction: { peer, node, gesture, location, isStories in self.contactListNode = ContactListNode(context: context, presentation: presentation, onlyWriteable: false, isGroupInvitation: false, displaySortOptions: true, contextAction: { peer, node, gesture, location, isStories in
contextAction?(peer, node, gesture, location, isStories) contextAction?(peer, node, gesture, location, isStories)
}) })

View File

@ -1244,8 +1244,8 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
return current.withUpdatedUpdating(false) return current.withUpdatedUpdating(false)
} }
if let adminPeer { if let adminPeer, case let .restricted(forbiddenPeer) = error {
let inviteScreen = SendInviteLinkScreen(context: context, peer: channelPeer, link: exportedInvitation?.link, peers: [adminPeer]) let inviteScreen = SendInviteLinkScreen(context: context, peer: channelPeer, link: exportedInvitation?.link, peers: [forbiddenPeer ?? TelegramForbiddenInvitePeer(peer: adminPeer, canInviteWithPremium: false, premiumRequiredToContact: false)])
pushControllerImpl?(inviteScreen) pushControllerImpl?(inviteScreen)
dismissImpl?() dismissImpl?()
@ -1433,9 +1433,9 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
return current.withUpdatedUpdating(true) return current.withUpdatedUpdating(true)
} }
updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(engine: context.engine, peerId: peerId, memberId: adminId, adminRights: TelegramChatAdminRights(rights: updateFlags), rank: updateRank) |> deliverOnMainQueue).start(error: { error in updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(engine: context.engine, peerId: peerId, memberId: adminId, adminRights: TelegramChatAdminRights(rights: updateFlags), rank: updateRank) |> deliverOnMainQueue).start(error: { error in
if case let .addMemberError(addMemberError) = error, let admin = adminPeer { if case let .addMemberError(addMemberError) = error, case let .restricted(forbiddenPeer) = addMemberError, let admin = adminPeer {
if let channelPeer { if let channelPeer {
let inviteScreen = SendInviteLinkScreen(context: context, peer: channelPeer, link: exportedInvitation?.link, peers: [admin]) let inviteScreen = SendInviteLinkScreen(context: context, peer: channelPeer, link: exportedInvitation?.link, peers: [forbiddenPeer ?? TelegramForbiddenInvitePeer(peer: admin, canInviteWithPremium: false, premiumRequiredToContact: false)])
pushControllerImpl?(inviteScreen) pushControllerImpl?(inviteScreen)
dismissImpl?() dismissImpl?()

View File

@ -549,25 +549,25 @@ public func channelMembersController(context: AccountContext, updatedPresentatio
contactsController?.dismiss() contactsController?.dismiss()
} else { } else {
if let chatPeer { if let chatPeer {
let _ = (context.engine.data.get( let failedPeers = failedPeerIds.compactMap { _, error -> TelegramForbiddenInvitePeer? in
EngineDataList(failedPeerIds.compactMap { item -> EnginePeer.Id? in if case let .restricted(peer) = error {
return item.0 return peer
}.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
)
|> deliverOnMainQueue).start(next: { peerItems in
let peers = peerItems.compactMap { $0 }
if !peers.isEmpty, let contactsController, let navigationController = contactsController.navigationController as? NavigationController {
var viewControllers = navigationController.viewControllers
if let index = viewControllers.firstIndex(where: { $0 === contactsController }) {
let inviteScreen = SendInviteLinkScreen(context: context, peer: chatPeer, link: exportedInvitation?.link, peers: peers)
viewControllers.remove(at: index)
viewControllers.append(inviteScreen)
navigationController.setViewControllers(viewControllers, animated: true)
}
} else { } else {
contactsController?.dismiss() return nil
} }
}) }
if !failedPeers.isEmpty, let contactsController, let navigationController = contactsController.navigationController as? NavigationController {
var viewControllers = navigationController.viewControllers
if let index = viewControllers.firstIndex(where: { $0 === contactsController }) {
let inviteScreen = SendInviteLinkScreen(context: context, peer: chatPeer, link: exportedInvitation?.link, peers: failedPeers)
viewControllers.remove(at: index)
viewControllers.append(inviteScreen)
navigationController.setViewControllers(viewControllers, animated: true)
}
} else {
contactsController?.dismiss()
}
return return
} }

View File

@ -4,15 +4,50 @@ import SwiftSignalKit
import TelegramApi import TelegramApi
import MtProtoKit import MtProtoKit
public enum AddGroupMemberError { public enum AddGroupMemberError {
case generic case generic
case groupFull case groupFull
case privacy case privacy(TelegramInvitePeersResult?)
case notMutualContact case notMutualContact
case tooManyChannels case tooManyChannels
} }
public final class TelegramForbiddenInvitePeer: Equatable {
public let peer: EnginePeer
public let canInviteWithPremium: Bool
public let premiumRequiredToContact: Bool
public init(peer: EnginePeer, canInviteWithPremium: Bool, premiumRequiredToContact: Bool) {
self.peer = peer
self.canInviteWithPremium = canInviteWithPremium
self.premiumRequiredToContact = premiumRequiredToContact
}
public static func ==(lhs: TelegramForbiddenInvitePeer, rhs: TelegramForbiddenInvitePeer) -> Bool {
if lhs === rhs {
return true
}
if lhs.peer != rhs.peer {
return false
}
if lhs.canInviteWithPremium != rhs.canInviteWithPremium {
return false
}
if lhs.premiumRequiredToContact != rhs.premiumRequiredToContact {
return false
}
return true
}
}
public final class TelegramInvitePeersResult {
public let forbiddenPeers: [TelegramForbiddenInvitePeer]
public init(forbiddenPeers: [TelegramForbiddenInvitePeer]) {
self.forbiddenPeers = forbiddenPeers
}
}
func _internal_addGroupMember(account: Account, peerId: PeerId, memberId: PeerId) -> Signal<Void, AddGroupMemberError> { func _internal_addGroupMember(account: Account, peerId: PeerId, memberId: PeerId) -> Signal<Void, AddGroupMemberError> {
return account.postbox.transaction { transaction -> Signal<Void, AddGroupMemberError> in return account.postbox.transaction { transaction -> Signal<Void, AddGroupMemberError> in
if let peer = transaction.getPeer(peerId), let memberPeer = transaction.getPeer(memberId), let inputUser = apiInputUser(memberPeer) { if let peer = transaction.getPeer(peerId), let memberPeer = transaction.getPeer(memberId), let inputUser = apiInputUser(memberPeer) {
@ -23,7 +58,7 @@ func _internal_addGroupMember(account: Account, peerId: PeerId, memberId: PeerId
case "USERS_TOO_MUCH": case "USERS_TOO_MUCH":
return .groupFull return .groupFull
case "USER_PRIVACY_RESTRICTED": case "USER_PRIVACY_RESTRICTED":
return .privacy return .privacy(nil)
case "USER_CHANNELS_TOO_MUCH": case "USER_CHANNELS_TOO_MUCH":
return .tooManyChannels return .tooManyChannels
case "USER_NOT_MUTUAL_CONTACT": case "USER_NOT_MUTUAL_CONTACT":
@ -34,14 +69,16 @@ func _internal_addGroupMember(account: Account, peerId: PeerId, memberId: PeerId
} }
|> mapToSignal { result -> Signal<Void, AddGroupMemberError> in |> mapToSignal { result -> Signal<Void, AddGroupMemberError> in
let updatesValue: Api.Updates let updatesValue: Api.Updates
let missingInviteesValue: [Api.MissingInvitee]
switch result { switch result {
case let .invitedUsers(updates, missingInvitees): case let .invitedUsers(updates, missingInvitees):
let _ = missingInvitees
updatesValue = updates updatesValue = updates
missingInviteesValue = missingInvitees
} }
account.stateManager.addUpdates(updatesValue) account.stateManager.addUpdates(updatesValue)
return account.postbox.transaction { transaction -> Void in
return account.postbox.transaction { transaction -> TelegramInvitePeersResult in
if let message = updatesValue.messages.first, let timestamp = message.timestamp { if let message = updatesValue.messages.first, let timestamp = message.timestamp {
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
if let cachedData = cachedData as? CachedGroupData, let participants = cachedData.participants { if let cachedData = cachedData as? CachedGroupData, let participants = cachedData.participants {
@ -62,8 +99,29 @@ func _internal_addGroupMember(account: Account, peerId: PeerId, memberId: PeerId
} }
}) })
} }
return TelegramInvitePeersResult(forbiddenPeers: missingInviteesValue.compactMap { invitee -> TelegramForbiddenInvitePeer? in
switch invitee {
case let .missingInvitee(flags, userId):
guard let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))) else {
return nil
}
return TelegramForbiddenInvitePeer(
peer: EnginePeer(peer),
canInviteWithPremium: (flags & (1 << 0)) != 0,
premiumRequiredToContact: (flags & (1 << 1)) != 0
)
}
})
} }
|> mapError { _ -> AddGroupMemberError in } |> mapError { _ -> AddGroupMemberError in }
|> mapToSignal { result -> Signal<Void, AddGroupMemberError> in
if result.forbiddenPeers.isEmpty {
return .single(Void())
} else {
return .fail(.privacy(result))
}
}
} }
} else { } else {
return .fail(.generic) return .fail(.generic)
@ -76,7 +134,7 @@ func _internal_addGroupMember(account: Account, peerId: PeerId, memberId: PeerId
public enum AddChannelMemberError { public enum AddChannelMemberError {
case generic case generic
case restricted case restricted(TelegramForbiddenInvitePeer?)
case notMutualContact case notMutualContact
case limitExceeded case limitExceeded
case tooMuchJoined case tooMuchJoined
@ -108,7 +166,7 @@ func _internal_addChannelMember(account: Account, peerId: PeerId, memberId: Peer
case "USERS_TOO_MUCH": case "USERS_TOO_MUCH":
return .fail(.limitExceeded) return .fail(.limitExceeded)
case "USER_PRIVACY_RESTRICTED": case "USER_PRIVACY_RESTRICTED":
return .fail(.restricted) return .fail(.restricted(nil))
case "USER_NOT_MUTUAL_CONTACT": case "USER_NOT_MUTUAL_CONTACT":
return .fail(.notMutualContact) return .fail(.notMutualContact)
case "USER_BOT": case "USER_BOT":
@ -127,7 +185,14 @@ func _internal_addChannelMember(account: Account, peerId: PeerId, memberId: Peer
let updatesValue: Api.Updates let updatesValue: Api.Updates
switch result { switch result {
case let .invitedUsers(updates, missingInvitees): case let .invitedUsers(updates, missingInvitees):
let _ = missingInvitees if case let .missingInvitee(flags, _) = missingInvitees.first {
return .fail(.restricted(TelegramForbiddenInvitePeer(
peer: EnginePeer(memberPeer),
canInviteWithPremium: (flags & (1 << 0)) != 0,
premiumRequiredToContact: (flags & (1 << 1)) != 0
)))
}
updatesValue = updates updatesValue = updates
} }
@ -193,8 +258,8 @@ func _internal_addChannelMember(account: Account, peerId: PeerId, memberId: Peer
} }
} }
func _internal_addChannelMembers(account: Account, peerId: PeerId, memberIds: [PeerId]) -> Signal<Void, AddChannelMemberError> { func _internal_addChannelMembers(account: Account, peerId: PeerId, memberIds: [PeerId]) -> Signal<TelegramInvitePeersResult, AddChannelMemberError> {
let signal = account.postbox.transaction { transaction -> Signal<Void, AddChannelMemberError> in let signal = account.postbox.transaction { transaction -> Signal<TelegramInvitePeersResult, AddChannelMemberError> in
var memberPeerIds: [PeerId:Peer] = [:] var memberPeerIds: [PeerId:Peer] = [:]
var inputUsers: [Api.InputUser] = [] var inputUsers: [Api.InputUser] = []
for memberId in memberIds { for memberId in memberIds {
@ -207,13 +272,13 @@ func _internal_addChannelMembers(account: Account, peerId: PeerId, memberIds: [P
} }
if let peer = transaction.getPeer(peerId), let channel = peer as? TelegramChannel, let inputChannel = apiInputChannel(channel) { if let peer = transaction.getPeer(peerId), let channel = peer as? TelegramChannel, let inputChannel = apiInputChannel(channel) {
let signal = account.network.request(Api.functions.channels.inviteToChannel(channel: inputChannel, users: inputUsers)) let signal: Signal<TelegramInvitePeersResult, AddChannelMemberError> = account.network.request(Api.functions.channels.inviteToChannel(channel: inputChannel, users: inputUsers))
|> mapError { error -> AddChannelMemberError in |> mapError { error -> AddChannelMemberError in
switch error.errorDescription { switch error.errorDescription {
case "CHANNELS_TOO_MUCH": case "CHANNELS_TOO_MUCH":
return .tooMuchJoined return .tooMuchJoined
case "USER_PRIVACY_RESTRICTED": case "USER_PRIVACY_RESTRICTED":
return .restricted return .restricted(nil)
case "USER_NOT_MUTUAL_CONTACT": case "USER_NOT_MUTUAL_CONTACT":
return .notMutualContact return .notMutualContact
case "USERS_TOO_MUCH": case "USERS_TOO_MUCH":
@ -224,21 +289,39 @@ func _internal_addChannelMembers(account: Account, peerId: PeerId, memberIds: [P
return .generic return .generic
} }
} }
|> map { result in |> mapToQueue { result -> Signal<TelegramInvitePeersResult, AddChannelMemberError> in
let updatesValue: Api.Updates let updatesValue: Api.Updates
let missingInviteesValue: [Api.MissingInvitee]
switch result { switch result {
case let .invitedUsers(updates, missingInvitees): case let .invitedUsers(updates, missingInvitees):
let _ = missingInvitees
updatesValue = updates updatesValue = updates
missingInviteesValue = missingInvitees
} }
account.stateManager.addUpdates(updatesValue) account.stateManager.addUpdates(updatesValue)
account.viewTracker.forceUpdateCachedPeerData(peerId: peerId) account.viewTracker.forceUpdateCachedPeerData(peerId: peerId)
return account.postbox.transaction { transaction -> TelegramInvitePeersResult in
return TelegramInvitePeersResult(forbiddenPeers: missingInviteesValue.compactMap { invitee -> TelegramForbiddenInvitePeer? in
switch invitee {
case let .missingInvitee(flags, userId):
guard let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))) else {
return nil
}
return TelegramForbiddenInvitePeer(
peer: EnginePeer(peer),
canInviteWithPremium: (flags & (1 << 0)) != 0,
premiumRequiredToContact: (flags & (1 << 1)) != 0
)
}
})
}
|> castError(AddChannelMemberError.self)
} }
return signal return signal
} else { } else {
return .single(Void()) return .fail(.generic)
} }
} }

View File

@ -15,14 +15,14 @@ public enum CreateGroupError {
public struct CreateGroupResult { public struct CreateGroupResult {
public var peerId: EnginePeer.Id public var peerId: EnginePeer.Id
public var failedToInvitePeerIds: [EnginePeer.Id] public var result: TelegramInvitePeersResult
public init( public init(
peerId: EnginePeer.Id, peerId: EnginePeer.Id,
failedToInvitePeerIds: [EnginePeer.Id] result: TelegramInvitePeersResult
) { ) {
self.peerId = peerId self.peerId = peerId
self.failedToInvitePeerIds = failedToInvitePeerIds self.result = result
} }
} }
@ -50,14 +50,12 @@ func _internal_createGroup(account: Account, title: String, peerIds: [PeerId], t
return .generic return .generic
} }
|> mapToSignal { result -> Signal<CreateGroupResult?, CreateGroupError> in |> mapToSignal { result -> Signal<CreateGroupResult?, CreateGroupError> in
var failedToInvitePeerIds: [EnginePeer.Id] = []
failedToInvitePeerIds = []
let updatesValue: Api.Updates let updatesValue: Api.Updates
let missingInviteesValue: [Api.MissingInvitee]
switch result { switch result {
case let .invitedUsers(updates, missingInvitees): case let .invitedUsers(updates, missingInvitees):
let _ = missingInvitees
updatesValue = updates updatesValue = updates
missingInviteesValue = missingInvitees
} }
account.stateManager.addUpdates(updatesValue) account.stateManager.addUpdates(updatesValue)
@ -68,11 +66,26 @@ func _internal_createGroup(account: Account, title: String, peerIds: [PeerId], t
} }
|> take(1) |> take(1)
|> castError(CreateGroupError.self) |> castError(CreateGroupError.self)
|> map { _ -> CreateGroupResult in |> mapToSignal { _ -> Signal<CreateGroupResult?, CreateGroupError> in
return CreateGroupResult( return account.postbox.transaction { transaction -> CreateGroupResult in
peerId: peerId, return CreateGroupResult(
failedToInvitePeerIds: failedToInvitePeerIds peerId: peerId,
) result: TelegramInvitePeersResult(forbiddenPeers: missingInviteesValue.compactMap { invitee -> TelegramForbiddenInvitePeer? in
switch invitee {
case let .missingInvitee(flags, userId):
guard let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))) else {
return nil
}
return TelegramForbiddenInvitePeer(
peer: EnginePeer(peer),
canInviteWithPremium: (flags & (1 << 0)) != 0,
premiumRequiredToContact: (flags & (1 << 1)) != 0
)
}
})
)
}
|> castError(CreateGroupError.self)
} }
} else { } else {
return .single(nil) return .single(nil)

View File

@ -78,7 +78,7 @@ func _internal_addGroupAdmin(account: Account, peerId: PeerId, adminId: PeerId)
} }
) )
} else if error.errorDescription == "USER_PRIVACY_RESTRICTED" { } else if error.errorDescription == "USER_PRIVACY_RESTRICTED" {
return .fail(.addMemberError(.privacy)) return .fail(.addMemberError(.privacy(nil)))
} else if error.errorDescription == "ADMINS_TOO_MUCH" { } else if error.errorDescription == "ADMINS_TOO_MUCH" {
return .fail(.adminsTooMuch) return .fail(.adminsTooMuch)
} }
@ -204,7 +204,7 @@ func _internal_updateChannelAdminRights(account: Account, peerId: PeerId, adminI
} else if error.errorDescription == "USER_NOT_MUTUAL_CONTACT" { } else if error.errorDescription == "USER_NOT_MUTUAL_CONTACT" {
return .fail(.addMemberError(.notMutualContact)) return .fail(.addMemberError(.notMutualContact))
} else if error.errorDescription == "USER_PRIVACY_RESTRICTED" { } else if error.errorDescription == "USER_PRIVACY_RESTRICTED" {
return .fail(.addMemberError(.restricted)) return .fail(.addMemberError(.restricted(nil)))
} else if error.errorDescription == "USER_CHANNELS_TOO_MUCH" { } else if error.errorDescription == "USER_CHANNELS_TOO_MUCH" {
return .fail(.addMemberError(.tooMuchJoined)) return .fail(.addMemberError(.tooMuchJoined))
} else if error.errorDescription == "ADMINS_TOO_MUCH" { } else if error.errorDescription == "ADMINS_TOO_MUCH" {

View File

@ -589,7 +589,7 @@ public extension TelegramEngine {
return _internal_sendBotRequestedPeer(account: self.account, peerId: messageId.peerId, messageId: messageId, buttonId: buttonId, requestedPeerIds: requestedPeerIds) return _internal_sendBotRequestedPeer(account: self.account, peerId: messageId.peerId, messageId: messageId, buttonId: buttonId, requestedPeerIds: requestedPeerIds)
} }
public func addChannelMembers(peerId: PeerId, memberIds: [PeerId]) -> Signal<Void, AddChannelMemberError> { public func addChannelMembers(peerId: PeerId, memberIds: [PeerId]) -> Signal<TelegramInvitePeersResult, AddChannelMemberError> {
return _internal_addChannelMembers(account: self.account, peerId: peerId, memberIds: memberIds) return _internal_addChannelMembers(account: self.account, peerId: peerId, memberIds: memberIds)
} }

View File

@ -316,6 +316,7 @@ public enum PresentationResourceKey: Int32 {
case sharedLinkIcon case sharedLinkIcon
case hideIconImage case hideIconImage
case peerStatusLockedImage
} }
public enum ChatExpiredStoryIndicatorType: Hashable { public enum ChatExpiredStoryIndicatorType: Hashable {

View File

@ -408,4 +408,10 @@ public struct PresentationResourcesItemList {
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/Archive/IconHide"), color: theme.list.itemAccentColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat List/Archive/IconHide"), color: theme.list.itemAccentColor)
}) })
} }
public static func peerStatusLockedImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.peerStatusLockedImage.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/SmallLock"), color: theme.list.itemSecondaryTextColor)
})
}
} }

View File

@ -274,6 +274,9 @@ private final class PeerInfoMembersContextImpl {
deinit { deinit {
self.disposable.dispose() self.disposable.dispose()
self.peerDisposable.dispose() self.peerDisposable.dispose()
for (_, disposable) in self.removingMemberIds {
disposable.dispose()
}
} }
private func pushState() { private func pushState() {
@ -293,7 +296,7 @@ private final class PeerInfoMembersContextImpl {
} }
func removeMember(memberId: PeerId) { func removeMember(memberId: PeerId) {
if removingMemberIds[memberId] == nil { if self.removingMemberIds[memberId] == nil {
let signal: Signal<Never, NoError> let signal: Signal<Never, NoError>
if self.peerId.namespace == Namespaces.Peer.CloudChannel { if self.peerId.namespace == Namespaces.Peer.CloudChannel {
signal = context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: self.context.engine, peerId: self.peerId, memberId: memberId, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max)) signal = context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: self.context.engine, peerId: self.peerId, memberId: memberId, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max))

View File

@ -12424,20 +12424,8 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa
}, clearHighlightAutomatically: true)) }, clearHighlightAutomatically: true))
} }
let contactsController: ViewController let contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), options: options, filters: [.excludeSelf, .disable(recentIds)], onlyWriteable: true, isGroupInvitation: true))
/*if groupPeer.id.namespace == Namespaces.Peer.CloudGroup {
contactsController = context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, autoDismiss: false, title: { $0.GroupInfo_AddParticipantTitle }, options: options, confirmation: { peer in
if let confirmationImpl = confirmationImpl, case let .peer(peer, _, _) = peer {
return confirmationImpl(peer.id)
} else {
return .single(false)
}
}))
contactsController.navigationPresentation = .modal contactsController.navigationPresentation = .modal
} else {*/
contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), options: options, filters: [.excludeSelf, .disable(recentIds)], onlyWriteable: true))
contactsController.navigationPresentation = .modal
//}
confirmationImpl = { [weak contactsController] peerId in confirmationImpl = { [weak contactsController] peerId in
return context.account.postbox.loadedPeerWithId(peerId) return context.account.postbox.loadedPeerWithId(peerId)
@ -12461,162 +12449,6 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa
} }
} }
/*let addMember: (ContactListPeer) -> Signal<Void, NoError> = { [weak contactsController] memberPeer -> Signal<Void, NoError> in
if case let .peer(selectedPeer, _, _) = memberPeer {
let memberId = selectedPeer.id
if groupPeer.id.namespace == Namespaces.Peer.CloudChannel {
return context.peerChannelMemberCategoriesContextsManager.addMember(engine: context.engine, peerId: groupPeer.id, memberId: memberId)
|> map { _ -> Void in
}
|> `catch` { error -> Signal<Void, NoError> in
let text: String
switch error {
case .limitExceeded:
text = presentationData.strings.Channel_ErrorAddTooMuch
case .tooMuchJoined:
text = presentationData.strings.Invite_ChannelsTooMuch
case .generic:
text = presentationData.strings.Login_UnknownError
case .restricted:
text = presentationData.strings.Channel_ErrorAddBlocked
case .notMutualContact:
if let peer = groupPeer as? TelegramChannel, case .broadcast = peer.info {
text = presentationData.strings.Channel_AddUserLeftError
} else {
text = presentationData.strings.GroupInfo_AddUserLeftError
}
case let .bot(memberId):
guard let peer = groupPeer as? TelegramChannel else {
parentController?.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
contactsController?.dismiss()
return .complete()
}
if peer.hasPermission(.addAdmins) {
parentController?.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Channel_AddBotErrorHaveRights, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Channel_AddBotAsAdmin, action: {
contactsController?.dismiss()
parentController?.push(channelAdminController(context: context, updatedPresentationData: updatedPresentationData, peerId: groupPeer.id, adminId: memberId, initialParticipant: nil, updated: { _ in
}, upgradedToSupergroup: { _, f in f () }, transferedOwnership: { _ in }))
})]), in: .window(.root))
} else {
parentController?.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Channel_AddBotErrorHaveRights, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
contactsController?.dismiss()
return .complete()
case .botDoesntSupportGroups:
text = presentationData.strings.Channel_BotDoesntSupportGroups
case .tooMuchBots:
text = presentationData.strings.Channel_TooMuchBots
case .kicked:
text = presentationData.strings.Channel_AddUserKickedError
}
parentController?.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
return .complete()
}
} else {
return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: groupPeer.id))
|> mapToSignal { exportedInvitation in
return context.engine.peers.addGroupMember(peerId: groupPeer.id, memberId: memberId)
|> deliverOnMainQueue
|> `catch` { error -> Signal<Void, NoError> in
if let exportedInvitation, let link = exportedInvitation.link {
let failedPeerIds: [(PeerId, AddGroupMemberError)] = [(memberId, error)]
let _ = (context.engine.data.get(
EngineDataList(failedPeerIds.compactMap { item -> EnginePeer.Id? in
return item.0
}.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
)
|> deliverOnMainQueue).start(next: { peerItems in
let peers = peerItems.compactMap { $0 }
if !peers.isEmpty, let contactsController, let navigationController = contactsController.navigationController as? NavigationController {
var viewControllers = navigationController.viewControllers
let inviteScreen = SendInviteLinkScreen(context: context, link: link, peers: peers)
if let index = viewControllers.firstIndex(where: { $0 === contactsController }) {
viewControllers.remove(at: index)
viewControllers.append(inviteScreen)
navigationController.setViewControllers(viewControllers, animated: true)
} else {
navigationController.pushViewController(inviteScreen)
}
} else {
contactsController?.dismiss()
}
})
return .complete()
}
switch error {
case .generic:
return .complete()
case .privacy:
let _ = (context.account.postbox.loadedPeerWithId(memberId)
|> deliverOnMainQueue).start(next: { peer in
parentController?.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(EnginePeer(peer).compactDisplayTitle, EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
})
return .complete()
case .notMutualContact:
let _ = (context.account.postbox.loadedPeerWithId(memberId)
|> deliverOnMainQueue).start(next: { peer in
let text: String
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
text = presentationData.strings.Channel_AddUserLeftError
} else {
text = presentationData.strings.GroupInfo_AddUserLeftError
}
parentController?.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
})
return .complete()
case .tooManyChannels:
let _ = (context.account.postbox.loadedPeerWithId(memberId)
|> deliverOnMainQueue).start(next: { peer in
parentController?.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Invite_ChannelsTooMuch, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
})
return .complete()
case .groupFull:
let signal = context.engine.peers.convertGroupToSupergroup(peerId: groupPeer.id)
|> map(Optional.init)
|> `catch` { error -> Signal<PeerId?, NoError> in
switch error {
case .tooManyChannels:
Queue.mainQueue().async {
parentController?.push(oldChannelsController(context: context, intent: .upgrade))
}
default:
break
}
return .single(nil)
}
|> mapToSignal { upgradedPeerId -> Signal<PeerId?, NoError> in
guard let upgradedPeerId = upgradedPeerId else {
return .single(nil)
}
return context.peerChannelMemberCategoriesContextsManager.addMember(engine: context.engine, peerId: upgradedPeerId, memberId: memberId)
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
}
|> mapToSignal { _ -> Signal<PeerId?, NoError> in
}
|> then(.single(upgradedPeerId))
}
|> deliverOnMainQueue
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
return signal
}
}
}
}
} else {
return .complete()
}
}*/
let addMembers: ([ContactListPeerId]) -> Signal<[(PeerId, AddChannelMemberError)], NoError> = { members -> Signal<[(PeerId, AddChannelMemberError)], NoError> in let addMembers: ([ContactListPeerId]) -> Signal<[(PeerId, AddChannelMemberError)], NoError> = { members -> Signal<[(PeerId, AddChannelMemberError)], NoError> in
let memberIds = members.compactMap { contact -> PeerId? in let memberIds = members.compactMap { contact -> PeerId? in
switch contact { switch contact {
@ -12652,8 +12484,8 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa
return .generic return .generic
case .groupFull: case .groupFull:
return .limitExceeded return .limitExceeded
case .privacy: case let .privacy(privacy):
return .restricted return .restricted(privacy?.forbiddenPeers.first)
case .notMutualContact: case .notMutualContact:
return .notMutualContact return .notMutualContact
case .tooManyChannels: case .tooManyChannels:
@ -12683,25 +12515,7 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa
} }
parentController?.push(contactsController) parentController?.push(contactsController)
/*if let contactsController = contactsController as? ContactSelectionController { do {
selectAddMemberDisposable.set((contactsController.result
|> deliverOnMainQueue).start(next: { [weak contactsController] result in
guard let (peers, _, _, _, _) = result, let memberPeer = peers.first else {
return
}
contactsController?.displayProgress = true
addMemberDisposable.set((addMember(memberPeer)
|> deliverOnMainQueue).start(completed: {
contactsController?.dismiss()
}))
}))
contactsController.dismissed = {
selectAddMemberDisposable.set(nil)
addMemberDisposable.set(nil)
}
}*/
if let contactsController = contactsController as? ContactMultiselectionController {
selectAddMemberDisposable.set(( selectAddMemberDisposable.set((
combineLatest(queue: .mainQueue(), combineLatest(queue: .mainQueue(),
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: groupPeer.id)), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: groupPeer.id)),
@ -12743,56 +12557,25 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa
}) })
} }
} else { } else {
if "".isEmpty { let failedPeers = failedPeerIds.compactMap { _, error -> TelegramForbiddenInvitePeer? in
let _ = (context.engine.data.get( if case let .restricted(peer) = error {
EngineDataList(failedPeerIds.compactMap { item -> EnginePeer.Id? in return peer
return item.0
}.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
)
|> deliverOnMainQueue).startStandalone(next: { peerItems in
let peers = peerItems.compactMap { $0 }
if !peers.isEmpty, let contactsController, let navigationController = contactsController.navigationController as? NavigationController {
var viewControllers = navigationController.viewControllers
if let index = viewControllers.firstIndex(where: { $0 === contactsController }) {
let inviteScreen = SendInviteLinkScreen(context: context, peer: EnginePeer(groupPeer), link: exportedInvitation?.link, peers: peers)
viewControllers.remove(at: index)
viewControllers.append(inviteScreen)
navigationController.setViewControllers(viewControllers, animated: true)
}
} else {
contactsController?.dismiss()
}
})
return
}
if peers.count == 1, case .restricted = failedPeerIds[0].1 {
switch peers[0] {
case let .peer(peerId):
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|> deliverOnMainQueue).startStandalone(next: { peer in
parentController?.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(EnginePeer(peer).compactDisplayTitle, EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
})
default:
break
}
} else if peers.count == 1, case .notMutualContact = failedPeerIds[0].1 {
let text: String
if let peer = groupPeer as? TelegramChannel, case .broadcast = peer.info {
text = presentationData.strings.Channel_AddUserLeftError
} else { } else {
text = presentationData.strings.GroupInfo_AddUserLeftError return nil
} }
parentController?.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
} else if case .tooMuchJoined = failedPeerIds[0].1 {
parentController?.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Invite_ChannelsTooMuch, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
} else if peers.count == 1, case .kicked = failedPeerIds[0].1 {
parentController?.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Channel_AddUserKickedError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
} }
contactsController?.dismiss() if !failedPeers.isEmpty, let contactsController, let navigationController = contactsController.navigationController as? NavigationController {
var viewControllers = navigationController.viewControllers
if let index = viewControllers.firstIndex(where: { $0 === contactsController }) {
let inviteScreen = SendInviteLinkScreen(context: context, peer: EnginePeer(groupPeer), link: exportedInvitation?.link, peers: failedPeers)
viewControllers.remove(at: index)
viewControllers.append(inviteScreen)
navigationController.setViewControllers(viewControllers, animated: true)
}
} else {
contactsController?.dismiss()
}
} }
})) }))
})) }))

View File

@ -1354,7 +1354,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
self.controller?.containerLayoutUpdated(layout, transition: .immediate) self.controller?.containerLayoutUpdated(layout, transition: .immediate)
} }
} else { } else {
let contactListNode = ContactListNode(context: self.context, updatedPresentationData: self.updatedPresentationData, presentation: .single(.natural(options: [], includeChatList: false, topPeers: .none)), onlyWriteable: self.filter.contains(.onlyWriteable)) let contactListNode = ContactListNode(context: self.context, updatedPresentationData: self.updatedPresentationData, presentation: .single(.natural(options: [], includeChatList: false, topPeers: .none)), onlyWriteable: self.filter.contains(.onlyWriteable), isGroupInvitation: false)
self.contactListNode = contactListNode self.contactListNode = contactListNode
contactListNode.enableUpdates = true contactListNode.enableUpdates = true
contactListNode.selectionStateUpdated = { [weak self] selectionState in contactListNode.selectionStateUpdated = { [weak self] selectionState in

View File

@ -34,13 +34,22 @@ final class PeerListItemComponent: Component {
case editing(isSelected: Bool) case editing(isSelected: Bool)
} }
enum SubtitleIcon {
case lock
}
enum Subtitle: Equatable {
case presence(EnginePeer.Presence?)
case text(text: String, icon: SubtitleIcon)
}
let context: AccountContext let context: AccountContext
let theme: PresentationTheme let theme: PresentationTheme
let strings: PresentationStrings let strings: PresentationStrings
let sideInset: CGFloat let sideInset: CGFloat
let title: String let title: String
let subtitle: Subtitle
let peer: EnginePeer? let peer: EnginePeer?
let presence: EnginePeer.Presence?
let selectionState: SelectionState let selectionState: SelectionState
let hasNext: Bool let hasNext: Bool
let action: (EnginePeer) -> Void let action: (EnginePeer) -> Void
@ -51,8 +60,8 @@ final class PeerListItemComponent: Component {
strings: PresentationStrings, strings: PresentationStrings,
sideInset: CGFloat, sideInset: CGFloat,
title: String, title: String,
subtitle: Subtitle,
peer: EnginePeer?, peer: EnginePeer?,
presence: EnginePeer.Presence?,
selectionState: SelectionState, selectionState: SelectionState,
hasNext: Bool, hasNext: Bool,
action: @escaping (EnginePeer) -> Void action: @escaping (EnginePeer) -> Void
@ -62,8 +71,8 @@ final class PeerListItemComponent: Component {
self.strings = strings self.strings = strings
self.sideInset = sideInset self.sideInset = sideInset
self.title = title self.title = title
self.subtitle = subtitle
self.peer = peer self.peer = peer
self.presence = presence
self.selectionState = selectionState self.selectionState = selectionState
self.hasNext = hasNext self.hasNext = hasNext
self.action = action self.action = action
@ -85,10 +94,10 @@ final class PeerListItemComponent: Component {
if lhs.title != rhs.title { if lhs.title != rhs.title {
return false return false
} }
if lhs.peer != rhs.peer { if lhs.subtitle != rhs.subtitle {
return false return false
} }
if lhs.presence != rhs.presence { if lhs.peer != rhs.peer {
return false return false
} }
if lhs.selectionState != rhs.selectionState { if lhs.selectionState != rhs.selectionState {
@ -108,6 +117,7 @@ final class PeerListItemComponent: Component {
private let separatorLayer: SimpleLayer private let separatorLayer: SimpleLayer
private let avatarNode: AvatarNode private let avatarNode: AvatarNode
private var labelIconView: UIImageView?
private var checkLayer: CheckLayer? private var checkLayer: CheckLayer?
private var component: PeerListItemComponent? private var component: PeerListItemComponent?
@ -165,7 +175,7 @@ final class PeerListItemComponent: Component {
self.component = component self.component = component
self.state = state self.state = state
if let presence = component.presence { if case let .presence(presence) = component.subtitle, let presence {
let statusManager: PeerPresenceStatusManager let statusManager: PeerPresenceStatusManager
if let current = self.statusManager { if let current = self.statusManager {
statusManager = current statusManager = current
@ -240,11 +250,26 @@ final class PeerListItemComponent: Component {
} }
} }
var labelIcon: UIImage?
let labelData: (String, Bool) let labelData: (String, Bool)
if let presence = component.presence { switch component.subtitle {
labelData = stringAndActivityForUserPresence(strings: component.strings, dateTimeFormat: PresentationDateTimeFormat(), presence: presence, relativeTo: Int32(Date().timeIntervalSince1970)) case let .presence(presence):
} else { if let presence {
labelData = (component.strings.LastSeen_Offline, false) labelData = stringAndActivityForUserPresence(strings: component.strings, dateTimeFormat: PresentationDateTimeFormat(), presence: presence, relativeTo: Int32(Date().timeIntervalSince1970))
} else {
labelData = (component.strings.LastSeen_Offline, false)
}
case let .text(text, icon):
switch icon {
case .lock:
labelIcon = PresentationResourcesItemList.peerStatusLockedImage(component.theme)
}
labelData = (text, false)
}
var maxTextSize = availableSize.width - leftInset - rightInset
if labelIcon != nil {
maxTextSize -= 48.0
} }
let labelSize = self.label.update( let labelSize = self.label.update(
@ -253,7 +278,7 @@ final class PeerListItemComponent: Component {
text: .plain(NSAttributedString(string: labelData.0, font: Font.regular(15.0), textColor: labelData.1 ? component.theme.list.itemAccentColor : component.theme.list.itemSecondaryTextColor)) text: .plain(NSAttributedString(string: labelData.0, font: Font.regular(15.0), textColor: labelData.1 ? component.theme.list.itemAccentColor : component.theme.list.itemSecondaryTextColor))
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0) containerSize: CGSize(width: maxTextSize, height: 100.0)
) )
let previousTitleFrame = self.title.view?.frame let previousTitleFrame = self.title.view?.frame
@ -268,7 +293,7 @@ final class PeerListItemComponent: Component {
text: .plain(NSAttributedString(string: component.title, font: Font.semibold(17.0), textColor: component.theme.list.itemPrimaryTextColor)) text: .plain(NSAttributedString(string: component.title, font: Font.semibold(17.0), textColor: component.theme.list.itemPrimaryTextColor))
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0) containerSize: CGSize(width: maxTextSize, height: 100.0)
) )
let titleSpacing: CGFloat = 1.0 let titleSpacing: CGFloat = 1.0
@ -296,6 +321,27 @@ final class PeerListItemComponent: Component {
transition.animateAlpha(view: titleView, from: 0.0, to: 1.0) transition.animateAlpha(view: titleView, from: 0.0, to: 1.0)
} }
} }
if let labelIcon {
let labelIconView: UIImageView
if let current = self.labelIconView {
labelIconView = current
} else {
labelIconView = UIImageView()
self.labelIconView = labelIconView
self.containerButton.addSubview(labelIconView)
}
labelIconView.image = labelIcon
let labelIconFrame = CGRect(origin: CGPoint(x: availableSize.width - rightInset - 48.0 + floor((48.0 - labelIcon.size.width) * 0.5), y: floor((height - verticalInset * 2.0 - labelIcon.size.height) / 2.0)), size: CGSize(width: labelIcon.size.width, height: labelIcon.size.height))
transition.setFrame(view: labelIconView, frame: labelIconFrame)
} else {
if let labelIconView = self.labelIconView {
self.labelIconView = nil
labelIconView.removeFromSuperview()
}
}
if let labelView = self.label.view { if let labelView = self.label.view {
if labelView.superview == nil { if labelView.superview == nil {
labelView.isUserInteractionEnabled = false labelView.isUserInteractionEnabled = false

View File

@ -22,13 +22,13 @@ private final class SendInviteLinkScreenComponent: Component {
let context: AccountContext let context: AccountContext
let link: String? let link: String?
let peers: [EnginePeer] let peers: [TelegramForbiddenInvitePeer]
let peerPresences: [EnginePeer.Id: EnginePeer.Presence] let peerPresences: [EnginePeer.Id: EnginePeer.Presence]
init( init(
context: AccountContext, context: AccountContext,
link: String?, link: String?,
peers: [EnginePeer], peers: [TelegramForbiddenInvitePeer],
peerPresences: [EnginePeer.Id: EnginePeer.Presence] peerPresences: [EnginePeer.Id: EnginePeer.Presence]
) { ) {
self.context = context self.context = context
@ -272,7 +272,9 @@ private final class SendInviteLinkScreenComponent: Component {
if self.component == nil { if self.component == nil {
for peer in component.peers { for peer in component.peers {
self.selectedItems.insert(peer.id) if component.link != nil && !peer.premiumRequiredToContact {
self.selectedItems.insert(peer.peer.id)
}
} }
} }
@ -280,8 +282,15 @@ private final class SendInviteLinkScreenComponent: Component {
self.state = state self.state = state
self.environment = environment self.environment = environment
let hasPremiumRestrictedUsers = "".isEmpty let premiumRestrictedUsers = component.peers.filter { peer in
let hasInviteLink = "".isEmpty return peer.canInviteWithPremium
}
var hasInviteLink = true
if premiumRestrictedUsers.count == component.peers.count && component.link == nil {
hasInviteLink = false
} else if component.link != nil && !premiumRestrictedUsers.isEmpty && component.peers.allSatisfy({ $0.premiumRequiredToContact }) {
hasInviteLink = false
}
if themeUpdated { if themeUpdated {
self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5) self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
@ -312,12 +321,13 @@ private final class SendInviteLinkScreenComponent: Component {
self.scrollContentView.addSubview(avatarsNode.view) self.scrollContentView.addSubview(avatarsNode.view)
} }
let avatarsContent = self.avatarsContext.update(peers: component.peers.count <= 3 ? component.peers : Array(component.peers.prefix(upTo: 3)), animated: false) let avatarPeers = component.peers.map(\.peer)
let avatarsContent = self.avatarsContext.update(peers: avatarPeers.count <= 3 ? avatarPeers : Array(avatarPeers.prefix(upTo: 3)), animated: false)
let avatarsSize = avatarsNode.update( let avatarsSize = avatarsNode.update(
context: component.context, context: component.context,
content: avatarsContent, content: avatarsContent,
itemSize: CGSize(width: 60.0, height: 60.0), itemSize: CGSize(width: 60.0, height: 60.0),
customSpacing: 50.0, customSpacing: 30.0,
font: avatarPlaceholderFont(size: 28.0), font: avatarPlaceholderFont(size: 28.0),
animated: false, animated: false,
synchronousLoad: true synchronousLoad: true
@ -347,7 +357,7 @@ private final class SendInviteLinkScreenComponent: Component {
transition.setFrame(view: leftButtonView, frame: leftButtonFrame) transition.setFrame(view: leftButtonView, frame: leftButtonFrame)
} }
if hasPremiumRestrictedUsers { if !premiumRestrictedUsers.isEmpty {
var premiumItemsTransition = transition var premiumItemsTransition = transition
let premiumTitle: ComponentView<Empty> let premiumTitle: ComponentView<Empty>
@ -397,20 +407,20 @@ private final class SendInviteLinkScreenComponent: Component {
//TODO:localize //TODO:localize
let text: String let text: String
if component.peers.count == 1 { if premiumRestrictedUsers.count == 1 {
text = "**\(component.peers[0].compactDisplayTitle)** accepts invitations to groups from contacts and **Premium** users." text = "**\(premiumRestrictedUsers[0].peer.compactDisplayTitle)** accepts invitations to groups from contacts and **Premium** users."
} else { } else {
let extraCount = component.peers.count - 3 let extraCount = premiumRestrictedUsers.count - 3
var peersText = "" var peersText = ""
for i in 0 ..< min(3, component.peers.count) { for i in 0 ..< min(3, premiumRestrictedUsers.count) {
if extraCount == 0 && i == component.peers.count - 1 { if extraCount == 0 && i == premiumRestrictedUsers.count - 1 {
peersText.append(", and ") peersText.append(", and ")
} else if i != 0 { } else if i != 0 {
peersText.append(", ") peersText.append(", ")
} }
peersText.append("**") peersText.append("**")
peersText.append(component.peers[i].compactDisplayTitle) peersText.append(premiumRestrictedUsers[i].peer.compactDisplayTitle)
peersText.append("**") peersText.append("**")
} }
@ -641,13 +651,13 @@ private final class SendInviteLinkScreenComponent: Component {
contentHeight += 8.0 contentHeight += 8.0
let text: String let text: String
if hasPremiumRestrictedUsers { if !premiumRestrictedUsers.isEmpty {
if component.link != nil { if component.link != nil {
//TODO:localize //TODO:localize
text = "You can try to send an invite link instead." text = "You can try to send an invite link instead."
} else { } else {
if component.peers.count == 1 { if component.peers.count == 1 {
text = environment.strings.SendInviteLink_TextUnavailableSingleUser(component.peers[0].displayTitle(strings: environment.strings, displayOrder: .firstLast)).string text = environment.strings.SendInviteLink_TextUnavailableSingleUser(component.peers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)).string
} else { } else {
text = environment.strings.SendInviteLink_TextUnavailableMultipleUsers(Int32(component.peers.count)) text = environment.strings.SendInviteLink_TextUnavailableMultipleUsers(Int32(component.peers.count))
} }
@ -655,13 +665,13 @@ private final class SendInviteLinkScreenComponent: Component {
} else { } else {
if component.link != nil { if component.link != nil {
if component.peers.count == 1 { if component.peers.count == 1 {
text = environment.strings.SendInviteLink_TextAvailableSingleUser(component.peers[0].displayTitle(strings: environment.strings, displayOrder: .firstLast)).string text = environment.strings.SendInviteLink_TextAvailableSingleUser(component.peers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)).string
} else { } else {
text = environment.strings.SendInviteLink_TextAvailableMultipleUsers(Int32(component.peers.count)) text = environment.strings.SendInviteLink_TextAvailableMultipleUsers(Int32(component.peers.count))
} }
} else { } else {
if component.peers.count == 1 { if component.peers.count == 1 {
text = environment.strings.SendInviteLink_TextUnavailableSingleUser(component.peers[0].displayTitle(strings: environment.strings, displayOrder: .firstLast)).string text = environment.strings.SendInviteLink_TextUnavailableSingleUser(component.peers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)).string
} else { } else {
text = environment.strings.SendInviteLink_TextUnavailableMultipleUsers(Int32(component.peers.count)) text = environment.strings.SendInviteLink_TextUnavailableMultipleUsers(Int32(component.peers.count))
} }
@ -696,6 +706,7 @@ private final class SendInviteLinkScreenComponent: Component {
contentHeight += descriptionTextFrame.height contentHeight += descriptionTextFrame.height
contentHeight += 22.0 contentHeight += 22.0
initialContentHeight = contentHeight
var singleItemHeight: CGFloat = 0.0 var singleItemHeight: CGFloat = 0.0
@ -706,7 +717,7 @@ private final class SendInviteLinkScreenComponent: Component {
for _ in 0 ..< 1 { for _ in 0 ..< 1 {
//let id: AnyHashable = AnyHashable("\(peer.id)_\(j)") //let id: AnyHashable = AnyHashable("\(peer.id)_\(j)")
let id = AnyHashable(peer.id) let id = AnyHashable(peer.peer.id)
validIds.append(id) validIds.append(id)
let item: ComponentView<Empty> let item: ComponentView<Empty>
@ -719,6 +730,15 @@ private final class SendInviteLinkScreenComponent: Component {
self.items[id] = item self.items[id] = item
} }
let itemSubtitle: PeerListItemComponent.Subtitle
let canBeSelected = component.link != nil && !peer.premiumRequiredToContact
if peer.premiumRequiredToContact {
//TODO:localize
itemSubtitle = .text(text: "Available only to premium users", icon: .lock)
} else {
itemSubtitle = .presence(component.peerPresences[peer.peer.id])
}
let itemSize = item.update( let itemSize = item.update(
transition: itemTransition, transition: itemTransition,
component: AnyComponent(PeerListItemComponent( component: AnyComponent(PeerListItemComponent(
@ -726,15 +746,18 @@ private final class SendInviteLinkScreenComponent: Component {
theme: environment.theme, theme: environment.theme,
strings: environment.strings, strings: environment.strings,
sideInset: 0.0, sideInset: 0.0,
title: peer.displayTitle(strings: environment.strings, displayOrder: .firstLast), title: peer.peer.displayTitle(strings: environment.strings, displayOrder: .firstLast),
peer: peer, subtitle: itemSubtitle,
presence: component.peerPresences[peer.id], peer: peer.peer,
selectionState: component.link == nil ? .none : .editing(isSelected: self.selectedItems.contains(peer.id)), selectionState: !canBeSelected ? .none : .editing(isSelected: self.selectedItems.contains(peer.peer.id)),
hasNext: i != component.peers.count - 1, hasNext: i != component.peers.count - 1,
action: { [weak self] peer in action: { [weak self] peer in
guard let self else { guard let self else {
return return
} }
if !canBeSelected {
return
}
if self.selectedItems.contains(peer.id) { if self.selectedItems.contains(peer.id) {
self.selectedItems.remove(peer.id) self.selectedItems.remove(peer.id)
} else { } else {
@ -771,7 +794,6 @@ private final class SendInviteLinkScreenComponent: Component {
} }
transition.setFrame(view: self.itemContainerView, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: itemsHeight))) transition.setFrame(view: self.itemContainerView, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: itemsHeight)))
initialContentHeight += singleItemHeight + 16.0
initialContentHeight += min(itemsHeight, floor(singleItemHeight * 2.5)) initialContentHeight += min(itemsHeight, floor(singleItemHeight * 2.5))
contentHeight += itemsHeight contentHeight += itemsHeight
@ -805,16 +827,16 @@ private final class SendInviteLinkScreenComponent: Component {
if self.selectedItems.isEmpty { if self.selectedItems.isEmpty {
controller.dismiss() controller.dismiss()
} else if let link = component.link { } else if let link = component.link {
let selectedPeers = component.peers.filter { self.selectedItems.contains($0.id) } let selectedPeers = component.peers.filter { self.selectedItems.contains($0.peer.id) }
let _ = enqueueMessagesToMultiplePeers(account: component.context.account, peerIds: Array(self.selectedItems), threadIds: [:], messages: [.message(text: link, attributes: [], inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).start() let _ = enqueueMessagesToMultiplePeers(account: component.context.account, peerIds: Array(self.selectedItems), threadIds: [:], messages: [.message(text: link, attributes: [], inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).start()
let text: String let text: String
if selectedPeers.count == 1 { if selectedPeers.count == 1 {
text = environment.strings.Conversation_ShareLinkTooltip_Chat_One(selectedPeers[0].displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string text = environment.strings.Conversation_ShareLinkTooltip_Chat_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string
} else if selectedPeers.count == 2 { } else if selectedPeers.count == 2 {
text = environment.strings.Conversation_ShareLinkTooltip_TwoChats_One(selectedPeers[0].displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), selectedPeers[1].displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string text = environment.strings.Conversation_ShareLinkTooltip_TwoChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), selectedPeers[1].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string
} else { } else {
text = environment.strings.Conversation_ShareLinkTooltip_ManyChats_One(selectedPeers[0].displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), "\(selectedPeers.count - 1)").string text = environment.strings.Conversation_ShareLinkTooltip_ManyChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), "\(selectedPeers.count - 1)").string
} }
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
@ -863,7 +885,7 @@ private final class SendInviteLinkScreenComponent: Component {
let topInset: CGFloat = max(0.0, availableSize.height - containerInset - initialContentHeight) let topInset: CGFloat = max(0.0, availableSize.height - containerInset - initialContentHeight)
let scrollContentHeight = max(topInset + contentHeight, availableSize.height - containerInset) let scrollContentHeight = max(topInset + contentHeight + containerInset, availableSize.height - containerInset)
self.scrollContentClippingView.layer.cornerRadius = 10.0 self.scrollContentClippingView.layer.cornerRadius = 10.0
@ -906,13 +928,13 @@ private final class SendInviteLinkScreenComponent: Component {
public class SendInviteLinkScreen: ViewControllerComponentContainer { public class SendInviteLinkScreen: ViewControllerComponentContainer {
private let context: AccountContext private let context: AccountContext
private let link: String? private let link: String?
private let peers: [EnginePeer] private let peers: [TelegramForbiddenInvitePeer]
private var isDismissed: Bool = false private var isDismissed: Bool = false
private var presenceDisposable: Disposable? private var presenceDisposable: Disposable?
public init(context: AccountContext, peer: EnginePeer, link: String?, peers: [EnginePeer]) { public init(context: AccountContext, peer: EnginePeer, link: String?, peers: [TelegramForbiddenInvitePeer]) {
self.context = context self.context = context
var link = link var link = link
@ -920,6 +942,120 @@ public class SendInviteLinkScreen: ViewControllerComponentContainer {
link = "https://t.me/\(addressName)" link = "https://t.me/\(addressName)"
} }
#if DEBUG
var peers = peers
if !"".isEmpty {
enum TestConfiguration: CaseIterable {
case singlePeerNoPremiumLink
case singlePeerPremiumLink
case singlePeerNoPremiumNoLink
case singlePeerPremiumNoLink
case somePeersNoPremiumLink
case somePeersOnePremiumLink
case somePeersAllPremiumLink
case somePeersNoPremiumNoLink
case somePeersOnePremiumNoLink
case somePeersAllPremiumNoLink
case morePeersNoPremiumLink
case morePeersOnePremiumLink
case morePeersAllPremiumLink
case morePeersNoPremiumNoLink
case morePeersOnePremiumNoLink
case morePeersAllPremiumNoLink
}
var nextPeerId: Int64 = 1
let makePeer: (Bool, Bool) -> TelegramForbiddenInvitePeer = { canInviteWithPremium, premiumRequiredToContact in
guard case let .user(user) = peers[0].peer else {
preconditionFailure()
}
let id = nextPeerId
nextPeerId += 1
return TelegramForbiddenInvitePeer(
peer: .user(TelegramUser(
id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(id)),
accessHash: user.accessHash,
firstName: user.firstName,
lastName: user.lastName,
username: user.username,
phone: user.phone,
photo: user.photo,
botInfo: user.botInfo,
restrictionInfo: user.restrictionInfo,
flags: user.flags,
emojiStatus: user.emojiStatus,
usernames: user.usernames,
storiesHidden: user.storiesHidden,
nameColor: user.nameColor,
backgroundEmojiId: user.backgroundEmojiId,
profileColor: user.profileColor,
profileBackgroundEmojiId: user.profileBackgroundEmojiId
)),
canInviteWithPremium: canInviteWithPremium,
premiumRequiredToContact: premiumRequiredToContact
)
}
let caseIndex = 9
let configuration = TestConfiguration.allCases[caseIndex]
do {
switch configuration {
case .singlePeerNoPremiumLink:
peers = [makePeer(false, false)]
link = "abcd"
case .singlePeerPremiumLink:
peers = [makePeer(true, false)]
link = "abcd"
case .singlePeerNoPremiumNoLink:
peers = [makePeer(false, false)]
link = nil
case .singlePeerPremiumNoLink:
peers = [makePeer(true, false)]
link = nil
case .somePeersNoPremiumLink:
peers = (0 ..< 3).map { _ in makePeer(false, false) }
link = "abcd"
case .somePeersOnePremiumLink:
peers = [
makePeer(false, false),
makePeer(true, true),
makePeer(false, false)
]
link = "abcd"
case .somePeersAllPremiumLink:
peers = (0 ..< 3).map { _ in makePeer(true, false) }
link = "abcd"
case .somePeersNoPremiumNoLink:
peers = (0 ..< 3).map { _ in makePeer(false, false) }
link = nil
case .somePeersOnePremiumNoLink:
peers = [
makePeer(false, false),
makePeer(true, false),
makePeer(false, false)
]
link = nil
case .somePeersAllPremiumNoLink:
peers = (0 ..< 3).map { _ in makePeer(true, false) }
link = nil
case .morePeersNoPremiumLink:
preconditionFailure()
case .morePeersOnePremiumLink:
preconditionFailure()
case .morePeersAllPremiumLink:
preconditionFailure()
case .morePeersNoPremiumNoLink:
preconditionFailure()
case .morePeersOnePremiumNoLink:
preconditionFailure()
case .morePeersAllPremiumNoLink:
preconditionFailure()
}
}
}
#endif
self.link = link self.link = link
self.peers = peers self.peers = peers
@ -930,7 +1066,7 @@ public class SendInviteLinkScreen: ViewControllerComponentContainer {
self.blocksBackgroundWhenInOverlay = true self.blocksBackgroundWhenInOverlay = true
self.presenceDisposable = (context.engine.data.subscribe(EngineDataMap( self.presenceDisposable = (context.engine.data.subscribe(EngineDataMap(
peers.map(\.id).map(TelegramEngine.EngineData.Item.Peer.Presence.init(id:)) peers.map(\.peer.id).map(TelegramEngine.EngineData.Item.Peer.Presence.init(id:))
)) ))
|> deliverOnMainQueue).start(next: { [weak self] presences in |> deliverOnMainQueue).start(next: { [weak self] presences in
guard let self else { guard let self else {

View File

@ -52,7 +52,7 @@ final class ComposeControllerNode: ASDisplayNode {
ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewChannel, icon: .generic(UIImage(bundleImageName: "Contact List/CreateChannelActionIcon")!), action: { ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewChannel, icon: .generic(UIImage(bundleImageName: "Contact List/CreateChannelActionIcon")!), action: {
openCreateNewChannelImpl?() openCreateNewChannelImpl?()
}) })
], includeChatList: false, topPeers: .none)), onlyWriteable: false, displayPermissionPlaceholder: false) ], includeChatList: false, topPeers: .none)), onlyWriteable: false, isGroupInvitation: false, displayPermissionPlaceholder: false)
super.init() super.init()

View File

@ -83,6 +83,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
private let options: [ContactListAdditionalOption] private let options: [ContactListAdditionalOption]
private let filters: [ContactListFilter] private let filters: [ContactListFilter]
private let onlyWriteable: Bool private let onlyWriteable: Bool
private let isGroupInvitation: Bool
private let limit: Int32? private let limit: Int32?
init(_ params: ContactMultiselectionControllerParams) { init(_ params: ContactMultiselectionControllerParams) {
@ -94,6 +95,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
self.options = params.options self.options = params.options
self.filters = params.filters self.filters = params.filters
self.onlyWriteable = params.onlyWriteable self.onlyWriteable = params.onlyWriteable
self.isGroupInvitation = params.isGroupInvitation
self.limit = params.limit self.limit = params.limit
self.presentationData = params.updatedPresentationData?.initial ?? params.context.sharedContext.currentPresentationData.with { $0 } self.presentationData = params.updatedPresentationData?.initial ?? params.context.sharedContext.currentPresentationData.with { $0 }
@ -302,7 +304,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
} }
override func loadDisplayNode() { override func loadDisplayNode() {
self.displayNode = ContactMultiselectionControllerNode(navigationBar: self.navigationBar, context: self.context, presentationData: self.presentationData, mode: self.mode, isPeerEnabled: self.isPeerEnabled, attemptDisabledItemSelection: self.attemptDisabledItemSelection, options: self.options, filters: self.filters, onlyWriteable: self.onlyWriteable, limit: self.limit, reachedSelectionLimit: self.params.reachedLimit, present: { [weak self] c, a in self.displayNode = ContactMultiselectionControllerNode(navigationBar: self.navigationBar, context: self.context, presentationData: self.presentationData, mode: self.mode, isPeerEnabled: self.isPeerEnabled, attemptDisabledItemSelection: self.attemptDisabledItemSelection, options: self.options, filters: self.filters, onlyWriteable: self.onlyWriteable, isGroupInvitation: self.isGroupInvitation, limit: self.limit, reachedSelectionLimit: self.params.reachedLimit, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a) self?.present(c, in: .window(.root), with: a)
}) })
switch self.contactsNode.contentNode { switch self.contactsNode.contentNode {

View File

@ -78,8 +78,9 @@ 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
init(navigationBar: NavigationBar?, context: AccountContext, presentationData: PresentationData, mode: ContactMultiselectionControllerMode, isPeerEnabled: ((EnginePeer) -> Bool)?, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?, options: [ContactListAdditionalOption], filters: [ContactListFilter], onlyWriteable: Bool, limit: Int32?, reachedSelectionLimit: ((Int32) -> Void)?, present: @escaping (ViewController, Any?) -> Void) { init(navigationBar: NavigationBar?, context: AccountContext, presentationData: PresentationData, mode: ContactMultiselectionControllerMode, isPeerEnabled: ((EnginePeer) -> Bool)?, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?, options: [ContactListAdditionalOption], 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
@ -90,6 +91,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
self.isPeerEnabled = isPeerEnabled self.isPeerEnabled = isPeerEnabled
self.onlyWriteable = onlyWriteable self.onlyWriteable = onlyWriteable
self.isGroupInvitation = isGroupInvitation
var proceedImpl: (() -> Void)? var proceedImpl: (() -> Void)?
@ -231,7 +233,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
} else { } else {
displayTopPeers = .none displayTopPeers = .none
} }
let contactListNode = ContactListNode(context: context, presentation: .single(.natural(options: options, includeChatList: includeChatList, topPeers: displayTopPeers)), filters: filters, onlyWriteable: onlyWriteable, selectionState: ContactListNodeGroupSelectionState()) let contactListNode = ContactListNode(context: context, presentation: .single(.natural(options: options, includeChatList: includeChatList, topPeers: displayTopPeers)), filters: filters, onlyWriteable: onlyWriteable, isGroupInvitation: isGroupInvitation, selectionState: ContactListNodeGroupSelectionState())
self.contentNode = .contacts(contactListNode) self.contentNode = .contacts(contactListNode)
if !selectedPeers.isEmpty { if !selectedPeers.isEmpty {
@ -359,7 +361,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
searchChannels: searchChannels, searchChannels: searchChannels,
globalSearch: globalSearch, globalSearch: globalSearch,
displaySavedMessages: displaySavedMessages displaySavedMessages: displaySavedMessages
))), filters: filters, onlyWriteable: strongSelf.onlyWriteable, isPeerEnabled: strongSelf.isPeerEnabled, selectionState: selectionState, isSearch: true) ))), filters: filters, onlyWriteable: strongSelf.onlyWriteable, isGroupInvitation: strongSelf.isGroupInvitation, isPeerEnabled: strongSelf.isPeerEnabled, selectionState: selectionState, isSearch: true)
searchResultsNode.openPeer = { peer, _ in searchResultsNode.openPeer = { peer, _ in
self?.tokenListNode.setText("") self?.tokenListNode.setText("")
self?.openPeer?(peer) self?.openPeer?(peer)

View File

@ -68,7 +68,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
self.filters = filters self.filters = filters
var contextActionImpl: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? var contextActionImpl: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?
self.contactListNode = ContactListNode(context: context, updatedPresentationData: (presentationData, self.presentationDataPromise.get()), presentation: .single(.natural(options: options, includeChatList: false, topPeers: .none)), filters: filters, onlyWriteable: false, displayCallIcons: displayCallIcons, contextAction: multipleSelection ? { peer, node, gesture, _, _ in self.contactListNode = ContactListNode(context: context, updatedPresentationData: (presentationData, self.presentationDataPromise.get()), presentation: .single(.natural(options: options, includeChatList: false, topPeers: .none)), filters: filters, onlyWriteable: false, isGroupInvitation: false, displayCallIcons: displayCallIcons, contextAction: multipleSelection ? { peer, node, gesture, _, _ in
contextActionImpl?(peer, node, gesture, nil) contextActionImpl?(peer, node, gesture, nil)
} : nil, multipleSelection: multipleSelection) } : nil, multipleSelection: multipleSelection)

View File

@ -676,7 +676,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
case .supergroup: case .supergroup:
createSignal = context.engine.peers.createSupergroup(title: title, description: nil) createSignal = context.engine.peers.createSupergroup(title: title, description: nil)
|> map { peerId -> CreateGroupResult? in |> map { peerId -> CreateGroupResult? in
return CreateGroupResult(peerId: peerId, failedToInvitePeerIds: []) return CreateGroupResult(peerId: peerId, result: TelegramInvitePeersResult(forbiddenPeers: []))
} }
|> mapError { error -> CreateGroupError in |> mapError { error -> CreateGroupError in
switch error { switch error {
@ -705,7 +705,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
} }
return context.engine.peers.createSupergroup(title: title, description: nil, location: (location.latitude, location.longitude, address)) return context.engine.peers.createSupergroup(title: title, description: nil, location: (location.latitude, location.longitude, address))
|> map { peerId -> CreateGroupResult? in |> map { peerId -> CreateGroupResult? in
return CreateGroupResult(peerId: peerId, failedToInvitePeerIds: []) return CreateGroupResult(peerId: peerId, result: TelegramInvitePeersResult(forbiddenPeers: []))
} }
|> mapError { error -> CreateGroupError in |> mapError { error -> CreateGroupError in
switch error { switch error {
@ -731,7 +731,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
let createGroupSignal: (Bool) -> Signal<CreateGroupResult?, CreateGroupError> = { isForum in let createGroupSignal: (Bool) -> Signal<CreateGroupResult?, CreateGroupError> = { isForum in
return context.engine.peers.createSupergroup(title: title, description: nil, isForum: isForum) return context.engine.peers.createSupergroup(title: title, description: nil, isForum: isForum)
|> map { peerId -> CreateGroupResult? in |> map { peerId -> CreateGroupResult? in
return CreateGroupResult(peerId: peerId, failedToInvitePeerIds: []) return CreateGroupResult(peerId: peerId, result: TelegramInvitePeersResult(forbiddenPeers: []))
} }
|> mapError { error -> CreateGroupError in |> mapError { error -> CreateGroupError in
switch error { switch error {
@ -834,7 +834,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
let controller = ChatControllerImpl(context: context, chatLocation: .peer(id: result.peerId)) let controller = ChatControllerImpl(context: context, chatLocation: .peer(id: result.peerId))
replaceControllerImpl?(controller) replaceControllerImpl?(controller)
if !result.failedToInvitePeerIds.isEmpty { if !result.result.forbiddenPeers.isEmpty {
context.account.viewTracker.forceUpdateCachedPeerData(peerId: result.peerId) context.account.viewTracker.forceUpdateCachedPeerData(peerId: result.peerId)
let _ = (context.engine.data.subscribe( let _ = (context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: result.peerId) TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: result.peerId)
@ -847,26 +847,10 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
TelegramEngine.EngineData.Item.Peer.Peer(id: result.peerId) TelegramEngine.EngineData.Item.Peer.Peer(id: result.peerId)
) )
|> deliverOnMainQueue).start(next: { peer in |> deliverOnMainQueue).start(next: { peer in
let _ = controller
let _ = exportedInvitation
if let peer, let exportedInvitation, let link = exportedInvitation.link { if let peer, let exportedInvitation, let link = exportedInvitation.link {
let _ = (context.engine.data.get(
EngineDataList(result.failedToInvitePeerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))) let inviteScreen = SendInviteLinkScreen(context: context, peer: peer, link: link, peers: result.result.forbiddenPeers)
) controller?.push(inviteScreen)
|> deliverOnMainQueue).start(next: { peerItems in
guard let controller else {
return
}
let _ = controller
let _ = peerItems
let peers = peerItems.compactMap { $0 }
if !peers.isEmpty {
let inviteScreen = SendInviteLinkScreen(context: context, peer: peer, link: link, peers: peers)
controller.push(inviteScreen)
}
})
} }
}) })
}) })