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 filters: [ContactListFilter]
public let onlyWriteable: Bool
public let isGroupInvitation: Bool
public let isPeerEnabled: ((EnginePeer) -> Bool)?
public let attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?
public let alwaysEnabled: Bool
public let limit: Int32?
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.updatedPresentationData = updatedPresentationData
self.mode = mode
self.options = options
self.filters = filters
self.onlyWriteable = onlyWriteable
self.isGroupInvitation = isGroupInvitation
self.isPeerEnabled = isPeerEnabled
self.attemptDisabledItemSelection = attemptDisabledItemSelection
self.alwaysEnabled = alwaysEnabled

View File

@ -1059,7 +1059,7 @@ public final class ContactListNode: ASDisplayNode {
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.filters = filters
self.displayPermissionPlaceholder = displayPermissionPlaceholder
@ -1383,7 +1383,7 @@ public final class ContactListNode: ASDisplayNode {
})
let peerRequiresPremiumForMessaging: Signal<[EnginePeer.Id: Bool], NoError>
if onlyWriteable {
if onlyWriteable && !isGroupInvitation {
peerRequiresPremiumForMessaging = foundPeers.get()
|> map { foundPeers -> Set<EnginePeer.Id> in
var result = Set<EnginePeer.Id>()

View File

@ -114,7 +114,7 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
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)
})

View File

