Files
Telegram-iOS/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift
T
IsaacIsaacClaude Opus 4.6
f84e94f507 MediaResource → EngineMediaResource refactor: wave 2
Drive raw `MediaResource` out of the `TelegramEngine` public facade for
photo-upload APIs, and complete a first batch of consumer-side
migrations. Behavior-preserving. The `_internal_*` Postbox-facing
functions are untouched — only the facade signatures change, bridging
inside via `_asResource()` / `EngineMediaResource(_:)`.

TelegramEngine facades migrated (signatures now take EngineMediaResource):
- TelegramEngine.Peers.uploadedPeerPhoto / uploadedPeerVideo
- TelegramEngine.Peers.updatePeerPhoto (closure param too)
- TelegramEngine.AccountData.updateAccountPhoto / updateFallbackPhoto
- TelegramEngine.Contacts.updateContactPhoto
- TelegramEngine.Auth.uploadedPeerVideo

Consumer-side changes:
- MapResourceToAvatarSizes utility: signature is now
  (engine: TelegramEngine, resource: EngineMediaResource, ...). Uses
  engine.resources.data(id:) internally. The submodule drops
  `import Postbox` and the Bazel dep.
- AuthorizationUI: avatar-video signal retyped from
  Signal<TelegramMediaResource?> to Signal<EngineMediaResource?>.
- 27 `mapResourceToAvatarSizes(postbox:...)` call sites across 5
  TelegramUI/TelegramCallsUI files migrated to the new form.

Deferred:
- SaveToCameraRoll (planned Task 8) abandoned — module has three
  public functions taking `postbox: Postbox` (umbrella-type leak,
  banned by rule 2) and requires a full module-migration wave, not a
  type swap. Reason recorded in the wave-2 plan doc.
- Other TelegramEngine facades still leaking MediaResource
  (TelegramEngineStickers.uploadSticker; UploadSecureIdFile.* —
  additionally leaks Postbox) flagged for a future wave.

Docs:
- CLAUDE.md: new rule 7 (TelegramCore never imports UIKit/Display,
  shared with Telegram-Mac), and a new "MediaResource →
  EngineMediaResource consumer migration" section describing the
  wrap/unwrap helpers and the modify-in-place facade-bridging
  pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:07:54 +02:00

147 lines
7.7 KiB
Swift

