Telegram-iOS/submodules/AccountContext/Sources/ContactSelectionController.swift
isaac 86d1456552 Postbox -> TelegramEngine waves 107-137 (squashed)
31 waves of consumer-side migration from `import Postbox` to TelegramEngine
typealiases. Net: 173 import drops + 39 BUILD-dep drops + 1 new typealias
(`EngineStoryId = StoryId`, wave 113).

Wave shapes used:
- Orphan-import sweeps (107, 108, 128): drop `import Postbox` from files
  whose only Postbox-symbol reference was the import line itself, then
  resolve build failures. Methodology requires token-level (`grep -oE`)
  filtering, not line-level, to avoid masking real Postbox usage on lines
  that also contain `Namespaces.X` references.
- Identifier-swap mini-waves (109-127, 129-134, 136-137): rename
  Postbox-typealiased identifiers to engine equivalents
  (PeerId -> EnginePeer.Id, MessageId -> EngineMessage.Id,
  MediaId -> EngineMedia.Id, MessageIndex -> EngineMessage.Index,
  StoryId -> EngineStoryId, ItemCollectionId -> EngineItemCollectionId,
  PreferencesEntry -> EnginePreferencesEntry,
  FetchResourceSourceType/Error -> EngineFetchResourceSourceType/Error,
  MemoryBuffer -> EngineMemoryBuffer, MessageTags -> EngineMessage.Tags,
  MessageAttribute -> EngineMessage.Attribute,
  TempBox -> EngineTempBox).
- Asset-string FP-only orphans (124).
- Typealias addition + drain (113): added `EngineStoryId` typealias to
  TelegramCore, then drained 3+11 consumer sites.