@ -1244,8 +1244,8 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
return current.withUpdatedUpdating(false)
}
if let adminPeer {
let inviteScreen = SendInviteLinkScreen(context: context, peer: channelPeer, link: exportedInvitation?.link, peers: [adminPeer])
if let adminPeer, case let .restricted(forbiddenPeer) = error {
let inviteScreen = SendInviteLinkScreen(context: context, peer: channelPeer, link: exportedInvitation?.link, peers: [forbiddenPeer ?? TelegramForbiddenInvitePeer(peer: adminPeer, canInviteWithPremium: false, premiumRequiredToContact: false)])
pushControllerImpl?(inviteScreen)
dismissImpl?()
@ -1433,9 +1433,9 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
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
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 {
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)
dismissImpl?()

View File

@ -549,25 +549,25 @@ public func channelMembersController(context: AccountContext, updatedPresentatio
contactsController?.dismiss()
} else {
if let chatPeer {
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
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)
}
let failedPeers = failedPeerIds.compactMap { _, error -> TelegramForbiddenInvitePeer? in
if case let .restricted(peer) = error {
return peer
} 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
}

View File

@ -4,15 +4,50 @@ import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum AddGroupMemberError {
case generic
case groupFull
case privacy
case privacy(TelegramInvitePeersResult?)
case notMutualContact
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> {
return account.postbox.transaction { transaction -> Signal<Void, AddGroupMemberError> in
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":
return .groupFull
case "USER_PRIVACY_RESTRICTED":
return .privacy
return .privacy(nil)
case "USER_CHANNELS_TOO_MUCH":
return .tooManyChannels
case "USER_NOT_MUTUAL_CONTACT":
@ -34,14 +69,16 @@ func _internal_addGroupMember(account: Account, peerId: PeerId, memberId: PeerId
}
|> mapToSignal { result -> Signal<Void, AddGroupMemberError> in
let updatesValue: Api.Updates
let missingInviteesValue: [Api.MissingInvitee]
switch result {
case let .invitedUsers(updates, missingInvitees):
let _ = missingInvitees
updatesValue = updates
missingInviteesValue = missingInvitees
}
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 {
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
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 }
|> mapToSignal { result -> Signal<Void, AddGroupMemberError> in
if result.forbiddenPeers.isEmpty {
return .single(Void())
} else {
return .fail(.privacy(result))
}
}
}
} else {
return .fail(.generic)
@ -76,7 +134,7 @@ func _internal_addGroupMember(account: Account, peerId: PeerId, memberId: PeerId
public enum AddChannelMemberError {
case generic
case restricted
case restricted(TelegramForbiddenInvitePeer?)
case notMutualContact
case limitExceeded
case tooMuchJoined
@ -108,7 +166,7 @@ func _internal_addChannelMember(account: Account, peerId: PeerId, memberId: Peer
case "USERS_TOO_MUCH":
return .fail(.limitExceeded)
case "USER_PRIVACY_RESTRICTED":
return .fail(.restricted)
return .fail(.restricted(nil))
case "USER_NOT_MUTUAL_CONTACT":
return .fail(.notMutualContact)
case "USER_BOT":
@ -127,7 +185,14 @@ func _internal_addChannelMember(account: Account, peerId: PeerId, memberId: Peer
let updatesValue: Api.Updates
switch result {
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
}
@ -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> {
let signal = account.postbox.transaction { transaction -> Signal<Void, AddChannelMemberError> in
func _internal_addChannelMembers(account: Account, peerId: PeerId, memberIds: [PeerId]) -> Signal<TelegramInvitePeersResult, AddChannelMemberError> {
let signal = account.postbox.transaction { transaction -> Signal<TelegramInvitePeersResult, AddChannelMemberError> in
var memberPeerIds: [PeerId:Peer] = [:]
var inputUsers: [Api.InputUser] = []
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) {
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
switch error.errorDescription {
case "CHANNELS_TOO_MUCH":
return .tooMuchJoined
case "USER_PRIVACY_RESTRICTED":
return .restricted
return .restricted(nil)
case "USER_NOT_MUTUAL_CONTACT":
return .notMutualContact
case "USERS_TOO_MUCH":
@ -224,21 +289,39 @@ func _internal_addChannelMembers(account: Account, peerId: PeerId, memberIds: [P
return .generic
}
}
|> map { result in
|> mapToQueue { result -> Signal<TelegramInvitePeersResult, AddChannelMemberError> in
let updatesValue: Api.Updates
let missingInviteesValue: [Api.MissingInvitee]
switch result {
case let .invitedUsers(updates, missingInvitees):
let _ = missingInvitees
updatesValue = updates
missingInviteesValue = missingInvitees
}
account.stateManager.addUpdates(updatesValue)
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
} else {
return .single(Void())
return .fail(.generic)
}
}

View File

@ -15,14 +15,14 @@ public enum CreateGroupError {
public struct CreateGroupResult {
public var peerId: EnginePeer.Id
public var failedToInvitePeerIds: [EnginePeer.Id]
public var result: TelegramInvitePeersResult
public init(
peerId: EnginePeer.Id,
failedToInvitePeerIds: [EnginePeer.Id]
result: TelegramInvitePeersResult
) {
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
}
|> mapToSignal { result -> Signal<CreateGroupResult?, CreateGroupError> in
var failedToInvitePeerIds: [EnginePeer.Id] = []
failedToInvitePeerIds = []
let updatesValue: Api.Updates
let missingInviteesValue: [Api.MissingInvitee]
switch result {
case let .invitedUsers(updates, missingInvitees):
let _ = missingInvitees
updatesValue = updates
missingInviteesValue = missingInvitees
}
account.stateManager.addUpdates(updatesValue)
@ -68,11 +66,26 @@ func _internal_createGroup(account: Account, title: String, peerIds: [PeerId], t
}
|> take(1)
|> castError(CreateGroupError.self)
|> map { _ -> CreateGroupResult in
return CreateGroupResult(
peerId: peerId,
failedToInvitePeerIds: failedToInvitePeerIds
)
|> mapToSignal { _ -> Signal<CreateGroupResult?, CreateGroupError> in
return account.postbox.transaction { transaction -> CreateGroupResult in
return CreateGroupResult(
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 {
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" {
return .fail(.addMemberError(.privacy))
return .fail(.addMemberError(.privacy(nil)))
} else if error.errorDescription == "ADMINS_TOO_MUCH" {
return .fail(.adminsTooMuch)
}
@ -204,7 +204,7 @@ func _internal_updateChannelAdminRights(account: Account, peerId: PeerId, adminI
} else if error.errorDescription == "USER_NOT_MUTUAL_CONTACT" {
return .fail(.addMemberError(.notMutualContact))
} else if error.errorDescription == "USER_PRIVACY_RESTRICTED" {
return .fail(.addMemberError(.restricted))
return .fail(.addMemberError(.restricted(nil)))
} else if error.errorDescription == "USER_CHANNELS_TOO_MUCH" {
return .fail(.addMemberError(.tooMuchJoined))
} 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)
}
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)
}

View File

@ -316,6 +316,7 @@ public enum PresentationResourceKey: Int32 {
case sharedLinkIcon
case hideIconImage
case peerStatusLockedImage
}
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)
})
}
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 {
self.disposable.dispose()
self.peerDisposable.dispose()
for (_, disposable) in self.removingMemberIds {
disposable.dispose()
}
}
private func pushState() {
@ -293,7 +296,7 @@ private final class PeerInfoMembersContextImpl {
}
func removeMember(memberId: PeerId) {
if removingMemberIds[memberId] == nil {
if self.removingMemberIds[memberId] == nil {
let signal: Signal<Never, NoError>
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))

View File

@ -12424,20 +12424,8 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa
}, clearHighlightAutomatically: true))
}
let contactsController: ViewController
/*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)
}
}))
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))
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
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 memberIds = members.compactMap { contact -> PeerId? in
switch contact {
@ -12652,8 +12484,8 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa
return .generic
case .groupFull:
return .limitExceeded
case .privacy:
return .restricted
case let .privacy(privacy):
return .restricted(privacy?.forbiddenPeers.first)
case .notMutualContact:
return .notMutualContact
case .tooManyChannels:
@ -12683,25 +12515,7 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa
}
parentController?.push(contactsController)
/*if let contactsController = contactsController as? ContactSelectionController {
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 {
do {
selectAddMemberDisposable.set((
combineLatest(queue: .mainQueue(),
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: groupPeer.id)),
@ -12743,56 +12557,25 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa
})
}
} else {
if "".isEmpty {
let _ = (context.engine.data.get(
EngineDataList(failedPeerIds.compactMap { item -> EnginePeer.Id? in
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
let failedPeers = failedPeerIds.compactMap { _, error -> TelegramForbiddenInvitePeer? in
if case let .restricted(peer) = error {
return peer
} 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)
}
} 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
contactListNode.enableUpdates = true
contactListNode.selectionStateUpdated = { [weak self] selectionState in

View File

@ -34,13 +34,22 @@ final class PeerListItemComponent: Component {
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 theme: PresentationTheme
let strings: PresentationStrings
let sideInset: CGFloat
let title: String
let subtitle: Subtitle
let peer: EnginePeer?
let presence: EnginePeer.Presence?
let selectionState: SelectionState
let hasNext: Bool
let action: (EnginePeer) -> Void
@ -51,8 +60,8 @@ final class PeerListItemComponent: Component {
strings: PresentationStrings,
sideInset: CGFloat,
title: String,
subtitle: Subtitle,
peer: EnginePeer?,
presence: EnginePeer.Presence?,
selectionState: SelectionState,
hasNext: Bool,
action: @escaping (EnginePeer) -> Void
@ -62,8 +71,8 @@ final class PeerListItemComponent: Component {
self.strings = strings
self.sideInset = sideInset
self.title = title
self.subtitle = subtitle
self.peer = peer
self.presence = presence
self.selectionState = selectionState
self.hasNext = hasNext
self.action = action
@ -85,10 +94,10 @@ final class PeerListItemComponent: Component {
if lhs.title != rhs.title {
return false
}
if lhs.peer != rhs.peer {
if lhs.subtitle != rhs.subtitle {
return false
}
if lhs.presence != rhs.presence {
if lhs.peer != rhs.peer {
return false
}
if lhs.selectionState != rhs.selectionState {
@ -108,6 +117,7 @@ final class PeerListItemComponent: Component {
private let separatorLayer: SimpleLayer
private let avatarNode: AvatarNode
private var labelIconView: UIImageView?
private var checkLayer: CheckLayer?
private var component: PeerListItemComponent?
@ -165,7 +175,7 @@ final class PeerListItemComponent: Component {
self.component = component
self.state = state
if let presence = component.presence {
if case let .presence(presence) = component.subtitle, let presence {
let statusManager: PeerPresenceStatusManager
if let current = self.statusManager {
statusManager = current
@ -240,11 +250,26 @@ final class PeerListItemComponent: Component {
}
}
var labelIcon: UIImage?
let labelData: (String, Bool)
if let presence = component.presence {
labelData = stringAndActivityForUserPresence(strings: component.strings, dateTimeFormat: PresentationDateTimeFormat(), presence: presence, relativeTo: Int32(Date().timeIntervalSince1970))
} else {
labelData = (component.strings.LastSeen_Offline, false)
switch component.subtitle {
case let .presence(presence):
if let presence {
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(
@ -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))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0)
containerSize: CGSize(width: maxTextSize, height: 100.0)
)
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))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0)
containerSize: CGSize(width: maxTextSize, height: 100.0)
)
let titleSpacing: CGFloat = 1.0
@ -296,6 +321,27 @@ final class PeerListItemComponent: Component {
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 labelView.superview == nil {
labelView.isUserInteractionEnabled = false

View File

@ -22,13 +22,13 @@ private final class SendInviteLinkScreenComponent: Component {
let context: AccountContext
let link: String?
let peers: [EnginePeer]
let peers: [TelegramForbiddenInvitePeer]
let peerPresences: [EnginePeer.Id: EnginePeer.Presence]
init(
context: AccountContext,
link: String?,
peers: [EnginePeer],
peers: [TelegramForbiddenInvitePeer],
peerPresences: [EnginePeer.Id: EnginePeer.Presence]
) {
self.context = context
@ -272,7 +272,9 @@ private final class SendInviteLinkScreenComponent: Component {
if self.component == nil {
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.environment = environment
let hasPremiumRestrictedUsers = "".isEmpty
let hasInviteLink = "".isEmpty
let premiumRestrictedUsers = component.peers.filter { peer in
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 {
self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
@ -312,12 +321,13 @@ private final class SendInviteLinkScreenComponent: Component {
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(
context: component.context,
content: avatarsContent,
itemSize: CGSize(width: 60.0, height: 60.0),
customSpacing: 50.0,
customSpacing: 30.0,
font: avatarPlaceholderFont(size: 28.0),
animated: false,
synchronousLoad: true
@ -347,7 +357,7 @@ private final class SendInviteLinkScreenComponent: Component {
transition.setFrame(view: leftButtonView, frame: leftButtonFrame)
}
if hasPremiumRestrictedUsers {
if !premiumRestrictedUsers.isEmpty {
var premiumItemsTransition = transition
let premiumTitle: ComponentView<Empty>
@ -397,20 +407,20 @@ private final class SendInviteLinkScreenComponent: Component {
//TODO:localize
let text: String
if component.peers.count == 1 {
text = "**\(component.peers[0].compactDisplayTitle)** accepts invitations to groups from contacts and **Premium** users."
if premiumRestrictedUsers.count == 1 {
text = "**\(premiumRestrictedUsers[0].peer.compactDisplayTitle)** accepts invitations to groups from contacts and **Premium** users."
} else {
let extraCount = component.peers.count - 3
let extraCount = premiumRestrictedUsers.count - 3
var peersText = ""
for i in 0 ..< min(3, component.peers.count) {
if extraCount == 0 && i == component.peers.count - 1 {
for i in 0 ..< min(3, premiumRestrictedUsers.count) {
if extraCount == 0 && i == premiumRestrictedUsers.count - 1 {
peersText.append(", and ")
} else if i != 0 {
peersText.append(", ")
}
peersText.append("**")
peersText.append(component.peers[i].compactDisplayTitle)
peersText.append(premiumRestrictedUsers[i].peer.compactDisplayTitle)
peersText.append("**")
}
@ -641,13 +651,13 @@ private final class SendInviteLinkScreenComponent: Component {
contentHeight += 8.0
let text: String
if hasPremiumRestrictedUsers {
if !premiumRestrictedUsers.isEmpty {
if component.link != nil {
//TODO:localize
text = "You can try to send an invite link instead."
} else {
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 {
text = environment.strings.SendInviteLink_TextUnavailableMultipleUsers(Int32(component.peers.count))
}
@ -655,13 +665,13 @@ private final class SendInviteLinkScreenComponent: Component {
} else {
if component.link != nil {
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 {
text = environment.strings.SendInviteLink_TextAvailableMultipleUsers(Int32(component.peers.count))
}
} else {
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 {
text = environment.strings.SendInviteLink_TextUnavailableMultipleUsers(Int32(component.peers.count))
}
@ -696,6 +706,7 @@ private final class SendInviteLinkScreenComponent: Component {
contentHeight += descriptionTextFrame.height
contentHeight += 22.0
initialContentHeight = contentHeight
var singleItemHeight: CGFloat = 0.0
@ -706,7 +717,7 @@ private final class SendInviteLinkScreenComponent: Component {
for _ in 0 ..< 1 {
//let id: AnyHashable = AnyHashable("\(peer.id)_\(j)")
let id = AnyHashable(peer.id)
let id = AnyHashable(peer.peer.id)
validIds.append(id)
let item: ComponentView<Empty>
@ -719,6 +730,15 @@ private final class SendInviteLinkScreenComponent: Component {
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(
transition: itemTransition,
component: AnyComponent(PeerListItemComponent(
@ -726,15 +746,18 @@ private final class SendInviteLinkScreenComponent: Component {
theme: environment.theme,
strings: environment.strings,
sideInset: 0.0,
title: peer.displayTitle(strings: environment.strings, displayOrder: .firstLast),
peer: peer,
presence: component.peerPresences[peer.id],
selectionState: component.link == nil ? .none : .editing(isSelected: self.selectedItems.contains(peer.id)),
title: peer.peer.displayTitle(strings: environment.strings, displayOrder: .firstLast),
subtitle: itemSubtitle,
peer: peer.peer,
selectionState: !canBeSelected ? .none : .editing(isSelected: self.selectedItems.contains(peer.peer.id)),
hasNext: i != component.peers.count - 1,
action: { [weak self] peer in
guard let self else {
return
}
if !canBeSelected {
return
}
if self.selectedItems.contains(peer.id) {
self.selectedItems.remove(peer.id)
} 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)))
initialContentHeight += singleItemHeight + 16.0
initialContentHeight += min(itemsHeight, floor(singleItemHeight * 2.5))
contentHeight += itemsHeight
@ -805,16 +827,16 @@ private final class SendInviteLinkScreenComponent: Component {
if self.selectedItems.isEmpty {
controller.dismiss()
} 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 text: String
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 {
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 {
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 }
@ -863,7 +885,7 @@ private final class SendInviteLinkScreenComponent: Component {
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
@ -906,13 +928,13 @@ private final class SendInviteLinkScreenComponent: Component {
public class SendInviteLinkScreen: ViewControllerComponentContainer {
private let context: AccountContext
private let link: String?
private let peers: [EnginePeer]
private let peers: [TelegramForbiddenInvitePeer]
private var isDismissed: Bool = false
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
var link = link
@ -920,6 +942,120 @@ public class SendInviteLinkScreen: ViewControllerComponentContainer {
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.peers = peers
@ -930,7 +1066,7 @@ public class SendInviteLinkScreen: ViewControllerComponentContainer {
self.blocksBackgroundWhenInOverlay = true
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
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: {
openCreateNewChannelImpl?()
})
], includeChatList: false, topPeers: .none)), onlyWriteable: false, displayPermissionPlaceholder: false)
], includeChatList: false, topPeers: .none)), onlyWriteable: false, isGroupInvitation: false, displayPermissionPlaceholder: false)
super.init()

View File

@ -83,6 +83,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
private let options: [ContactListAdditionalOption]
private let filters: [ContactListFilter]
private let onlyWriteable: Bool
private let isGroupInvitation: Bool
private let limit: Int32?
init(_ params: ContactMultiselectionControllerParams) {
@ -94,6 +95,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
self.options = params.options
self.filters = params.filters
self.onlyWriteable = params.onlyWriteable
self.isGroupInvitation = params.isGroupInvitation
self.limit = params.limit
self.presentationData = params.updatedPresentationData?.initial ?? params.context.sharedContext.currentPresentationData.with { $0 }
@ -302,7 +304,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
}
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)
})
switch self.contactsNode.contentNode {

View File

@ -78,8 +78,9 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
private let isPeerEnabled: ((EnginePeer) -> 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.context = context
@ -90,6 +91,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
self.isPeerEnabled = isPeerEnabled
self.onlyWriteable = onlyWriteable
self.isGroupInvitation = isGroupInvitation
var proceedImpl: (() -> Void)?
@ -231,7 +233,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
} else {
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)
if !selectedPeers.isEmpty {
@ -359,7 +361,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
searchChannels: searchChannels,
globalSearch: globalSearch,
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
self?.tokenListNode.setText("")
self?.openPeer?(peer)

View File

@ -68,7 +68,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
self.filters = filters
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)
} : nil, multipleSelection: multipleSelection)

View File

@ -676,7 +676,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
case .supergroup:
createSignal = context.engine.peers.createSupergroup(title: title, description: nil)
|> map { peerId -> CreateGroupResult? in
return CreateGroupResult(peerId: peerId, failedToInvitePeerIds: [])
return CreateGroupResult(peerId: peerId, result: TelegramInvitePeersResult(forbiddenPeers: []))
}
|> mapError { error -> CreateGroupError in
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))
|> map { peerId -> CreateGroupResult? in
return CreateGroupResult(peerId: peerId, failedToInvitePeerIds: [])
return CreateGroupResult(peerId: peerId, result: TelegramInvitePeersResult(forbiddenPeers: []))
}
|> mapError { error -> CreateGroupError in
switch error {
@ -731,7 +731,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
let createGroupSignal: (Bool) -> Signal<CreateGroupResult?, CreateGroupError> = { isForum in
return context.engine.peers.createSupergroup(title: title, description: nil, isForum: isForum)
|> map { peerId -> CreateGroupResult? in
return CreateGroupResult(peerId: peerId, failedToInvitePeerIds: [])
return CreateGroupResult(peerId: peerId, result: TelegramInvitePeersResult(forbiddenPeers: []))
}
|> mapError { error -> CreateGroupError in
switch error {
@ -834,7 +834,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
let controller = ChatControllerImpl(context: context, chatLocation: .peer(id: result.peerId))
replaceControllerImpl?(controller)
if !result.failedToInvitePeerIds.isEmpty {
if !result.result.forbiddenPeers.isEmpty {
context.account.viewTracker.forceUpdateCachedPeerData(peerId: result.peerId)
let _ = (context.engine.data.subscribe(
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)
)
|> deliverOnMainQueue).start(next: { peer in
let _ = controller
let _ = exportedInvitation
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:)))
)
|> 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)
}
})
let inviteScreen = SendInviteLinkScreen(context: context, peer: peer, link: link, peers: result.result.forbiddenPeers)
controller?.push(inviteScreen)
}
})
})