mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-06-20 18:24:43 +00:00
d1aa0db537
Squash of 63 commits spanning waves 46-93 (plus interspersed docs commits) of the gradual Postbox->TelegramEngine consumer-side migration. Scope: 139 files changed, 2123 insertions(+), 452 deletions(-). ## Themes by wave-block **Waves 46-58 — Peer field migrations + facade additions** Foundational EnginePeer convenience init additions (PeerReference, RenderedPeer, SelectivePrivacyPeer). Multiple `peer: Peer` field migrations across PeerInfo, ChatList, and SettingsUI components. **Waves 59-73 — peer field cascade + EnginePeer wrap drops** Series of single- to two-file peer-field migrations; consumer-side wrap removal (`EnginePeer(peer)` -> direct EnginePeer use); `as? TelegramUser` cast conversion to `case let .user(...)` enum match. Wave 64: RenderedPeer convenience init. Wave 68: SelectivePrivacyPeer convenience init. **Waves 74-83 — controller-Node bridge cleanup + small migrations** Wave-71 shadow-pattern cleanup at controller->Node bridges. Migrations of ChatRecentActionsController.peer (74), PeerInfoMember (75), MentionChatInputPanelItem (76), PassportUI SecureIdAuthController (77), AccountWithInfo + ShareController (78), peerInputActivitiesPromise (79), InactiveChannel (80), BlockedPeers (81), openHashtag resolveSignal (82), NotificationExceptionsList (83). **Waves 84-90 — TelegramEngine.Resources facade migrations** Per-method Shape-A/B sweeps converting `<ctx>.account.postbox.mediaBox.X(...)` to `<ctx>.engine.resources.X(...)`. Wave 90 was a single-commit big sweep: 40 fetchedMediaResource sites in 25 files migrated to engine.resources.fetch facade in one atomic pass with first-pass-clean build. Methods covered: storeResourceData, completedResourcePath, cancelInteractiveResourceFetch, resourceRangesStatus, resourceStatus, fetch (fetchedMediaResource). **Waves 91-92 — additional type migrations** Wave 91: ItemListWebsiteItem.peer + RecentSessionsController enum-case payload + openWebSession callback Peer? -> EnginePeer?. Wave 92: ChatListController StateHolder.EntryContext status type MediaResourceStatus -> EngineMediaResource.FetchStatus. **Wave 93 — speculative `import Postbox` drop sweep** Drop import from 7 wave-touched files where it became unused; restore in 5 files where bare PeerId/Message/MediaId/StoryId references escaped the pre-flight regex. Includes one MediaId(...) -> EngineMedia.Id(...) swap in InAppPurchaseManager to unlock its import drop. ## Build state Final state at squash: clean Telegram/Telegram build at debug_sim_arm64. ## Persistent-state notes - Pre-existing WIP unchanged across the squashed range: - build-system/bazel-rules/sourcekit-bazel-bsp submodule marker - Untracked: build-system/tulsi/, submodules/TgVoip/, third-party/libx264/ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
228 lines
13 KiB
Swift
228 lines
13 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import AccountContext
|
|
import TelegramPresentationData
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramCore
|
|
import InviteLinksUI
|
|
import SendInviteLinkScreen
|
|
import UndoUI
|
|
import PresentationDataUtils
|
|
|
|
public func presentAddMembersImpl(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, parentController: ViewController, groupPeer: EnginePeer, selectAddMemberDisposable: MetaDisposable, addMemberDisposable: MetaDisposable) {
|
|
let members: Promise<[PeerId]> = Promise()
|
|
if groupPeer.id.namespace == Namespaces.Peer.CloudChannel {
|
|
/*var membersDisposable: Disposable?
|
|
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { listState in
|
|
members.set(.single(listState.list.map {$0.peer.id}))
|
|
membersDisposable?.dispose()
|
|
})
|
|
membersDisposable = disposable*/
|
|
members.set(.single([]))
|
|
} else {
|
|
members.set(.single([]))
|
|
}
|
|
|
|
let _ = (members.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).startStandalone(next: { [weak parentController] recentIds in
|
|
var createInviteLinkImpl: (() -> Void)?
|
|
var confirmationImpl: ((PeerId) -> Signal<Bool, NoError>)?
|
|
let _ = confirmationImpl
|
|
var options: [ContactListAdditionalOption] = []
|
|
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
|
|
|
var canCreateInviteLink = false
|
|
if case let .legacyGroup(group) = groupPeer {
|
|
switch group.role {
|
|
case .creator:
|
|
canCreateInviteLink = true
|
|
case let .admin(rights, _):
|
|
canCreateInviteLink = rights.rights.contains(.canInviteUsers)
|
|
default:
|
|
break
|
|
}
|
|
} else if case let .channel(channel) = groupPeer, (channel.addressName?.isEmpty ?? true) {
|
|
if channel.flags.contains(.isCreator) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) {
|
|
canCreateInviteLink = true
|
|
}
|
|
}
|
|
|
|
if canCreateInviteLink {
|
|
options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: .generic(UIImage(bundleImageName: "Contact List/LinkActionIcon")!), action: {
|
|
createInviteLinkImpl?()
|
|
}, clearHighlightAutomatically: true))
|
|
}
|
|
|
|
let contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), options: .single(options), filters: [.excludeSelf, .disable(recentIds)], onlyWriteable: true, isGroupInvitation: true))
|
|
contactsController.navigationPresentation = .modal
|
|
|
|
confirmationImpl = { [weak contactsController] peerId in
|
|
return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
|
|> mapToSignal { peer -> Signal<EnginePeer, NoError> in
|
|
if let peer {
|
|
return .single(peer)
|
|
} else {
|
|
return .never()
|
|
}
|
|
}
|
|
|> deliverOnMainQueue
|
|
|> mapToSignal { peer in
|
|
let result = ValuePromise<Bool>()
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
if let contactsController = contactsController {
|
|
let alertController = textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.GroupInfo_AddParticipantConfirmation(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, actions: [
|
|
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_No, action: {
|
|
result.set(false)
|
|
}),
|
|
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: {
|
|
result.set(true)
|
|
})
|
|
])
|
|
contactsController.present(alertController, in: .window(.root))
|
|
}
|
|
|
|
return result.get()
|
|
}
|
|
}
|
|
|
|
let addMembers: ([ContactListPeerId]) -> Signal<[(PeerId, AddChannelMemberError)], NoError> = { members -> Signal<[(PeerId, AddChannelMemberError)], NoError> in
|
|
let memberIds = members.compactMap { contact -> PeerId? in
|
|
switch contact {
|
|
case let .peer(peerId):
|
|
return peerId
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
return context.account.postbox.multiplePeersView(memberIds)
|
|
|> take(1)
|
|
|> deliverOnMainQueue
|
|
|> mapToSignal { view -> Signal<[(PeerId, AddChannelMemberError)], NoError> in
|
|
if groupPeer.id.namespace == Namespaces.Peer.CloudChannel {
|
|
if memberIds.count == 1 {
|
|
return context.peerChannelMemberCategoriesContextsManager.addMember(engine: context.engine, peerId: groupPeer.id, memberId: memberIds[0])
|
|
|> map { _ -> [(PeerId, AddChannelMemberError)] in
|
|
}
|
|
|> then(Signal<[(PeerId, AddChannelMemberError)], AddChannelMemberError>.single([]))
|
|
|> `catch` { error -> Signal<[(PeerId, AddChannelMemberError)], NoError> in
|
|
return .single([(memberIds[0], error)])
|
|
}
|
|
} else {
|
|
return context.peerChannelMemberCategoriesContextsManager.addMembersAllowPartial(engine: context.engine, peerId: groupPeer.id, memberIds: memberIds)
|
|
}
|
|
} else {
|
|
var signals: [Signal<(PeerId, AddChannelMemberError)?, NoError>] = []
|
|
for memberId in memberIds {
|
|
let signal: Signal<(PeerId, AddChannelMemberError)?, NoError> = context.engine.peers.addGroupMember(peerId: groupPeer.id, memberId: memberId)
|
|
|> mapError { error -> AddChannelMemberError in
|
|
switch error {
|
|
case .generic:
|
|
return .generic
|
|
case .groupFull:
|
|
return .limitExceeded
|
|
case let .privacy(privacy):
|
|
return .restricted(privacy?.forbiddenPeers.first)
|
|
case .notMutualContact:
|
|
return .notMutualContact
|
|
case .tooManyChannels:
|
|
return .generic
|
|
}
|
|
}
|
|
|> ignoreValues
|
|
|> map { _ -> (PeerId, AddChannelMemberError)? in
|
|
}
|
|
|> then(Signal<(PeerId, AddChannelMemberError)?, AddChannelMemberError>.single(nil))
|
|
|> `catch` { error -> Signal<(PeerId, AddChannelMemberError)?, NoError> in
|
|
return .single((memberId, error))
|
|
}
|
|
signals.append(signal)
|
|
}
|
|
return combineLatest(signals)
|
|
|> map { values -> [(PeerId, AddChannelMemberError)] in
|
|
return values.compactMap { $0 }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
createInviteLinkImpl = { [weak contactsController] in
|
|
contactsController?.view.window?.endEditing(true)
|
|
contactsController?.present(InviteLinkInviteController(context: context, updatedPresentationData: updatedPresentationData, mode: .groupOrChannel(peerId: groupPeer.id), initialInvite: nil, parentNavigationController: contactsController?.navigationController as? NavigationController), in: .window(.root))
|
|
}
|
|
|
|
parentController?.push(contactsController)
|
|
do {
|
|
selectAddMemberDisposable.set((
|
|
combineLatest(queue: .mainQueue(),
|
|
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: groupPeer.id)),
|
|
contactsController.result
|
|
)
|
|
|> deliverOnMainQueue).start(next: { [weak contactsController] exportedInvitation, result in
|
|
var peers: [ContactListPeerId] = []
|
|
if case let .result(peerIdsValue, _) = result {
|
|
peers = peerIdsValue
|
|
}
|
|
|
|
contactsController?.displayProgress = true
|
|
addMemberDisposable.set((addMembers(peers)
|
|
|> deliverOnMainQueue).start(next: { failedPeerIds in
|
|
if failedPeerIds.isEmpty {
|
|
contactsController?.dismiss()
|
|
|
|
let mappedPeerIds: [EnginePeer.Id] = peers.compactMap { peer -> EnginePeer.Id? in
|
|
switch peer {
|
|
case let .peer(id):
|
|
return id
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
if !mappedPeerIds.isEmpty {
|
|
let _ = (context.engine.data.get(EngineDataMap(mappedPeerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))))
|
|
|> deliverOnMainQueue).startStandalone(next: { maybePeers in
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
let peers = maybePeers.compactMap { $0.value }
|
|
|
|
let text: String
|
|
if peers.count == 1 {
|
|
text = presentationData.strings.PeerInfo_NotificationMemberAdded(peers[0].displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string
|
|
} else {
|
|
text = presentationData.strings.PeerInfo_NotificationMultipleMembersAdded(Int32(peers.count))
|
|
}
|
|
parentController?.present(UndoOverlayController(presentationData: presentationData, content: .peers(context: context, peers: peers, title: nil, text: text, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
|
})
|
|
}
|
|
} else {
|
|
let failedPeers = failedPeerIds.compactMap { _, error -> TelegramForbiddenInvitePeer? in
|
|
if case let .restricted(peer) = error {
|
|
return peer
|
|
} else {
|
|
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, subject: .chat(peer: groupPeer, link: exportedInvitation?.link), peers: failedPeers)
|
|
viewControllers.remove(at: index)
|
|
viewControllers.append(inviteScreen)
|
|
navigationController.setViewControllers(viewControllers, animated: true)
|
|
}
|
|
} else {
|
|
contactsController?.dismiss()
|
|
}
|
|
}
|
|
}))
|
|
}))
|
|
contactsController.dismissed = {
|
|
selectAddMemberDisposable.set(nil)
|
|
addMemberDisposable.set(nil)
|
|
}
|
|
}
|
|
})
|
|
}
|