mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-06-20 18:24:43 +00:00
86d1456552
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>
242 lines
11 KiB
Swift
242 lines
11 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import AccountContext
|
|
import TelegramCore
|
|
import SwiftSignalKit
|
|
import TelegramPresentationData
|
|
import LegacyComponents
|
|
import LegacyUI
|
|
import AttachmentUI
|
|
import MediaPickerUI
|
|
import LegacyCamera
|
|
import LegacyMediaPickerUI
|
|
import LocationUI
|
|
import AttachmentFileController
|
|
import ChatEntityKeyboardInputNode
|
|
import ICloudResources
|
|
|
|
public enum PollAttachmentSubject {
|
|
case description
|
|
case quizAnswer
|
|
case option
|
|
}
|
|
|
|
public func presentPollAttachmentScreen(
|
|
context: AccountContext,
|
|
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
|
|
subject: PollAttachmentSubject,
|
|
availableButtons: [AttachmentButtonType],
|
|
inputMediaNodeData: Signal<ChatEntityKeyboardInputNode.InputData?, NoError>? = nil,
|
|
present: @escaping (ViewController, Bool) -> Void,
|
|
completion: @escaping (AnyMediaReference) -> Void
|
|
) {
|
|
let attachmentController = AttachmentController(
|
|
context: context,
|
|
updatedPresentationData: updatedPresentationData,
|
|
style: .glass,
|
|
chatLocation: nil,
|
|
isScheduledMessages: false,
|
|
buttons: availableButtons,
|
|
initialButton: .gallery
|
|
)
|
|
let inputMediaNodeDataPromise = Promise<ChatEntityKeyboardInputNode.InputData?>(nil)
|
|
if let inputMediaNodeData {
|
|
inputMediaNodeDataPromise.set(inputMediaNodeData)
|
|
} else if availableButtons.contains(.sticker) || availableButtons.contains(.emoji) {
|
|
inputMediaNodeDataPromise.set(.single(nil) |> then(
|
|
ChatEntityKeyboardInputNode.inputData(
|
|
context: context,
|
|
chatPeerId: nil,
|
|
areCustomEmojiEnabled: true,
|
|
hasTrending: false,
|
|
hasSearch: true,
|
|
hasStickers: true,
|
|
hasGifs: false,
|
|
hideBackground: true,
|
|
maskEdge: .fade,
|
|
sendGif: nil
|
|
)
|
|
|> map(Optional.init)
|
|
))
|
|
}
|
|
|
|
attachmentController.requestController = { [weak attachmentController] type, controllerCompletion in
|
|
let mediaPickerPollSubject: MediaPickerScreenImpl.Subject.AssetsMode.PollMode
|
|
let filePickerPollSubject: AttachmentFileControllerSource.PollMode
|
|
let locationPickerPollSubject: LocationPickerController.Source.PollMode
|
|
let stickerPickerPollSubject: StickerAttachmentScreen.Source.PollMode
|
|
switch subject {
|
|
case .description:
|
|
mediaPickerPollSubject = .description
|
|
filePickerPollSubject = .description
|
|
locationPickerPollSubject = .description
|
|
stickerPickerPollSubject = .description
|
|
case .quizAnswer:
|
|
mediaPickerPollSubject = .quizAnswer
|
|
filePickerPollSubject = .quizAnswer
|
|
locationPickerPollSubject = .quizAnswer
|
|
stickerPickerPollSubject = .quizAnswer
|
|
default:
|
|
mediaPickerPollSubject = .option
|
|
filePickerPollSubject = .description
|
|
locationPickerPollSubject = .option
|
|
stickerPickerPollSubject = .option
|
|
}
|
|
|
|
switch type {
|
|
case .gallery:
|
|
let controller = MediaPickerScreenImpl(
|
|
context: context,
|
|
updatedPresentationData: updatedPresentationData,
|
|
style: .glass,
|
|
peer: nil,
|
|
threadTitle: nil,
|
|
chatLocation: nil,
|
|
enableMultiselection: false,
|
|
subject: .assets(nil, .poll(mode: mediaPickerPollSubject, asFile: false))
|
|
)
|
|
controller.getCaptionPanelView = {
|
|
return nil
|
|
}
|
|
controller.legacyCompletion = { _, signals, _, _, _, _, sendCompletion in
|
|
let _ = (legacyAssetPickerEnqueueMessages(context: context, account: context.account, signals: signals)
|
|
|> deliverOnMainQueue).start(next: { items in
|
|
if let item = items.first, case let .message(_, _, _, mediaReference, _, _, _, _, _, _) = item.message, let mediaReference {
|
|
completion(mediaReference)
|
|
sendCompletion()
|
|
}
|
|
})
|
|
}
|
|
controllerCompletion(controller, controller.mediaPickerContext)
|
|
return true
|
|
case .file:
|
|
let controller = makeAttachmentFileControllerImpl(
|
|
context: context,
|
|
updatedPresentationData: updatedPresentationData,
|
|
source: .poll(filePickerPollSubject),
|
|
bannedSendMedia: nil,
|
|
presentGallery: { [weak attachmentController] in
|
|
attachmentController?.dismiss(animated: true)
|
|
|
|
let controller = MediaPickerScreenImpl(
|
|
context: context,
|
|
updatedPresentationData: updatedPresentationData,
|
|
style: .glass,
|
|
peer: nil,
|
|
threadTitle: nil,
|
|
chatLocation: nil,
|
|
enableMultiselection: false,
|
|
subject: .assets(nil, .poll(mode: mediaPickerPollSubject, asFile: true))
|
|
)
|
|
controller.getCaptionPanelView = {
|
|
return nil
|
|
}
|
|
controller.legacyCompletion = { _, signals, _, _, _, _, sendCompletion in
|
|
let _ = (legacyAssetPickerEnqueueMessages(context: context, account: context.account, signals: signals)
|
|
|> deliverOnMainQueue).start(next: { items in
|
|
if let item = items.first, case let .message(_, _, _, mediaReference, _, _, _, _, _, _) = item.message, let mediaReference {
|
|
completion(mediaReference)
|
|
sendCompletion()
|
|
}
|
|
})
|
|
}
|
|
controller.navigationPresentation = .modal
|
|
present(controller, true)
|
|
},
|
|
presentFiles: { [weak attachmentController] in
|
|
attachmentController?.dismiss(animated: true)
|
|
|
|
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
|
let controller = legacyICloudFilePicker(theme: presentationData.theme, documentTypes: ["public.item"], completion: { urls in
|
|
guard let url = urls.first else {
|
|
return
|
|
}
|
|
let _ = (iCloudFileDescription(url)
|
|
|> deliverOnMainQueue).start(next: { item in
|
|
guard let item else {
|
|
return
|
|
}
|
|
let fileId = Int64.random(in: Int64.min ... Int64.max)
|
|
let mimeType = guessMimeTypeByFileExtension((item.fileName as NSString).pathExtension)
|
|
var previewRepresentations: [TelegramMediaImageRepresentation] = []
|
|
if mimeType.hasPrefix("image/") || mimeType == "application/pdf" {
|
|
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 320, height: 320), resource: ICloudFileResource(urlData: item.urlData, thumbnail: true), progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
|
|
}
|
|
var attributes: [TelegramMediaFileAttribute] = []
|
|
attributes.append(.FileName(fileName: item.fileName))
|
|
if let audioMetadata = item.audioMetadata {
|
|
attributes.append(.Audio(isVoice: false, duration: audioMetadata.duration, title: audioMetadata.title, performer: audioMetadata.performer, waveform: nil))
|
|
}
|
|
|
|
let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: fileId), partialReference: nil, resource: ICloudFileResource(urlData: item.urlData, thumbnail: false), previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: Int64(item.fileSize), attributes: attributes, alternativeRepresentations: [])
|
|
completion(.standalone(media: file))
|
|
})
|
|
})
|
|
present(controller, false)
|
|
},
|
|
presentDocumentScanner: nil,
|
|
send: { mediaReferences, _, _, _ in
|
|
completion(mediaReferences.first!)
|
|
}
|
|
) as! AttachmentFileControllerImpl
|
|
controllerCompletion(controller, controller.mediaPickerContext)
|
|
return true
|
|
case .location:
|
|
let controller = LocationPickerController(
|
|
context: context,
|
|
style: .glass,
|
|
updatedPresentationData: updatedPresentationData,
|
|
mode: .share(peer: nil, selfPeer: nil, hasLiveLocation: false),
|
|
source: .poll(locationPickerPollSubject),
|
|
completion: { location, _, _, _, _ in
|
|
completion(.standalone(media: location))
|
|
})
|
|
controllerCompletion(controller, controller.mediaPickerContext)
|
|
return true
|
|
case .sticker:
|
|
let _ = (inputMediaNodeDataPromise.get()
|
|
|> filter { $0 != nil }
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { content in
|
|
guard let content = content?.stickers else {
|
|
return
|
|
}
|
|
let controller = StickerAttachmentScreen(
|
|
context: context,
|
|
mode: .stickers(content),
|
|
source: .poll(stickerPickerPollSubject),
|
|
completion: { sticker in
|
|
completion(sticker)
|
|
}
|
|
)
|
|
controllerCompletion(controller, controller.mediaPickerContext)
|
|
})
|
|
return true
|
|
case .emoji:
|
|
let _ = (inputMediaNodeDataPromise.get()
|
|
|> filter { $0 != nil }
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { content in
|
|
guard let content = content?.emoji else {
|
|
return
|
|
}
|
|
let controller = StickerAttachmentScreen(
|
|
context: context,
|
|
mode: .emoji(content),
|
|
source: .poll(stickerPickerPollSubject),
|
|
completion: { sticker in
|
|
completion(sticker)
|
|
}
|
|
)
|
|
controllerCompletion(controller, controller.mediaPickerContext)
|
|
})
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
attachmentController.navigationPresentation = .flatModal
|
|
present(attachmentController, true)
|
|
}
|