import Foundation
import SwiftSignalKit
import Postbox
public extension TelegramEngine {
final class Contacts {
private let account: Account
init(account: Account) {
self.account = account
}
public func deleteContactPeerInteractively(peerId: PeerId) -> Signal<Never, NoError> {
return _internal_deleteContactPeerInteractively(account: self.account, peerId: peerId)
}
public func deleteContacts(peerIds: [PeerId]) -> Signal<Never, NoError> {
return _internal_deleteContacts(account: self.account, peerIds: peerIds)
}
public func deleteAllContacts() -> Signal<Never, NoError> {
return _internal_deleteAllContacts(account: self.account)
}
public func resetSavedContacts() -> Signal<Void, NoError> {
return _internal_resetSavedContacts(network: self.account.network)
}
public func updateContactName(peerId: PeerId, firstName: String, lastName: String) -> Signal<Void, UpdateContactNameError> {
return _internal_updateContactName(account: self.account, peerId: peerId, firstName: firstName, lastName: lastName)
}
public func updateContactPhoto(peerId: PeerId, resource: EngineMediaResource?, videoResource: EngineMediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mode: SetCustomPeerPhotoMode, mapResourceToAvatarSizes: @escaping (EngineMediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updateContactPhoto(account: self.account, peerId: peerId, resource: resource?._asResource(), videoResource: videoResource?._asResource(), videoStartTimestamp: videoStartTimestamp, markup: markup, mode: mode, mapResourceToAvatarSizes: { rawResource, representations in
return mapResourceToAvatarSizes(EngineMediaResource(rawResource), representations)
})
}
public func updateContactNote(peerId: PeerId, text: String, entities: [MessageTextEntity]) -> Signal<Never, UpdateContactNoteError> {
return _internal_updateContactNote(account: self.account, peerId: peerId, text: text, entities: entities)
}
public func deviceContactsImportedByCount(contacts: [(String, [DeviceContactNormalizedPhoneNumber])]) -> Signal<[String: Int32], NoError> {
return _internal_deviceContactsImportedByCount(postbox: self.account.postbox, contacts: contacts)
}
public func importContact(firstName: String, lastName: String, phoneNumber: String, noteText: String, noteEntities: [MessageTextEntity]) -> Signal<PeerId?, NoError> {
return _internal_importContact(account: self.account, firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, noteText: noteText, noteEntities: noteEntities)
}
public func addContactInteractively(peerId: PeerId, firstName: String, lastName: String, phoneNumber: String, noteText: String, noteEntities: [MessageTextEntity], addToPrivacyExceptions: Bool) -> Signal<Never, AddContactError> {
return _internal_addContactInteractively(account: self.account, peerId: peerId, firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, noteText: noteText, noteEntities: noteEntities, addToPrivacyExceptions: addToPrivacyExceptions)
}
public func acceptAndShareContact(peerId: PeerId) -> Signal<Never, AcceptAndShareContactError> {
return _internal_acceptAndShareContact(account: self.account, peerId: peerId)
}
public func searchRemotePeers(query: String, scope: TelegramSearchPeersScope = .everywhere) -> Signal<([FoundPeer], [FoundPeer]), NoError> {
return _internal_searchPeers(accountPeerId: self.account.peerId, postbox: self.account.postbox, network: self.account.network, query: query, scope: scope)
}
public func searchLocalPeers(query: String, scope: TelegramSearchPeersScope = .everywhere, predicate: ChatListFilterPredicate? = nil) -> Signal<[EngineRenderedPeer], NoError> {
return self.account.postbox.searchPeers(query: query, predicate: predicate)
|> map { peers in
switch scope {
case .everywhere:
return peers.map(EngineRenderedPeer.init)
case .channels:
return peers.filter { peer in
if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info {
return true
} else {
return false
}
}.map(EngineRenderedPeer.init)
case .groups:
return peers.filter { item in
if let channel = item.peer as? TelegramChannel, case .group = channel.info {
return true
} else if item.peer is TelegramGroup {
return true
} else {
return false
}
}.map(EngineRenderedPeer.init)
case .privateChats:
return peers.filter { item in
if item.peer is TelegramUser {
return true
} else {
return false
}
}.map(EngineRenderedPeer.init)
case .globalPosts:
return []
}
}
}
public func searchContacts(query: String) -> Signal<([EnginePeer], [EnginePeer.Id: EnginePeer.Presence]), NoError> {
return self.account.postbox.searchContacts(query: query)
|> map { peers, presences in
return (peers.map(EnginePeer.init), presences.mapValues(EnginePeer.Presence.init))
}
}
public func updateIsContactSynchronizationEnabled(isContactSynchronizationEnabled: Bool) -> Signal<Never, NoError> {
return self.account.postbox.transaction { transaction -> Void in
transaction.updatePreferencesEntry(key: PreferencesKeys.contactsSettings, { current in
var settings = current?.get(ContactsSettings.self) ?? ContactsSettings.defaultSettings
settings.synchronizeContacts = isContactSynchronizationEnabled
return PreferencesEntry(settings)
})
}
|> ignoreValues
}
public func findPeerByLocalContactIdentifier(identifier: String) -> Signal<EnginePeer?, NoError> {
return self.account.postbox.transaction { transaction -> EnginePeer? in
var foundPeerId: PeerId?
transaction.enumerateDeviceContactImportInfoItems({ _, value in
if let value = value as? TelegramDeviceContactImportedData {
switch value {
case let .imported(data, _, peerId):
if data.localIdentifiers.contains(identifier) {
if let peerId = peerId {
foundPeerId = peerId
return false
}
}
default:
break
}
}
return true
})
if let foundPeerId = foundPeerId {
return transaction.getPeer(foundPeerId).flatMap(EnginePeer.init)
} else {
return nil
}
}
}
}
}