Hard blockers identified during these waves (must restore `import Postbox`
when present): MediaResource[A-Za-z]* (any suffix -- the literal
`MediaResource` matches don't catch MediaResourceData/MediaResourceId/etc.),
Postbox/MediaBox/MediaResource raw types, PostboxCoding/PostboxEncoder/
PostboxDecoder, TempBoxFile, ValueBoxKey, PostboxView, combinedView,
HashFunctions, postboxLog, openPostbox, declareEncodable, PeerView,
MessageHistoryView, MessageHistoryThreadData, CachedPeerData, RenderedPeer,
SelectivePrivacyPeer, SimpleDictionary, ItemCollectionInfosView,
ItemCollectionItem, ItemCollectionItemIndex, ItemCollectionViewEntryIndex,
ChatListIndex, ChatListEntrySummaryComponents, CodableEntry,
MessageHistoryThread, MessageHistoryAnchorIndex,
MessageHistoryEntryLocation, PeerStoryStats, PeerNameIndex,
PeerSummaryCounterTags, ChatListTotalUnreadStateCategory/Stats,
arePeersEqual. Protocol-shape blockers: bare `Peer`/`Message`/`Media`
in function signatures, generic args, enum-case payloads, or dict value
types (e.g., `[PeerId: Peer]`, `case messages([Message])`,
`Signal<(Peer?, ...), NoError>`).

`replace_all PeerId -> EnginePeer.Id` is dangerous: mangles compound
names like `failedPeerId`, `ContactListPeerId`, `nextRemoteMediaId`,
`replyToMessageId`. Pre-flight grep `\b[a-z][a-zA-Z]*PeerId\b` and only
replace_all if 0 matches.

Also removes unneeded design/plan docs from a separate (link-highlighting)
feature branch:
- docs/superpowers/plans/2026-05-02-link-highlighting-modern-path-fixes.md
- docs/superpowers/specs/2026-05-02-link-highlighting-modern-path-fixes-design.md

Squashed commits: 6d82c2980d..e6de5d53a3 (59 commits, including
per-wave content commits and per-wave CLAUDE.md bumps).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 10:28:50 +02:00

164 lines
5.9 KiB
Swift

import Foundation
import Display
import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
public protocol ContactSelectionController: ViewController {
var result: Signal<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?, ChatSendMessageActionSheetController.SendParameters?)?, NoError> { get }
var displayProgress: Bool { get set }
var dismissed: (() -> Void)? { get set }
var presentScheduleTimePicker: (@escaping (Int32, Int32?, Bool) -> Void) -> Void { get set }
func dismissSearch()
}
public enum ContactSelectionControllerMode {
case generic
case starsGifting(birthdays: [EnginePeer.Id: TelegramBirthday]?, hasActions: Bool, showSelf: Bool, selfSubtitle: String?)
}
public struct ContactListAdditionalOption: Equatable {
public enum Style: Equatable {
case accent
case generic
}
public let title: String
public let subtitle: String?
public let icon: ContactListActionItemIcon
public let style: Style
public let action: () -> Void
public let clearHighlightAutomatically: Bool
public init(title: String, subtitle: String? = nil, icon: ContactListActionItemIcon, style: Style = .accent, action: @escaping () -> Void, clearHighlightAutomatically: Bool = false) {
self.title = title
self.subtitle = subtitle
self.icon = icon
self.style = style
self.action = action
self.clearHighlightAutomatically = clearHighlightAutomatically
}
public static func ==(lhs: ContactListAdditionalOption, rhs: ContactListAdditionalOption) -> Bool {
return lhs.title == rhs.title && lhs.subtitle == rhs.subtitle && lhs.icon == rhs.icon && lhs.style == rhs.style
}
}
public enum ContactListPeerId: Hashable {
case peer(EnginePeer.Id)
case deviceContact(DeviceContactStableId)
}
public enum ContactListAction: Equatable {
case generic
case voiceCall
case videoCall
case more
}
public enum ContactListPeer: Equatable {
case peer(peer: EnginePeer, isGlobal: Bool, participantCount: Int32?)
case deviceContact(DeviceContactStableId, DeviceContactBasicData)
public var id: ContactListPeerId {
switch self {
case let .peer(peer, _, _):
return .peer(peer.id)
case let .deviceContact(id, _):
return .deviceContact(id)
}
}
public var indexName: EnginePeer.IndexName {
switch self {
case let .peer(peer, _, _):
return peer.indexName
case let .deviceContact(_, contact):
return .personName(first: contact.firstName, last: contact.lastName, addressNames: [], phoneNumber: "")
}
}
public static func ==(lhs: ContactListPeer, rhs: ContactListPeer) -> Bool {
switch lhs {
case let .peer(lhsPeer, lhsIsGlobal, lhsParticipantCount):
if case let .peer(rhsPeer, rhsIsGlobal, rhsParticipantCount) = rhs, lhsPeer == rhsPeer, lhsIsGlobal == rhsIsGlobal, lhsParticipantCount == rhsParticipantCount {
return true
} else {
return false
}
case let .deviceContact(id, contact):
if case .deviceContact(id, contact) = rhs {
return true
} else {
return false
}
}
}
}
public final class ContactSelectionControllerParams {
public enum MultipleSelectionMode {
case disabled
case possible
case always
}
public enum Style {
case glass
case legacy
}
public let context: AccountContext
public let style: Style
public let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
public let mode: ContactSelectionControllerMode
public let autoDismiss: Bool
public let title: (PresentationStrings) -> String
public let options: Signal<[ContactListAdditionalOption], NoError>
public let displayDeviceContacts: Bool
public let displayCallIcons: Bool
public let multipleSelection: MultipleSelectionMode
public let requirePhoneNumbers: Bool
public let allowChannelsInSearch: Bool
public let confirmation: (ContactListPeer) -> Signal<Bool, NoError>
public let isPeerEnabled: (ContactListPeer) -> Bool
public let openProfile: ((EnginePeer) -> Void)?
public let sendMessage: ((EnginePeer) -> Void)?
public init(
context: AccountContext,
style: Style = .legacy,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
mode: ContactSelectionControllerMode = .generic,
autoDismiss: Bool = true,
title: @escaping (PresentationStrings) -> String,
options: Signal<[ContactListAdditionalOption], NoError> = .single([]),
displayDeviceContacts: Bool = false,
displayCallIcons: Bool = false,
multipleSelection: MultipleSelectionMode = .disabled,
requirePhoneNumbers: Bool = false,
allowChannelsInSearch: Bool = false,
confirmation: @escaping (ContactListPeer) -> Signal<Bool, NoError> = { _ in .single(true) },
isPeerEnabled: @escaping (ContactListPeer) -> Bool = { _ in true },
openProfile: ((EnginePeer) -> Void)? = nil,
sendMessage: ((EnginePeer) -> Void)? = nil
) {
self.context = context
self.style = style
self.updatedPresentationData = updatedPresentationData
self.mode = mode
self.autoDismiss = autoDismiss
self.title = title
self.options = options
self.displayDeviceContacts = displayDeviceContacts
self.displayCallIcons = displayCallIcons
self.multipleSelection = multipleSelection
self.requirePhoneNumbers = requirePhoneNumbers
self.allowChannelsInSearch = allowChannelsInSearch
self.confirmation = confirmation
self.isPeerEnabled = isPeerEnabled
self.openProfile = openProfile
self.sendMessage = sendMessage
}
}