diff --git a/CLAUDE.md b/CLAUDE.md index 05e7d9b794..8972129bc3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -41,7 +41,7 @@ A gradual migration is underway to eliminate direct `import Postbox` from consum **Historical record:** Wave-by-wave outcomes, the running tally of Postbox-free modules, and full verbose forms of the guidance subsections below live in [`docs/superpowers/postbox-refactor-log.md`](docs/superpowers/postbox-refactor-log.md). Read that file when you need wave-specific context, a full worked example of a pattern, or the history of a particular module's migration. -Waves landed so far (as of 2026-04-21): 26 waves plus standalone cleanups. See the log file for per-wave detail; the list of still-open migration opportunities lives in the `project_postbox_refactor_next_wave.md` memory file. +Waves landed so far (as of 2026-04-24): 36 waves plus standalone cleanups. See the log file for per-wave detail; the list of still-open migration opportunities lives in the `project_postbox_refactor_next_wave.md` memory file. ### Rules that apply to every wave @@ -120,14 +120,15 @@ Distilled lessons from waves 1–26. Each bullet below has a full-form counterpa Full per-shape recipe and wave-specific examples in the log. -### TelegramEngine.Resources facade inventory (as of wave 26) +### TelegramEngine.Resources facade inventory (as of wave 32) All mediaBox methods with clean signatures (no Postbox-protocol leaks, no complex return-type migrations) have been migrated to `TelegramEngine.Resources`. Quick reference for consumers — all of these live in `submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift`: | Facade | Wave | Wraps | |---|---|---| | `fetch(reference:userLocation:userContentType:)` | 3 | `fetchedMediaResource` | -| `status(resource:)` | 3 | `MediaBox.resourceStatus` | +| `status(resource:)` | 3 | `MediaBox.resourceStatus` (resource-based) | +| `status(id:, resourceSize:)` | 32 | `MediaBox.resourceStatus(_ id:, resourceSize:)` | | `data(resource:, pathExtension:, waitUntilFetchStatus:)` | 3 | `MediaBox.resourceData` (resource-based) | | `data(id:, attemptSynchronously:)` | 3 | `MediaBox.resourceData` (id-based, defaults to `.complete(waitUntilFetchStatus: false)`) | | `custom(id:, fetch:, cacheTimeout:, attemptSynchronously:)` | pre-wave-21 | `MediaBox.customResourceData` | diff --git a/docs/superpowers/plans/2026-04-24-contactlistpeer-engine-peer-migration.md b/docs/superpowers/plans/2026-04-24-contactlistpeer-engine-peer-migration.md new file mode 100644 index 0000000000..8732b7a790 --- /dev/null +++ b/docs/superpowers/plans/2026-04-24-contactlistpeer-engine-peer-migration.md @@ -0,0 +1,944 @@ +# Wave 36: `ContactListPeer.peer: Peer → EnginePeer` Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Migrate the public enum case `ContactListPeer.peer(peer: Peer, isGlobal: Bool, participantCount: Int32?)` from the Postbox `Peer` protocol to the TelegramCore `EnginePeer` enum in a single atomic commit. Cascading changes: change `ContactListPeer.indexName` return type from `PeerIndexNameRepresentation` to `EnginePeer.IndexName` (drops 2 `EnginePeer.IndexName(...)` wraps at one call site); rewrite the enum's custom `==` to use `EnginePeer`'s synthesized Equatable; drop 20 outflow `._asPeer()` bridges, 16 inflow `EnginePeer(peer)` wraps; rewrite 2 Postbox-concrete cast chains to EnginePeer case patterns. + +**Architecture:** One atomic commit. The enum-case payload change is necessarily atomic. `ContactListPeer` lives in `submodules/AccountContext/Sources/ContactSelectionController.swift`; 7 consumer files touched in addition. 2 consumer files verified untouched (`ComposeController.swift`, `ChatSendAudioMessageContextPreview.swift`). No new wrappers, no new typealiases. `import Postbox` stays in every touched consumer (follow-up unused-import sweep handles it). + +**Tech Stack:** Swift, Bazel build via Make.py wrapper. No tests — verification is build success + targeted grep checks. + +**Spec:** `docs/superpowers/specs/2026-04-24-contactlistpeer-engine-peer-migration-design.md` + +--- + +## File Structure + +**Modified files (8 expected — 1 definition + 7 consumer. Plus 2 verify-only.)** + +| File | Edits | Categories | +|---|---|---| +| `submodules/AccountContext/Sources/ContactSelectionController.swift` | 3 (case type + indexName return type + `==` body) | α | +| `submodules/ContactListUI/Sources/ContactListNode.swift` | ~21 (12 outflow + 4 inflow + 2 cast rewrites [L182-186, L1968] + 2 IndexName wraps [L517]) | β + δ + φ + ε′ | +| `submodules/ContactListUI/Sources/ContactsController.swift` | 1 (inflow wrap at L294) | δ | +| `submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift` | 7 (3 outflow + 4 inflow) | β + δ | +| `submodules/TelegramUI/Sources/ContactMultiselectionController.swift` | 6 (2 outflow + 4 inflow) | β + δ | +| `submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift` | 2 (1 outflow + 1 inflow) | β + δ | +| `submodules/TelegramUI/Sources/ContactSelectionController.swift` | 2 (inflow wraps L517/527) | δ | +| `submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift` | 2 (outflow bridges L160/230) | β | + +**Verify-only (no edits expected):** + +| File | Reason | +|---|---| +| `submodules/TelegramUI/Sources/ComposeController.swift` | Destructures at L120/160 access `.id` only. Same-type access works on EnginePeer. | +| `submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift` | Only holds `[ContactListPeer]` at collection level; no `.peer` destructures. | + +**EnginePeer enum case mapping (used in cast rewrites):** + +| Postbox concrete | EnginePeer case | +|---|---| +| `TelegramUser` | `.user(TelegramUser)` | +| `TelegramGroup` | `.legacyGroup(TelegramGroup)` | +| `TelegramChannel` | `.channel(TelegramChannel)` | + +**Sites that stay as `._asPeer()` bridges (NOT in wave scope):** + +- `submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift:488, 528, 562` — `canSendMessagesToPeer(peer._asPeer())` / `canSendMessagesToPeer(peer.peer._asPeer())`. `canSendMessagesToPeer(_: Peer)` migration is a deferred future wave. +- `submodules/TelegramUI/Sources/ContactMultiselectionController.swift:171, 201, 748` — `peerTokenTitle(accountPeerId:..., peer: peer._asPeer(), strings:...)`. `peerTokenTitle(peer: Peer)` migration is out of scope. + +--- + +## Task 1: Edit `AccountContext/Sources/ContactSelectionController.swift` — definition + +**Files:** +- Modify: `submodules/AccountContext/Sources/ContactSelectionController.swift` + +Foundational change. Without it, none of the consumer edits compile. + +- [ ] **Step 1.1: Update the case payload type, `indexName` return type, and `==` operator body** + +Edit using the Edit tool: + +```swift +// OLD (lines 61-99) +public enum ContactListPeer: Equatable { + case peer(peer: Peer, 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: PeerIndexNameRepresentation { + 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.isEqual(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 + } + } + } +} +``` + +```swift +// NEW +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 + } + } + } +} +``` + +Three changes in this edit: +1. Line 62: `peer: Peer` → `peer: EnginePeer` +2. Line 74: return type `PeerIndexNameRepresentation` → `EnginePeer.IndexName` +3. Line 86 (inside the `==` operator): `lhsPeer.isEqual(rhsPeer)` → `lhsPeer == rhsPeer` + +`EnginePeer.IndexName.personName(first:last:addressNames:phoneNumber:)` has the same labels/types as `PeerIndexNameRepresentation.personName`, so line 79 body is untouched — only its return target enum changes. + +- [ ] **Step 1.2: Verify** + +Run: + +```bash +grep -nE "case peer\(peer:|public var indexName:|\.isEqual\(" submodules/AccountContext/Sources/ContactSelectionController.swift +``` + +Expected output: +- Line 62: `case peer(peer: EnginePeer, ...)` +- Line 74: `public var indexName: EnginePeer.IndexName {` +- No `isEqual(` match on the `==` path (the only remaining occurrences would be unrelated). + +Do not commit yet. + +--- + +## Task 2: Edit `ContactListNode.swift` — largest consumer, multi-category + +**Files:** +- Modify: `submodules/ContactListUI/Sources/ContactListNode.swift` + +Most changes happen here: 12 outflow bridges + 4 inflow wraps + 2 cast chain rewrites + 2 IndexName wrap drops. + +- [ ] **Step 2.1: Drop the 12 outflow `._asPeer()` bridges via `replace_all`** + +All 12 `._asPeer()` bridges at ContactListPeer.peer construction sites follow the shape `._asPeer(), isGlobal:`. Non-construction `._asPeer()` uses in this file (if any) feed other functions and do NOT use this exact substring. + +Pre-flight verify: + +```bash +grep -cE "\._asPeer\(\), isGlobal:" submodules/ContactListUI/Sources/ContactListNode.swift +``` + +Expected: `12`. + +If the count is 12, apply the Edit tool with `replace_all=true`: +- `old_string`: `._asPeer(), isGlobal:` +- `new_string`: `, isGlobal:` + +If the count is not 12, fall back to per-site Edits at lines 632, 690, 701, 747, 765, 1365, 1647, 1656, 1693, 1731, 1942, 1944 using enough surrounding context to make each `old_string` unique. + +- [ ] **Step 2.2: Verify the 12 outflow drops** + +Run: + +```bash +grep -nE "\._asPeer\(\), isGlobal:" submodules/ContactListUI/Sources/ContactListNode.swift +``` + +Expected: zero matches. + +- [ ] **Step 2.3: Drop 2 inflow wraps at L204** + +Read lines 200–210 first to confirm the line text. + +Edit: + +```swift +// OLD (line 204) + itemPeer = .peer(peer: EnginePeer(peer), chatPeer: EnginePeer(peer)) +``` + +```swift +// NEW + itemPeer = .peer(peer: peer, chatPeer: peer) +``` + +- [ ] **Step 2.4: Drop 1 inflow wrap at L252** + +Read lines 248–256 first to confirm. + +Edit: + +```swift +// OLD (line 252) + interaction.openDisabledPeer(EnginePeer(peer), requiresPremiumForMessaging ? .premiumRequired : .generic) +``` + +```swift +// NEW + interaction.openDisabledPeer(peer, requiresPremiumForMessaging ? .premiumRequired : .generic) +``` + +- [ ] **Step 2.5: Drop 1 inflow wrap at L844** + +Read lines 840–848 first to confirm. + +Edit: + +```swift +// OLD (line 844) + if let isPeerEnabled, !isPeerEnabled(EnginePeer(peer)) { +``` + +```swift +// NEW + if let isPeerEnabled, !isPeerEnabled(peer) { +``` + +- [ ] **Step 2.6: Rewrite the L182-186 cast chain to EnginePeer case patterns** + +Read lines 176–200 first. The cast chain is inside the ContactListPeer.peer destructure at line 177. + +Edit: + +```swift +// OLD (lines 182-186) + } else { + if let _ = peer as? TelegramUser { + status = .presence(presence ?? EnginePeer.Presence(status: .longTimeAgo, lastActivity: 0), dateTimeFormat) + } else if let group = peer as? TelegramGroup { + status = .custom(string: NSAttributedString(string: strings.Conversation_StatusMembers(Int32(group.participantCount))), multiline: false, isActive: false, icon: nil) + } else if let channel = peer as? TelegramChannel { +``` + +```swift +// NEW + } else { + if case .user = peer { + status = .presence(presence ?? EnginePeer.Presence(status: .longTimeAgo, lastActivity: 0), dateTimeFormat) + } else if case let .legacyGroup(group) = peer { + status = .custom(string: NSAttributedString(string: strings.Conversation_StatusMembers(Int32(group.participantCount))), multiline: false, isActive: false, icon: nil) + } else if case let .channel(channel) = peer { +``` + +`channel.info` access inside the surviving inner block continues to compile unchanged (`EnginePeer.channel` wraps `TelegramChannel`). `group.participantCount` inside the `legacyGroup` branch works identically. The first branch doesn't bind the user — the `case .user = peer` form preserves that. + +- [ ] **Step 2.7: Rewrite the L1968 cast to an EnginePeer case pattern** + +Read lines 1964–1976 first. The cast is inside the ContactListPeer.peer destructure at line 1966. + +Edit: + +```swift +// OLD (lines 1967-1968) + if requirePhoneNumbers, + let user = peer as? TelegramUser { +``` + +```swift +// NEW + if requirePhoneNumbers, + case let .user(user) = peer { +``` + +`user.phone` on the following line continues to compile (`EnginePeer.user` wraps `TelegramUser`). + +- [ ] **Step 2.8: Drop 2 IndexName wraps at L517** + +Read lines 515–522 first. + +Edit: + +```swift +// OLD (line 517) + let result = EnginePeer.IndexName(lhs.indexName).isLessThan(other: EnginePeer.IndexName(rhs.indexName), ordering: sortOrder) +``` + +```swift +// NEW + let result = lhs.indexName.isLessThan(other: rhs.indexName, ordering: sortOrder) +``` + +`ContactListPeer.indexName` now returns `EnginePeer.IndexName` (from Task 1), and `isLessThan(other:ordering:)` is defined on `EnginePeer.IndexName` at `submodules/LocalizedPeerData/Sources/PeerTitle.swift:64`, so the wrap idiom is no longer required. + +- [ ] **Step 2.9: Verify ContactListNode.swift changes** + +Run: + +```bash +grep -nE "\._asPeer\(\), isGlobal:|EnginePeer\(peer\)|peer as\? Telegram(User|Group|Channel)\b|EnginePeer\.IndexName\(lhs\.indexName\)|EnginePeer\.IndexName\(rhs\.indexName\)" submodules/ContactListUI/Sources/ContactListNode.swift +``` + +Expected output: only `EnginePeer(peer)` matches at lines 1819 and 1825 (out-of-scope; `peer` there is from `entryData.renderedPeer.peer`, raw `Peer`, wraps stay). Similarly, `peer as? TelegramChannel` at 1802/1820 and `peer is TelegramGroup` at 1818 stay. + +If any other match appears, re-examine that site and apply the matching fix. + +--- + +## Task 3: Edit `ContactsController.swift` — 1 inflow wrap drop + +**Files:** +- Modify: `submodules/ContactListUI/Sources/ContactsController.swift` + +- [ ] **Step 3.1: Drop inflow wrap at L294** + +Read lines 285–300 first. + +Edit: + +```swift +// OLD (line 294) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)), purposefulAction: { [weak self] in +``` + +```swift +// NEW + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), purposefulAction: { [weak self] in +``` + +`peer` here is destructured from the ContactListPeer.peer case at line 287; post-migration it is already `EnginePeer`. `chatLocation: .peer(EnginePeer)` case takes `EnginePeer`. + +- [ ] **Step 3.2: Verify** + +Run: + +```bash +grep -nE "chatLocation: \.peer\(EnginePeer\(peer\)\)" submodules/ContactListUI/Sources/ContactsController.swift +``` + +Expected: zero matches. + +--- + +## Task 4: Edit `ContactsSearchContainerNode.swift` — 3 outflow + 4 inflow + +**Files:** +- Modify: `submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift` + +- [ ] **Step 4.1: Drop the 3 outflow `._asPeer()` bridges at L494/535/569** + +Use the same `._asPeer(), isGlobal:` pattern as Task 2.1. The 3 bridges at `ContactListPeer.peer(...)` constructions all match this substring; the 3 unrelated bridges at L488/528/562 (`canSendMessagesToPeer(...)` sites) do NOT match (they lack the `, isGlobal:` suffix). + +Pre-flight verify: + +```bash +grep -cE "\._asPeer\(\), isGlobal:" submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift +``` + +Expected: `3`. + +Apply Edit with `replace_all=true`: +- `old_string`: `._asPeer(), isGlobal:` +- `new_string`: `, isGlobal:` + +- [ ] **Step 4.2: Drop 4 inflow wraps at L164/165/181** + +Read lines 160–185 first. + +Three edits, each targeting one source line. + +Edit (line 164 — 2 wraps in one expression): + +```swift +// OLD + peerItem = .peer(peer: EnginePeer(peer), chatPeer: EnginePeer(peer)) +``` + +```swift +// NEW + peerItem = .peer(peer: peer, chatPeer: peer) +``` + +Edit (line 165): + +```swift +// OLD + nativePeer = EnginePeer(peer) +``` + +```swift +// NEW + nativePeer = peer +``` + +Edit (line 181): + +```swift +// OLD + openDisabledPeer(EnginePeer(peer), requiresPremiumForMessaging ? .premiumRequired : .generic) +``` + +```swift +// NEW + openDisabledPeer(peer, requiresPremiumForMessaging ? .premiumRequired : .generic) +``` + +- [ ] **Step 4.3: Verify** + +Run: + +```bash +grep -nE "\._asPeer\(\), isGlobal:|EnginePeer\(peer\)" submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift +``` + +Expected: zero matches. + +The `._asPeer()` calls at L488/528/562 (feeding `canSendMessagesToPeer`) should remain. Verify: + +```bash +grep -nE "canSendMessagesToPeer\(.*\._asPeer\(\)\)" submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift +``` + +Expected: 3 matches (L488, L528, L562). + +--- + +## Task 5: Edit `TelegramUI/Sources/ContactMultiselectionController.swift` — 2 outflow + 4 inflow + +**Files:** +- Modify: `submodules/TelegramUI/Sources/ContactMultiselectionController.swift` + +- [ ] **Step 5.1: Drop 2 outflow bridges at L451/459 via `replace_all`** + +Pre-flight verify: + +```bash +grep -cE "\._asPeer\(\), isGlobal:" submodules/TelegramUI/Sources/ContactMultiselectionController.swift +``` + +Expected: `2`. + +Apply Edit with `replace_all=true`: +- `old_string`: `._asPeer(), isGlobal:` +- `new_string`: `, isGlobal:` + +Unrelated `._asPeer()` calls at L171/201/748 (feeding `peerTokenTitle(peer: Peer, ...)`) do NOT use this substring and stay. + +- [ ] **Step 5.2: Drop 4 inflow wraps at L386/403/481/491** + +Read the file around each site to confirm exact text. Two wraps (L386, L403) have identical text; the other two (L481, L491) have distinct tails. + +Edit for L386 and L403 — `replace_all=true` on the substring: + +Pre-flight verify: + +```bash +grep -cE "subject: \.peer\(EnginePeer\(peer\)\)" submodules/TelegramUI/Sources/ContactMultiselectionController.swift +``` + +Expected: `2`. + +Apply Edit with `replace_all=true`: +- `old_string`: `subject: .peer(EnginePeer(peer))` +- `new_string`: `subject: .peer(peer)` + +Edit for L481: + +```swift +// OLD + self.params.sendMessage?(EnginePeer(peer)) +``` + +```swift +// NEW + self.params.sendMessage?(peer) +``` + +Edit for L491: + +```swift +// OLD + self.params.openProfile?(EnginePeer(peer)) +``` + +```swift +// NEW + self.params.openProfile?(peer) +``` + +- [ ] **Step 5.3: Verify** + +Run: + +```bash +grep -nE "\._asPeer\(\), isGlobal:|subject: \.peer\(EnginePeer\(peer\)\)|sendMessage\?\(EnginePeer\(peer\)\)|openProfile\?\(EnginePeer\(peer\)\)" submodules/TelegramUI/Sources/ContactMultiselectionController.swift +``` + +Expected: zero matches. + +Preserved bridge sites (sanity check): + +```bash +grep -nE "peerTokenTitle\(.*\._asPeer\(\)" submodules/TelegramUI/Sources/ContactMultiselectionController.swift +``` + +Expected: 3 matches (L171, L201, L748). + +--- + +## Task 6: Edit `TelegramUI/Sources/ContactMultiselectionControllerNode.swift` — 1 outflow + 1 inflow + +**Files:** +- Modify: `submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift` + +- [ ] **Step 6.1: Drop 1 outflow bridge at L317** + +Read lines 315–320 first. + +Edit: + +```swift +// OLD (line 317) + self?.openPeer?(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil)) +``` + +```swift +// NEW + self?.openPeer?(.peer(peer: peer, isGlobal: false, participantCount: nil)) +``` + +- [ ] **Step 6.2: Drop 1 inflow wrap at L492** + +Read lines 488–495 first. + +Edit: + +```swift +// OLD (line 492) + callTitle = self.presentationData.strings.NewCall_ActionCallSingle(EnginePeer(peer).compactDisplayTitle).string +``` + +```swift +// NEW + callTitle = self.presentationData.strings.NewCall_ActionCallSingle(peer.compactDisplayTitle).string +``` + +- [ ] **Step 6.3: Verify** + +Run: + +```bash +grep -nE "\._asPeer\(\), isGlobal:|EnginePeer\(peer\)\.compactDisplayTitle" submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift +``` + +Expected: zero matches. + +--- + +## Task 7: Edit `TelegramUI/Sources/ContactSelectionController.swift` — 2 inflow wraps + +**Files:** +- Modify: `submodules/TelegramUI/Sources/ContactSelectionController.swift` + +- [ ] **Step 7.1: Drop 2 inflow wraps at L517/527** + +Read lines 510–535 first. Both sites are inside the destructure at L504. + +Edit for L517: + +```swift +// OLD + self.sendMessage?(EnginePeer(peer)) +``` + +```swift +// NEW + self.sendMessage?(peer) +``` + +Edit for L527: + +```swift +// OLD + self.openProfile?(EnginePeer(peer)) +``` + +```swift +// NEW + self.openProfile?(peer) +``` + +- [ ] **Step 7.2: Verify** + +Run: + +```bash +grep -nE "sendMessage\?\(EnginePeer\(peer\)\)|openProfile\?\(EnginePeer\(peer\)\)" submodules/TelegramUI/Sources/ContactSelectionController.swift +``` + +Expected: zero matches. + +--- + +## Task 8: Edit `TelegramUI/Sources/ContactSelectionControllerNode.swift` — 2 outflow bridges + +**Files:** +- Modify: `submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift` + +- [ ] **Step 8.1: Drop 2 outflow bridges at L160/230 via `replace_all`** + +Pre-flight verify: + +```bash +grep -cE "\._asPeer\(\), isGlobal:" submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift +``` + +Expected: `2`. + +Apply Edit with `replace_all=true`: +- `old_string`: `._asPeer(), isGlobal:` +- `new_string`: `, isGlobal:` + +- [ ] **Step 8.2: Verify** + +Run: + +```bash +grep -nE "\._asPeer\(\), isGlobal:" submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift +``` + +Expected: zero matches. + +--- + +## Task 9: Verify no-edit consumer files + +**Files (read only):** +- Read: `submodules/TelegramUI/Sources/ComposeController.swift` +- Read: `submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift` + +- [ ] **Step 9.1: Confirm ComposeController.swift has no inflow wraps, casts, or outflow bridges** + +Run: + +```bash +grep -nE "\.peer\(peer:|EnginePeer\(peer\)|peer as\? Telegram|\._asPeer\(\)" submodules/TelegramUI/Sources/ComposeController.swift +``` + +Expected: zero matches (destructures at L120/160 only access `.id`). + +If any match appears, add the appropriate fix step here and re-run Task 9.1 before proceeding. + +- [ ] **Step 9.2: Confirm ChatSendAudioMessageContextPreview.swift has no ContactListPeer.peer destructures** + +Run: + +```bash +grep -nE "case let \.peer\(peer, _, _\)|case \.peer\(let peer|EnginePeer\(peer\)|\.peer\(peer: " submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift +``` + +Expected: zero matches. The file only references `[ContactListPeer]` at the collection level. + +--- + +## Task 10: Build verification (first pass) + +- [ ] **Step 10.1: Run the full build with `--continueOnError`** + +Run: + +```bash +source ~/.zshrc 2>/dev/null && python3 build-system/Make/Make.py --overrideXcodeVersion --cacheDir ~/telegram-bazel-cache build --configurationPath build-system/appstore-configuration.json --gitCodesigningRepository git@gitlab.com:peter-iakovlev/fastlanematch.git --gitCodesigningType development --gitCodesigningUseCurrent --buildNumber=1 --configuration=debug_sim_arm64 --continueOnError 2>&1 | tee /tmp/wave36-build.log +``` + +Expected outcome: ideally clean. Realistic: 0–3 inventory-missed sites (wave 35 trend was 14% miss rate on a 7-file wave; this 8-file wave has a larger surface area, so budget for up to 3 misses). + +- [ ] **Step 10.2: Triage build errors** + +Likely patterns and fixes: + +| Error | Fix | +|---|---| +| `cannot convert value of type 'EnginePeer' to expected argument type 'Peer'` at a call site | Add `._asPeer()` bridge. The callee takes raw `Peer` and is out of wave scope. | +| `cannot convert value of type 'Peer' to expected argument type 'EnginePeer'` at a `.peer(peer:, ...)` construction | Wrap raw peer with `EnginePeer(...)`. The raw-Peer source is probably from `transaction.getPeer(...)` or similar. | +| `value of type 'EnginePeer' has no member 'isEqual'` | Replace with `==`. | +| `type 'EnginePeer' cannot be cast to 'TelegramUser'` / `TelegramGroup` / `TelegramChannel` | Missed φ-category cast — rewrite to `case .user = peer` / `case let .legacyGroup(x) = peer` / `case let .channel(x) = peer`. | +| `cannot invoke initializer for type 'EnginePeer' with an argument list of type '(EnginePeer)'` | Missed inflow drop — strip `EnginePeer(...)` wrap. | +| `cannot convert value of type 'EnginePeer.IndexName' to expected argument type 'PeerIndexNameRepresentation'` | Either wrap the call site's expected-type change or adjust the consumer to accept `EnginePeer.IndexName`. Probably rare — ContactListPeer.indexName consumers were grepped in pre-flight and found only in ContactListNode. | +| `value of type 'EnginePeer' has no member ''` | That method is only on the Postbox `Peer` protocol. Bridge via `._asPeer()` OR find the EnginePeer-native equivalent. | + +For each error: identify file:line, apply the fix, re-run the build until clean. + +- [ ] **Step 10.3: Iterate to clean build** + +Re-run the build after each batch of fixes. The wave is complete when the build returns 0 errors for the targeted configuration. + +If 10+ unexpected errors surface, halt and reassess: the inventory may have significantly undercounted and the wave may need to be split. Discuss with the user before continuing. + +--- + +## Task 11: Post-build grep validations + +- [ ] **Step 11.1: Outflow-bridge-drop validation** + +Run: + +```bash +grep -rnE "\.peer\(peer: \w+\._asPeer\(\), isGlobal:" submodules/ --include="*.swift" +``` + +Expected: zero hits. Any remaining site is a missed outflow-bridge drop. + +- [ ] **Step 11.2: Inflow-wrap-drop validation** + +Run: + +```bash +for f in submodules/ContactListUI/Sources/ContactListNode.swift \ + submodules/ContactListUI/Sources/ContactsController.swift \ + submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift \ + submodules/TelegramUI/Sources/ContactMultiselectionController.swift \ + submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift \ + submodules/TelegramUI/Sources/ContactSelectionController.swift; do + echo "=== $f ===" + grep -nE "EnginePeer\(peer\)" "$f" +done +``` + +Expected hits: +- ContactListNode.swift L1819, L1825 (raw `renderedPeer.peer`, out-of-scope wraps stay) +- Any other hit in the 6 listed files is a missed inflow drop — inspect and fix. + +- [ ] **Step 11.3: Cast-rewrite validation** + +Run: + +```bash +grep -nE "\bpeer (as\?|as!|is) Telegram(User|Group|Channel)\b" submodules/ContactListUI/Sources/ContactListNode.swift +``` + +Expected: only L1802, L1818, L1820 remain (out-of-scope, `peer` is raw from `renderedPeer.peer`). + +If L182, L184, L186, or L1968 appear, those are missed φ rewrites. + +- [ ] **Step 11.4: IndexName wrap validation** + +Run: + +```bash +grep -nE "EnginePeer\.IndexName\(lhs\.indexName\)|EnginePeer\.IndexName\(rhs\.indexName\)" submodules/ContactListUI/Sources/ContactListNode.swift +``` + +Expected: zero matches. + +- [ ] **Step 11.5: isEqual-in-==-operator validation** + +Run: + +```bash +grep -nE "lhsPeer\.isEqual\(rhsPeer\)" submodules/AccountContext/Sources/ContactSelectionController.swift +``` + +Expected: zero matches. + +- [ ] **Step 11.6: Construction-site sanity sweep** + +Run: + +```bash +grep -rnE "ContactListPeer\.peer\(peer: |\.peer\(peer: \w+, isGlobal:" submodules/ --include="*.swift" | head -40 +``` + +Inspect each hit. Expected forms: +- `.peer(peer: , isGlobal: …)` where `` is either a local already typed `EnginePeer` or `EnginePeer()`. +- Anything of the form `.peer(peer: , isGlobal: …)` where `` is a Postbox `Peer` value is a miss (would surface as a build error — this is a belt-and-suspenders check). + +If any validation fails, return to Task 10. + +--- + +## Task 12: Atomic commit + memory + log update + +- [ ] **Step 12.1: Stage and review** + +Run: + +```bash +git status --short +git diff --stat +``` + +Confirm exactly 8 modified Swift files: +- `submodules/AccountContext/Sources/ContactSelectionController.swift` +- `submodules/ContactListUI/Sources/ContactListNode.swift` +- `submodules/ContactListUI/Sources/ContactsController.swift` +- `submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift` +- `submodules/TelegramUI/Sources/ContactMultiselectionController.swift` +- `submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift` +- `submodules/TelegramUI/Sources/ContactSelectionController.swift` +- `submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift` + +Pre-existing WIP (`build-system/bazel-rules/sourcekit-bazel-bsp`, `ChatListFilterPresetController.swift`, `ChatListFilterPresetListController.swift`, untracked `build-system/tulsi/` / `submodules/TgVoip/` / `third-party/libx264/` / `docs/superpowers/plans/2026-04-22-claude-md-reorganization.md`) should NOT be staged. + +- [ ] **Step 12.2: Stage only the wave-36 files** + +Run: + +```bash +git add submodules/AccountContext/Sources/ContactSelectionController.swift \ + submodules/ContactListUI/Sources/ContactListNode.swift \ + submodules/ContactListUI/Sources/ContactsController.swift \ + submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift \ + submodules/TelegramUI/Sources/ContactMultiselectionController.swift \ + submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift \ + submodules/TelegramUI/Sources/ContactSelectionController.swift \ + submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift +``` + +If Task 10 introduced additional files (inventory-miss fixes), append them. + +- [ ] **Step 12.3: Commit** + +Run: + +```bash +git commit -m "$(cat <<'EOF' +Postbox -> TelegramEngine wave 36: ContactListPeer.peer Peer -> EnginePeer + +Migrates the public enum case `ContactListPeer.peer(peer: Peer, isGlobal:, +participantCount:)` from the Postbox `Peer` protocol to the TelegramCore +`EnginePeer` enum. Also cascades `ContactListPeer.indexName` return type +from `PeerIndexNameRepresentation` to `EnginePeer.IndexName` and rewrites +the enum's custom `==` operator to use EnginePeer's synthesized Equatable. + +Consumer-side cascade in 7 files: + - 20 `._asPeer()` outflow bridge-drops at ContactListPeer.peer + construction sites (the payload is now EnginePeer) + - 16 `EnginePeer(peer)` inflow wrap-drops at destructure sites (the + destructured `peer` is already EnginePeer) + - 2 `EnginePeer.IndexName(...)` wrap-drops at a sort-comparator (the + indexName property now returns EnginePeer.IndexName directly) + - 2 Postbox-concrete cast chains rewritten to EnginePeer case patterns + (`peer as? TelegramUser` → `case .user = peer`, etc.) + - `lhsPeer.isEqual(rhsPeer)` → `lhsPeer == rhsPeer` in the ==operator + +Files modified: + submodules/AccountContext/Sources/ContactSelectionController.swift + submodules/ContactListUI/Sources/ContactListNode.swift + submodules/ContactListUI/Sources/ContactsController.swift + submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift + submodules/TelegramUI/Sources/ContactMultiselectionController.swift + submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift + submodules/TelegramUI/Sources/ContactSelectionController.swift + submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift + +Bridges intentionally retained (out-of-wave scope): + - `canSendMessagesToPeer(peer._asPeer())` — callee takes Peer, deferred + - `peerTokenTitle(peer: peer._asPeer(), ...)` — callee takes Peer, + deferred + +Plan: docs/superpowers/plans/2026-04-24-contactlistpeer-engine-peer-migration.md +Spec: docs/superpowers/specs/2026-04-24-contactlistpeer-engine-peer-migration-design.md + +Co-Authored-By: Claude Opus 4.7 (1M context) +EOF +)" +``` + +- [ ] **Step 12.4: Update CLAUDE.md wave counter** + +Edit `CLAUDE.md` to bump the "Waves landed so far" line from "35 waves" to "36 waves" and update the "as of" date if the commit lands after 2026-04-24. + +- [ ] **Step 12.5: Append wave outcome to the postbox-refactor-log** + +Append a "Wave 36 outcome" section to `docs/superpowers/postbox-refactor-log.md` documenting: +- Actual files touched + edit counts vs. plan +- Any inventory undercounts surfaced by Task 10 (file:line + missed-category) +- Any lessons learned (e.g., whether the γ category really had zero sites; how the φ cast-rewrites behaved; post-migration undercount percentage vs wave 35's 14%) +- Ratio of bridge-drops to bridge-additions (wave theme: removal-dominated) + +Keep concise. + +- [ ] **Step 12.6: Commit the docs update** + +Run: + +```bash +git add CLAUDE.md docs/superpowers/postbox-refactor-log.md +git commit -m "$(cat <<'EOF' +docs: add wave 36 outcome (ContactListPeer.peer Peer→EnginePeer) + +Co-Authored-By: Claude Opus 4.7 (1M context) +EOF +)" +``` + +- [ ] **Step 12.7: Update the next-wave memory** + +Edit `/Users/isaac/.claude/projects/-Users-isaac-build-telegram-telegram-ios/memory/project_postbox_refactor_next_wave.md`: +- Add wave 36 to the "Latest commits" section. +- Move ContactListPeer migration from "Recommended wave 36 candidates" to landed. +- Record the inventory undercount ratio (actual-files-touched ÷ pre-flight-file-count) for calibration. +- Update the "Recommended wave 37" section. Promote candidates: `canSendMessagesToPeer(_:)` parameter (the ContactsSearchContainerNode `._asPeer()` bridges at L488/528/562 plus others elsewhere drop when this lands); `peerTokenTitle(peer:)` parameter (drops 3 bridges in ContactMultiselectionController); `makePeerInfoController` / `makeChatQrCodeScreen` / `makeChatRecentActionsController` AccountContext protocol methods (largest remaining Peer-typed-API); accountManager engine path; Shape-C `resourceData` module. + +Use the Edit tool on the memory file. No git commit needed. + +--- + +## Risks and notes + +- **Inventory undercount.** Pre-flight caught several sites the Explore agent missed (inflow wraps at L481/491/517/527/492/844, cast rewrites at L182-186 and L1968). Budget for 1–3 additional misses surfacing in Task 10. If the build surfaces 5+ misses in new categories, stop and reassess. +- **`replace_all` usage.** Every `replace_all=true` Edit in this plan is gated by a pre-flight `grep -c` count check. If the count is wrong, fall back to per-site Edits with surrounding context. +- **Cast rewrite at L182-186.** The original cast chain binds `group` and `channel` (but not `user`). The EnginePeer case-pattern form preserves this: `case .user = peer` is a binding-free match, mirroring `if let _ = peer as? TelegramUser`. +- **`._asPeer()` sites that stay.** Tasks 4.3 and 5.3 explicitly verify that the 3 `canSendMessagesToPeer(peer._asPeer())` bridges and 3 `peerTokenTitle(peer: peer._asPeer(), ...)` bridges remain intact. Dropping these would be out-of-scope migration. +- **WIP isolation.** Pre-existing `ChatListFilterPresetController.swift` / `ChatListFilterPresetListController.swift` edits and untracked `build-system/tulsi/`, `submodules/TgVoip/`, `third-party/libx264/` paths are user WIP — do NOT stage them. Use the explicit `git add ` form in Step 12.2. +- **Scope boundary.** Task 10 errors surfacing in `TelegramCore`, `Postbox`, or `TelegramApi` mean the migration cascaded beyond its intended consumer scope. Halt and investigate — do NOT edit TelegramCore in this wave. +- **No new typealiases/wrappers.** Rule 2 and 3 of the Postbox refactor guidance apply — this wave stays inside. diff --git a/docs/superpowers/plans/2026-04-24-foundpeer-engine-peer-migration.md b/docs/superpowers/plans/2026-04-24-foundpeer-engine-peer-migration.md new file mode 100644 index 0000000000..f596e3270e --- /dev/null +++ b/docs/superpowers/plans/2026-04-24-foundpeer-engine-peer-migration.md @@ -0,0 +1,1287 @@ +# Wave 34: `FoundPeer.peer: Peer → EnginePeer` Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Migrate the public field `FoundPeer.peer` from the Postbox `Peer` protocol to the TelegramCore `EnginePeer` enum in a single atomic commit. Drops 5 `._asPeer()` bridges (most added in wave 33), eliminates 22 `EnginePeer(peer.peer)` redundant wraps, rewrites 30 Postbox-concrete-type downcasts to enum patterns, and adds bridges where `peer.peer` flows out to APIs that still take raw `Peer`. + +**Architecture:** One atomic commit. The field-type change is necessarily atomic (half-migrated FoundPeer doesn't compile), so all edits land together. TelegramCore's `_internal_searchPeers` keeps `import Postbox` — only `FoundPeer`'s public surface changes. No new wrappers, no new typealiases beyond what already exists. + +**Tech Stack:** Swift, Bazel build via Make.py wrapper. No tests — verification is build success + targeted grep checks. + +**Spec:** `docs/superpowers/specs/2026-04-24-foundpeer-engine-peer-migration-design.md` + +--- + +## File Structure + +**Modified files (8 total — 1 TelegramCore + 7 consumer):** + +| File | Edit count | +|---|---| +| `submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift` | ~13 spot edits (struct change + 6 filter rewrites + 4 constructor wraps + manual `==` body) | +| `submodules/TelegramCallsUI/Sources/VideoChatScreen.swift` | 1 (bridge-drop at line 1833) | +| `submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift` | 7 (3 C2 downcasts + 4 C3 wraps) | +| `submodules/ContactListUI/Sources/ContactListNode.swift` | ~21 (3 C4 + 13 C2 + 0 C3 + 3 outflow bridges; some lines have multiple edits) | +| `submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift` | ~17 (8 C2 + ~9 C3) | +| `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenCallActions.swift` | ~10 (2 C4 + 3 C2 + 4 C3 + 1 outflow bridge at line 161 — verify) | +| `submodules/TelegramBaseController/Sources/TelegramBaseController.swift` | 6 (1 C4 + 3 C2 + 2 C3) | +| `submodules/SettingsUI/Sources/Data and Storage/StorageUsageExceptionsScreen.swift` | 3 (1 C4 + 2 C3) | + +Note: line counts above are approximations from spec — execution may surface additional outflow-bridge sites caught by the build pass (Task 10). + +**EnginePeer enum case mapping (used throughout):** + +| Postbox concrete | EnginePeer case | +|---|---| +| `TelegramUser` | `.user(TelegramUser)` | +| `TelegramSecretChat` | `.secretChat(TelegramSecretChat)` | +| `TelegramGroup` | `.legacyGroup(TelegramGroup)` | +| `TelegramChannel` | `.channel(TelegramChannel)` | + +--- + +## Task 1: Edit `SearchPeers.swift` — struct definition + body rewrites + +**Files:** +- Modify: `submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift` + +This is the foundational change. Without it, none of the consumer edits compile in the right direction. + +- [ ] **Step 1.1: Update the FoundPeer struct field, init parameter, and `==` body** + +Edit: + +```swift +// OLD +public struct FoundPeer: Equatable { + public let peer: Peer + public let subscribers: Int32? + + public init(peer: Peer, subscribers: Int32?) { + self.peer = peer + self.subscribers = subscribers + } + + public static func ==(lhs: FoundPeer, rhs: FoundPeer) -> Bool { + return lhs.peer.isEqual(rhs.peer) && lhs.subscribers == rhs.subscribers + } +} +``` + +```swift +// NEW +public struct FoundPeer: Equatable { + public let peer: EnginePeer + public let subscribers: Int32? + + public init(peer: EnginePeer, subscribers: Int32?) { + self.peer = peer + self.subscribers = subscribers + } + + public static func ==(lhs: FoundPeer, rhs: FoundPeer) -> Bool { + return lhs.peer == rhs.peer && lhs.subscribers == rhs.subscribers + } +} +``` + +Use the Edit tool with the OLD block as `old_string` and the NEW block as `new_string`. + +- [ ] **Step 1.2: Wrap raw peer values in the four constructor sites inside `_internal_searchPeers`** + +There are four `FoundPeer(peer: peer, subscribers: …)` calls inside `_internal_searchPeers` at lines 70, 72, 85, 87. Each wraps `peer` (a raw `Peer` from `parsedPeers.get(peerId)`) with `EnginePeer(peer)`. + +Edit (replace_all=false because there are 4 distinct contexts; use enough surrounding context per edit to make each unique): + +For lines 70 and 72 (inside the `myResults` loop): + +```swift +// OLD + if let user = peer as? TelegramUser { + renderedMyPeers.append(FoundPeer(peer: peer, subscribers: user.subscriberCount)) + } else { + renderedMyPeers.append(FoundPeer(peer: peer, subscribers: subscribers[peerId])) + } +``` + +```swift +// NEW + if let user = peer as? TelegramUser { + renderedMyPeers.append(FoundPeer(peer: EnginePeer(peer), subscribers: user.subscriberCount)) + } else { + renderedMyPeers.append(FoundPeer(peer: EnginePeer(peer), subscribers: subscribers[peerId])) + } +``` + +For lines 85 and 87 (inside the `results` loop): + +```swift +// OLD + if let user = peer as? TelegramUser { + renderedPeers.append(FoundPeer(peer: peer, subscribers: user.subscriberCount)) + } else { + renderedPeers.append(FoundPeer(peer: peer, subscribers: subscribers[peerId])) + } +``` + +```swift +// NEW + if let user = peer as? TelegramUser { + renderedPeers.append(FoundPeer(peer: EnginePeer(peer), subscribers: user.subscriberCount)) + } else { + renderedPeers.append(FoundPeer(peer: EnginePeer(peer), subscribers: subscribers[peerId])) + } +``` + +- [ ] **Step 1.3: Rewrite the six scope-filter expressions to enum-pattern form** + +For `.channels` scope (two filter blocks; identical bodies — use one Edit per block, NOT replace_all, since the surrounding `renderedMyPeers =` vs `renderedPeers =` differs): + +```swift +// OLD (renderedMyPeers, lines ~96-102) + case .channels: + renderedMyPeers = renderedMyPeers.filter { item in + if let channel = item.peer as? TelegramChannel, case .broadcast = channel.info { + return true + } else { + return false + } + } + renderedPeers = renderedPeers.filter { item in + if let channel = item.peer as? TelegramChannel, case .broadcast = channel.info { + return true + } else { + return false + } + } +``` + +```swift +// NEW + case .channels: + renderedMyPeers = renderedMyPeers.filter { item in + if case let .channel(channel) = item.peer, case .broadcast = channel.info { + return true + } else { + return false + } + } + renderedPeers = renderedPeers.filter { item in + if case let .channel(channel) = item.peer, case .broadcast = channel.info { + return true + } else { + return false + } + } +``` + +For `.groups` scope (two filter blocks): + +```swift +// OLD + case .groups: + renderedMyPeers = renderedMyPeers.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 + } + } + renderedPeers = renderedPeers.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 + } + } +``` + +```swift +// NEW + case .groups: + renderedMyPeers = renderedMyPeers.filter { item in + if case let .channel(channel) = item.peer, case .group = channel.info { + return true + } else if case .legacyGroup = item.peer { + return true + } else { + return false + } + } + renderedPeers = renderedPeers.filter { item in + if case let .channel(channel) = item.peer, case .group = channel.info { + return true + } else if case .legacyGroup = item.peer { + return true + } else { + return false + } + } +``` + +For `.privateChats` scope: + +```swift +// OLD + case .privateChats: + renderedMyPeers = renderedMyPeers.filter { item in + if item.peer is TelegramUser { + return true + } else { + return false + } + } + renderedPeers = renderedPeers.filter { item in + if item.peer is TelegramUser { + return true + } else { + return false + } + } +``` + +```swift +// NEW + case .privateChats: + renderedMyPeers = renderedMyPeers.filter { item in + if case .user = item.peer { + return true + } else { + return false + } + } + renderedPeers = renderedPeers.filter { item in + if case .user = item.peer { + return true + } else { + return false + } + } +``` + +- [ ] **Step 1.4: Verify** — read the updated file from line 1 to ~155 and confirm there are no remaining `item.peer as? Telegram*` or `item.peer is Telegram*` patterns and the four `FoundPeer(peer: peer, ...)` constructions are now `FoundPeer(peer: EnginePeer(peer), ...)`. Do not commit yet. + +--- + +## Task 2: Edit `VideoChatScreen.swift` + +**Files:** +- Modify: `submodules/TelegramCallsUI/Sources/VideoChatScreen.swift` + +- [ ] **Step 2.1: Drop `._asPeer()` bridge in the FoundPeer constructor at line 1833** + +Edit: + +```swift +// OLD + |> map { peer in + return [FoundPeer(peer: peer._asPeer(), subscribers: nil)] + } +``` + +```swift +// NEW + |> map { peer in + return [FoundPeer(peer: peer, subscribers: nil)] + } +``` + +The surrounding context (`|> mapToSignal { peer -> Signal` two lines earlier) confirms `peer` is `EnginePeer`. The `._asPeer()` bridge becomes unnecessary. + +--- + +## Task 3: Edit `VideoChatScreenMoreMenu.swift` + +**Files:** +- Modify: `submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift` + +7 edits in this file: 4 C3 wrap-drops + 3 C2 downcast rewrites. + +- [ ] **Step 3.1: Drop the two `EnginePeer(peer.peer)` wraps on line 171** + +Edit: + +```swift +// OLD + items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_DisplayAs, textLayout: .secondLineWithValue(EnginePeer(peer.peer).displayTitle(strings: environment.strings, displayOrder: currentCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: currentCall.accountContext.account, peer: EnginePeer(peer.peer), size: avatarSize)), action: { [weak self] c, _ in +``` + +```swift +// NEW + items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_DisplayAs, textLayout: .secondLineWithValue(peer.peer.displayTitle(strings: environment.strings, displayOrder: currentCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: currentCall.accountContext.account, peer: peer.peer, size: avatarSize)), action: { [weak self] c, _ in +``` + +- [ ] **Step 3.2: Rewrite the C2 downcasts at lines 628–648** + +Edit: + +```swift +// OLD (around lines 627-635) + for peer in displayAsPeers { + if peer.peer is TelegramGroup { + isGroup = true + break + } else if let peer = peer.peer as? TelegramChannel, case .group = peer.info { + isGroup = true + break + } + } +``` + +```swift +// NEW + for peer in displayAsPeers { + if case .legacyGroup = peer.peer { + isGroup = true + break + } else if case let .channel(channel) = peer.peer, case .group = channel.info { + isGroup = true + break + } + } +``` + +(Note the `else if let peer = peer.peer as? TelegramChannel` shadowed the outer loop `peer` with a new `peer: TelegramChannel`. The rewrite uses `channel` to avoid further shadowing the EnginePeer loop variable.) + +Edit (around line 648): + +```swift +// OLD + } else if let subscribers = peer.subscribers { + if let peer = peer.peer as? TelegramChannel, case .broadcast = peer.info { + subtitle = environment.strings.Conversation_StatusSubscribers(subscribers) + } else { + subtitle = environment.strings.Conversation_StatusMembers(subscribers) + } + } +``` + +```swift +// NEW + } else if let subscribers = peer.subscribers { + if case let .channel(channel) = peer.peer, case .broadcast = channel.info { + subtitle = environment.strings.Conversation_StatusSubscribers(subscribers) + } else { + subtitle = environment.strings.Conversation_StatusMembers(subscribers) + } + } +``` + +- [ ] **Step 3.3: Drop the `EnginePeer(peer.peer)` wrap at line 658** + +Edit: + +```swift +// OLD + let avatarSignal = peerAvatarCompleteImage(account: groupCall.accountContext.account, peer: EnginePeer(peer.peer), size: avatarSize) +``` + +```swift +// NEW + let avatarSignal = peerAvatarCompleteImage(account: groupCall.accountContext.account, peer: peer.peer, size: avatarSize) +``` + +- [ ] **Step 3.4: Drop the `EnginePeer(peer.peer)` wrap at line 679** + +Edit: + +```swift +// OLD + items.append(.action(ContextMenuActionItem(text: EnginePeer(peer.peer).displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: isSelected ? extendedAvatarSize : avatarSize, signal: avatarSignal), action: { [weak self] _, f in +``` + +```swift +// NEW + items.append(.action(ContextMenuActionItem(text: peer.peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: isSelected ? extendedAvatarSize : avatarSize, signal: avatarSignal), action: { [weak self] _, f in +``` + +--- + +## Task 4: Edit `ContactListNode.swift` + +**Files:** +- Modify: `submodules/ContactListUI/Sources/ContactListNode.swift` + +The largest file: 3 C4 (constructor) edits, 13 C2 (downcast) rewrites, and 3 outflow bridges where `peer.peer` is passed to `.peer(peer:)` (raw-Peer-taking enum case). + +- [ ] **Step 4.1: Bridge-drop at constructor lines 1485 and 1517** + +Edit: + +```swift +// OLD (line 1485) + resultPeers.append(FoundPeer(peer: mainPeer._asPeer(), subscribers: nil)) +``` + +```swift +// NEW + resultPeers.append(FoundPeer(peer: mainPeer, subscribers: nil)) +``` + +(Verification: `mainPeer` is bound from `peer.chatMainPeer` at line 1479. `chatMainPeer` returns `EnginePeer?` on `EngineRenderedPeer`. After migration this needs no bridge.) + +Edit: + +```swift +// OLD (line 1517) + return (peers.map({ FoundPeer(peer: $0._asPeer(), subscribers: nil) }), presences) +``` + +```swift +// NEW + return (peers.map({ FoundPeer(peer: $0, subscribers: nil) }), presences) +``` + +(Verification: `peers` comes from `context.engine.contacts.searchContacts(query:)` whose return type's first element is `[EnginePeer]`. Confirmed by inspection.) + +- [ ] **Step 4.2: Rewrite the C2 downcast at line 1501** + +Edit: + +```swift +// OLD + if let _ = peer.peer as? TelegramChannel { +``` + +```swift +// NEW + if case .channel = peer.peer { +``` + +- [ ] **Step 4.3: Rewrite the three identical C2 sites at lines 1563, 1569, 1574** + +These three lines have the SAME pattern (`if let user = peer.peer as? TelegramUser, user.flags.contains(.requirePremium) {`). Use `replace_all=true` since the line is identical (verify uniqueness by grepping first). + +Edit (`replace_all=true`): + +```swift +// OLD + if let user = peer.peer as? TelegramUser, user.flags.contains(.requirePremium) { +``` + +```swift +// NEW + if case let .user(user) = peer.peer, user.flags.contains(.requirePremium) { +``` + +Verify `replace_all` actually replaced exactly 3 sites (count occurrences before and after; if not 3, something else uses the same pattern and you must split into individual edits). + +- [ ] **Step 4.4: Add `._asPeer()` outflow bridges at lines 1656, 1693, 1731** + +These are `peers.append(.peer(peer: peer.peer, ...))` where `.peer(peer:)` is `ContactListPeer.peer(peer:isGlobal:participantCount:)` taking raw `Peer`. Add bridge. + +Edit (line 1656): + +```swift +// OLD + peers.append(.peer(peer: peer.peer, isGlobal: false, participantCount: peer.subscribers)) +``` + +```swift +// NEW + peers.append(.peer(peer: peer.peer._asPeer(), isGlobal: false, participantCount: peer.subscribers)) +``` + +Edit (line 1693): + +```swift +// OLD + peers.append(.peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers)) +``` + +```swift +// NEW + peers.append(.peer(peer: peer.peer._asPeer(), isGlobal: true, participantCount: peer.subscribers)) +``` + +The same pattern appears at line 1731 — apply the same edit. Use `replace_all` if and only if the second occurrence is identical (it is, but verify). + +- [ ] **Step 4.5: Rewrite the C2 sites at lines 1658, 1665, 1673, 1675, 1695, 1703, 1711, 1713, 1733** + +These are scattered through three nearly-identical loop bodies (`localPeersAndStatuses.0`, `remotePeers.0`, `remotePeers.1`). The patterns: + +For lines 1658, 1695, 1733 (`if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone`): + +Edit (`replace_all=true` if the 3 occurrences are textually identical — verify): + +```swift +// OLD + if searchDeviceContacts, + let user = peer.peer as? TelegramUser, + let phone = user.phone { +``` + +```swift +// NEW + if searchDeviceContacts, + case let .user(user) = peer.peer, + let phone = user.phone { +``` + +For line 1665 and 1703 (single-condition `if let user = peer.peer as? TelegramUser {`): + +Edit (`replace_all=true` if textually identical): + +```swift +// OLD + if let user = peer.peer as? TelegramUser { +``` + +```swift +// NEW + if case let .user(user) = peer.peer { +``` + +For line 1673 (`if peer.peer is TelegramGroup && searchGroups`): + +Edit: + +```swift +// OLD + if peer.peer is TelegramGroup && searchGroups { + matches = true + } else if let channel = peer.peer as? TelegramChannel { +``` + +```swift +// NEW + if case .legacyGroup = peer.peer, searchGroups { + matches = true + } else if case let .channel(channel) = peer.peer { +``` + +For line 1711 (`if peer.peer is TelegramGroup`): + +Edit: + +```swift +// OLD + if peer.peer is TelegramGroup { + matches = searchGroups + } else if let channel = peer.peer as? TelegramChannel { +``` + +```swift +// NEW + if case .legacyGroup = peer.peer { + matches = searchGroups + } else if case let .channel(channel) = peer.peer { +``` + +(Note that line 1675 and 1713 carry the same `else if let channel = peer.peer as? TelegramChannel` pattern that is folded into the edits above. Confirm both got rewritten.) + +- [ ] **Step 4.6: Verify** — grep for remaining FoundPeer-relevant Postbox patterns in the file: + +Run: `grep -nE "peer\.peer\s+(as\?|is)\s+Telegram|EnginePeer\(peer\.peer\)|FoundPeer\(peer:\s+\w+\._asPeer\(\)" submodules/ContactListUI/Sources/ContactListNode.swift` + +Expected: zero matches if the C2/C3/C4 edits are complete. + +--- + +## Task 5: Edit `ChatListSearchListPaneNode.swift` + +**Files:** +- Modify: `submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift` + +8 C2 downcasts + 9 C3 wrap drops. + +- [ ] **Step 5.1a: Add outflow bridge at line 1018** + +Edit: + +```swift +// OLD + enabled = canSendMessagesToPeer(peer.peer) +``` + +```swift +// NEW + enabled = canSendMessagesToPeer(peer.peer._asPeer()) +``` + +(`canSendMessagesToPeer(_ peer: Peer, ...)` in `submodules/TelegramCore/Sources/Utils/CanSendMessagesToPeer.swift` takes raw `Peer`, so the bridge is required.) + +- [ ] **Step 5.1b: Rewrite the disjunction at line 1024 using `switch`** + +Edit: + +```swift +// OLD + if filter.contains(.onlyPrivateChats) { + if !(peer.peer is TelegramUser || peer.peer is TelegramSecretChat) { + enabled = false + } + } +``` + +```swift +// NEW + if filter.contains(.onlyPrivateChats) { + switch peer.peer { + case .user, .secretChat: + break + default: + enabled = false + } + } +``` + +The `switch ... case .user, .secretChat: break / default: enabled = false` form preserves the negation semantics of the original `if !(... || ...)` and reads cleanly. + +- [ ] **Step 5.1c: Rewrite C2 downcasts at lines 1029-1030** + +Edit (lines 1029-1030): + +```swift +// OLD + if let _ = peer.peer as? TelegramGroup { + } else if let peer = peer.peer as? TelegramChannel, case .group = peer.info { +``` + +```swift +// NEW + if case .legacyGroup = peer.peer { + } else if case let .channel(channel) = peer.peer, case .group = channel.info { +``` + +Edit (lines 1038-1040): + +```swift +// OLD + if peer.peer is TelegramUser { +``` + +(continues `} else if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info {` at line 1040) + +```swift +// NEW + if case .user = peer.peer { +``` + +```swift +// OLD + } else if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info { +``` + +```swift +// NEW + } else if case let .channel(channel) = peer.peer, case .broadcast = channel.info { +``` + +(If line 1040's old form is used elsewhere in the file, scope the Edit by including more surrounding context.) + +- [ ] **Step 5.2: Drop C3 wraps on line 1075** + +Edit: + +```swift +// OLD + return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch(isSavedMessages: isSavedMessages), peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .addressName(suffixString), badge: badge, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, searchQuery: query, isAd: false, action: { _ in +``` + +```swift +// NEW + return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch(isSavedMessages: isSavedMessages), peer: .peer(peer: peer.peer, chatPeer: peer.peer), status: .addressName(suffixString), badge: badge, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, searchQuery: query, isAd: false, action: { _ in +``` + +- [ ] **Step 5.3: Drop C3 wraps on lines 1076, 1078, 1081** + +Edit (line 1076): + +```swift +// OLD + interaction.peerSelected(EnginePeer(peer.peer), nil, nil, nil, false) +``` + +```swift +// NEW + interaction.peerSelected(peer.peer, nil, nil, nil, false) +``` + +Edit (line 1078): + +```swift +// OLD + interaction.disabledPeerSelected(EnginePeer(peer.peer), nil, requiresPremiumForMessaging ? .premiumRequired : .generic) +``` + +```swift +// NEW + interaction.disabledPeerSelected(peer.peer, nil, requiresPremiumForMessaging ? .premiumRequired : .generic) +``` + +Edit (line 1081): + +```swift +// OLD + peerContextAction(EnginePeer(peer.peer), .search(nil), node, gesture, location) +``` + +```swift +// NEW + peerContextAction(peer.peer, .search(nil), node, gesture, location) +``` + +- [ ] **Step 5.4: Rewrite the C2 downcasts at lines 1500 and 1507 (inside `filteredPeerSearchQueryResults`)** + +Edit (line 1500, inside `value.0.filter`): + +```swift +// OLD + value.0.filter { peer in + if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info { + return true + } else { + return false + } + }, +``` + +```swift +// NEW + value.0.filter { peer in + if case let .channel(channel) = peer.peer, case .broadcast = channel.info { + return true + } else { + return false + } + }, +``` + +Edit (line 1507, inside `value.1.filter`): + +```swift +// OLD + value.1.filter { peer in + if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info { + return true + } else { + return false + } + } +``` + +```swift +// NEW + value.1.filter { peer in + if case let .channel(channel) = peer.peer, case .broadcast = channel.info { + return true + } else { + return false + } + } +``` + +- [ ] **Step 5.5: Drop C3 wraps in `foundRemotePeers` loops (lines 3088, 3096, 3214, 3216, 3241)** + +Edit (`replace_all=true` for the lines that match exactly the same pattern, otherwise individual Edits): + +```swift +// OLD (occurs at 3088, 3096, 3214, 3241 — the FoundPeer wrap; the EnginePeer(accountPeer) wrap stays since `accountPeer` is raw Peer) + if !existingPeerIds.contains(peer.peer.id), filteredPeer(EnginePeer(peer.peer), EnginePeer(accountPeer)) { +``` + +```swift +// NEW + if !existingPeerIds.contains(peer.peer.id), filteredPeer(peer.peer, EnginePeer(accountPeer)) { +``` + +If `replace_all=true`, verify the count is exactly 4 by grep before and after. + +Edit (line 3216 separately — different pattern): + +```swift +// OLD + entries.append(.localPeer(EnginePeer(peer.peer), nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType, nil, false, false)) +``` + +```swift +// NEW + entries.append(.localPeer(peer.peer, nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType, nil, false, false)) +``` + +(Note: this assumes `.localPeer(EnginePeer, ...)` accepts EnginePeer directly. If the build fails saying it expected raw `Peer`, add `._asPeer()` instead. Verify in build pass.) + +- [ ] **Step 5.6: Verify** — grep: + +Run: `grep -nE "peer\.peer\s+(as\?|is)\s+Telegram|EnginePeer\(peer\.peer\)" submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift` + +Expected: zero matches. + +--- + +## Task 6: Edit `PeerInfoScreenCallActions.swift` + +**Files:** +- Modify: `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenCallActions.swift` + +2 C4 bridge-drops + 3 C2 downcasts + 4 C3 wraps. The function is duplicated almost verbatim at the second site (line 156 vs 265). + +- [ ] **Step 6.1: Bridge-drops at lines 156 and 265** + +Edit (`replace_all=true` if the line is literally identical at both sites): + +```swift +// OLD + return [FoundPeer(peer: peer._asPeer(), subscribers: nil)] +``` + +```swift +// NEW + return [FoundPeer(peer: peer, subscribers: nil)] +``` + +Verify `replace_all` got exactly 2 sites. + +- [ ] **Step 6.2: Rewrite C2 downcasts at lines 175, 178, 193** + +Edit (lines 175 and 178 form one if-else chain): + +```swift +// OLD + for peer in peers { + if peer.peer is TelegramGroup { + isGroup = true + break + } else if let peer = peer.peer as? TelegramChannel, case .group = peer.info { + isGroup = true + break + } + } +``` + +```swift +// NEW + for peer in peers { + if case .legacyGroup = peer.peer { + isGroup = true + break + } else if case let .channel(channel) = peer.peer, case .group = channel.info { + isGroup = true + break + } + } +``` + +Edit (line 193 — inside the second `for peer in peers` loop): + +```swift +// OLD + } else if let subscribers = peer.subscribers { + if let peer = peer.peer as? TelegramChannel, case .broadcast = peer.info { + subtitle = strongSelf.presentationData.strings.Conversation_StatusSubscribers(subscribers) + } else { + subtitle = strongSelf.presentationData.strings.Conversation_StatusMembers(subscribers) + } + } +``` + +```swift +// NEW + } else if let subscribers = peer.subscribers { + if case let .channel(channel) = peer.peer, case .broadcast = channel.info { + subtitle = strongSelf.presentationData.strings.Conversation_StatusSubscribers(subscribers) + } else { + subtitle = strongSelf.presentationData.strings.Conversation_StatusMembers(subscribers) + } + } +``` + +- [ ] **Step 6.3: Drop C3 wraps at lines 201, 202** + +Edit: + +```swift +// OLD + let avatarSignal = peerAvatarCompleteImage(account: strongSelf.context.account, peer: EnginePeer(peer.peer), size: avatarSize) + items.append(.action(ContextMenuActionItem(text: EnginePeer(peer.peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: avatarSignal), action: { _, f in +``` + +```swift +// NEW + let avatarSignal = peerAvatarCompleteImage(account: strongSelf.context.account, peer: peer.peer, size: avatarSize) + items.append(.action(ContextMenuActionItem(text: peer.peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: avatarSignal), action: { _, f in +``` + +- [ ] **Step 6.4: Drop two C3 wraps on line 288** + +Edit: + +```swift +// OLD + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_DisplayAs, textLayout: .secondLineWithValue(EnginePeer(peer.peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: strongSelf.context.account, peer: EnginePeer(peer.peer), size: avatarSize)), action: { c, f in +``` + +```swift +// NEW + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_DisplayAs, textLayout: .secondLineWithValue(peer.peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: strongSelf.context.account, peer: peer.peer, size: avatarSize)), action: { c, f in +``` + +- [ ] **Step 6.5: Verify** — grep: + +Run: `grep -nE "peer\.peer\s+(as\?|is)\s+Telegram|EnginePeer\(peer\.peer\)" submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenCallActions.swift` + +Expected: zero matches. + +--- + +## Task 7: Edit `TelegramBaseController.swift` + +**Files:** +- Modify: `submodules/TelegramBaseController/Sources/TelegramBaseController.swift` + +1 C4 bridge-drop + 3 C2 downcasts + 2 C3 wraps. + +- [ ] **Step 7.1: Bridge-drop at line 208** + +Edit: + +```swift +// OLD + |> map { peer in + return [FoundPeer(peer: peer._asPeer(), subscribers: nil)] + } +``` + +```swift +// NEW + |> map { peer in + return [FoundPeer(peer: peer, subscribers: nil)] + } +``` + +- [ ] **Step 7.2: Rewrite C2 downcasts at lines 243, 246, 258** + +Edit (lines 243 and 246 form one if-else chain): + +```swift +// OLD + for peer in peers { + if peer.peer is TelegramGroup { + isGroup = true + break + } else if let peer = peer.peer as? TelegramChannel, case .group = peer.info { + isGroup = true + break + } + } +``` + +```swift +// NEW + for peer in peers { + if case .legacyGroup = peer.peer { + isGroup = true + break + } else if case let .channel(channel) = peer.peer, case .group = channel.info { + isGroup = true + break + } + } +``` + +Edit (line 258): + +```swift +// OLD + } else if let subscribers = peer.subscribers { + if let peer = peer.peer as? TelegramChannel, case .broadcast = peer.info { + subtitle = strongSelf.presentationData.strings.Conversation_StatusSubscribers(subscribers) + } else { + subtitle = strongSelf.presentationData.strings.Conversation_StatusMembers(subscribers) + } + } +``` + +```swift +// NEW + } else if let subscribers = peer.subscribers { + if case let .channel(channel) = peer.peer, case .broadcast = channel.info { + subtitle = strongSelf.presentationData.strings.Conversation_StatusSubscribers(subscribers) + } else { + subtitle = strongSelf.presentationData.strings.Conversation_StatusMembers(subscribers) + } + } +``` + +- [ ] **Step 7.3: Drop two C3 wraps on line 265** + +Edit: + +```swift +// OLD + items.append(VoiceChatPeerActionSheetItem(context: context, peer: EnginePeer(peer.peer), title: EnginePeer(peer.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), subtitle: subtitle ?? "", action: { +``` + +```swift +// NEW + items.append(VoiceChatPeerActionSheetItem(context: context, peer: peer.peer, title: peer.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), subtitle: subtitle ?? "", action: { +``` + +- [ ] **Step 7.4: Verify** — grep: + +Run: `grep -nE "peer\.peer\s+(as\?|is)\s+Telegram|EnginePeer\(peer\.peer\)" submodules/TelegramBaseController/Sources/TelegramBaseController.swift` + +Expected: zero matches. + +--- + +## Task 8: Edit `StorageUsageExceptionsScreen.swift` + +**Files:** +- Modify: `submodules/SettingsUI/Sources/Data and Storage/StorageUsageExceptionsScreen.swift` + +1 C4 wrap-needed + 2 C3 wrap-drops. + +- [ ] **Step 8.1: Drop C3 wrap at line 173** + +Edit: + +```swift +// OLD + title = EnginePeer(peer.peer).displayTitle(strings: presentationData.strings, displayOrder: .firstLast) +``` + +```swift +// NEW + title = peer.peer.displayTitle(strings: presentationData.strings, displayOrder: .firstLast) +``` + +- [ ] **Step 8.2: Drop C3 wrap at line 176** + +Edit: + +```swift +// OLD + return ItemListDisclosureItem(presentationData: presentationData, icon: nil, context: arguments.context, iconPeer: EnginePeer(peer.peer), title: title, enabled: true, titleFont: .bold, label: optionText, labelStyle: .text, additionalDetailLabel: additionalDetailLabel, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: { +``` + +```swift +// NEW + return ItemListDisclosureItem(presentationData: presentationData, icon: nil, context: arguments.context, iconPeer: peer.peer, title: title, enabled: true, titleFont: .bold, label: optionText, labelStyle: .text, additionalDetailLabel: additionalDetailLabel, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: { +``` + +- [ ] **Step 8.3: Add EnginePeer wrap at constructor line 288** + +Edit: + +```swift +// OLD + result.append((peer: FoundPeer(peer: peer, subscribers: subscriberCount), value: value)) +``` + +```swift +// NEW + result.append((peer: FoundPeer(peer: EnginePeer(peer), subscribers: subscriberCount), value: value)) +``` + +(Verification: `peer` here is bound from a Postbox transaction higher up — it is a raw `Peer`. The wrap is required.) + +- [ ] **Step 8.4: Verify** — grep: + +Run: `grep -nE "EnginePeer\(peer\.peer\)" "submodules/SettingsUI/Sources/Data and Storage/StorageUsageExceptionsScreen.swift"` + +Expected: zero matches. + +--- + +## Task 9: Build verification (first pass) + +- [ ] **Step 9.1: Run the full build with `--continueOnError`** + +Run: + +```bash +source ~/.zshrc 2>/dev/null && python3 build-system/Make/Make.py --overrideXcodeVersion --cacheDir ~/telegram-bazel-cache build --configurationPath build-system/appstore-configuration.json --gitCodesigningRepository git@gitlab.com:peter-iakovlev/fastlanematch.git --gitCodesigningType development --gitCodesigningUseCurrent --buildNumber=1 --configuration=debug_sim_arm64 --continueOnError 2>&1 | tee /tmp/wave34-build.log +``` + +Expected outcome: ideally clean. Realistic outcome: a small number of errors at sites the inventory missed. + +- [ ] **Step 9.2: Triage build errors** + +Likely error patterns and their fixes: + +| Error | Fix | +|---|---| +| `cannot convert value of type 'EnginePeer' to expected argument type 'Peer'` at site `(peer.peer, ...)` | Add `._asPeer()` bridge: `(peer.peer._asPeer(), ...)` | +| `value of type 'EnginePeer' has no member 'isEqual'` | Replace with `==`: `peer.peer == otherPeer` | +| `cannot convert value of type 'EnginePeer' to expected argument type 'Peer?'` | Same as above: `._asPeer()` bridge | +| `pattern of type 'TelegramX' cannot match values of type 'EnginePeer'` | Missed C2 site — rewrite to `if case .X = peer.peer` form | +| `extraneous argument 'EnginePeer'` (when `.localPeer` etc. expected raw Peer) | Add `._asPeer()` bridge | + +For each error, identify the file:line, apply the appropriate fix, and re-run the build (Step 9.1) until clean. + +- [ ] **Step 9.3: Iterate to clean build** + +Re-run the build after each batch of fixes. The wave is complete when the build returns 0 errors. + +If 10+ unexpected errors surface, halt and reassess: the inventory was significantly incomplete and the wave may need to be split into pre-cleanup commits. Discuss with user. + +--- + +## Task 10: Post-build grep validations + +- [ ] **Step 10.1: Bridge-drop validation** + +Run: + +```bash +grep -rn "FoundPeer(peer:.*\._asPeer()" submodules/ --include="*.swift" | grep -v "^submodules/TelegramCore/" | grep -v "^submodules/Postbox/" +``` + +Expected: zero hits. If any remain, they are missed bridge-drops — fix and re-build. + +- [ ] **Step 10.2: C3 wrap validation** + +Run for each touched consumer file: + +```bash +for f in submodules/TelegramCallsUI/Sources/VideoChatScreen.swift \ + submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift \ + submodules/ContactListUI/Sources/ContactListNode.swift \ + submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift \ + submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenCallActions.swift \ + submodules/TelegramBaseController/Sources/TelegramBaseController.swift \ + "submodules/SettingsUI/Sources/Data and Storage/StorageUsageExceptionsScreen.swift"; do + echo "=== $f ===" + grep -n "EnginePeer(peer\.peer)" "$f" +done +``` + +Expected: zero hits in each touched file. + +- [ ] **Step 10.3: C2 downcast validation** + +Run: + +```bash +for f in submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift \ + submodules/ContactListUI/Sources/ContactListNode.swift \ + submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift \ + submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenCallActions.swift \ + submodules/TelegramBaseController/Sources/TelegramBaseController.swift; do + echo "=== $f ===" + grep -nE "peer\.peer\s+(as\?|is)\s+Telegram" "$f" +done +``` + +Expected: zero hits. + +If any of the validations fail, return to Task 9 to fix. + +--- + +## Task 11: Single atomic commit + memory + log update + +- [ ] **Step 11.1: Stage and review** + +Run: + +```bash +git status --short +git diff --stat +``` + +Confirm exactly 8 modified files (1 TelegramCore + 7 consumer) and no other unintended changes. WIP from earlier (`build-system/bazel-rules/sourcekit-bazel-bsp`, `ChatListFilterPresetController.swift`, `ChatListFilterPresetListController.swift`, untracked `build-system/tulsi/`, `submodules/TgVoip/`, `third-party/libx264/`) should NOT be staged. + +- [ ] **Step 11.2: Stage only the wave-34 files** + +Run: + +```bash +git add submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift \ + submodules/TelegramCallsUI/Sources/VideoChatScreen.swift \ + submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift \ + submodules/ContactListUI/Sources/ContactListNode.swift \ + submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift \ + submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenCallActions.swift \ + submodules/TelegramBaseController/Sources/TelegramBaseController.swift \ + "submodules/SettingsUI/Sources/Data and Storage/StorageUsageExceptionsScreen.swift" +``` + +- [ ] **Step 11.3: Commit** + +Run: + +```bash +git commit -m "$(cat <<'EOF' +Postbox -> TelegramEngine wave 34: FoundPeer.peer Peer -> EnginePeer + +Migrates the public field `FoundPeer.peer` from the Postbox `Peer` protocol +to the TelegramCore `EnginePeer` enum. The `_internal_searchPeers` body +keeps `import Postbox` (it still calls `postbox.transaction`) and wraps raw +peer values with `EnginePeer(peer)` at the FoundPeer constructor sites. + +Consumer-side cascade in 7 files: + - 5 `._asPeer()` bridge-drops at FoundPeer constructor sites + - 22 redundant `EnginePeer(peer.peer)` wrap drops (the field is now + EnginePeer, so the wrap fails to compile) + - 30 `peer.peer as? TelegramX` / `is TelegramX` Postbox-concrete + downcasts rewritten to `if case .X = peer.peer` enum-pattern form + - 3 `._asPeer()` bridges added where `peer.peer` flows into + `ContactListPeer.peer(peer:)` (downstream API still takes raw Peer) + - Manual `==` body updated from `lhs.peer.isEqual(rhs.peer)` to + `lhs.peer == rhs.peer` (EnginePeer is Equatable) + +Files modified: + submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift + submodules/TelegramCallsUI/Sources/VideoChatScreen.swift + submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift + submodules/ContactListUI/Sources/ContactListNode.swift + submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift + submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenCallActions.swift + submodules/TelegramBaseController/Sources/TelegramBaseController.swift + submodules/SettingsUI/Sources/Data and Storage/StorageUsageExceptionsScreen.swift + +Plan: docs/superpowers/plans/2026-04-24-foundpeer-engine-peer-migration.md +Spec: docs/superpowers/specs/2026-04-24-foundpeer-engine-peer-migration-design.md + +Co-Authored-By: Claude Opus 4.7 (1M context) +EOF +)" +``` + +- [ ] **Step 11.4: Update CLAUDE.md wave counter** + +Edit `CLAUDE.md` to bump the "Waves landed so far" line from "33 waves" to "34 waves" and update the "as of" date if changed. + +- [ ] **Step 11.5: Append wave outcome to the postbox-refactor-log** + +Append a new "Wave 34 outcome" section to `docs/superpowers/postbox-refactor-log.md` documenting the migration, the actual edit count (in case it differed from the planned ~70), and any lessons learned. Keep concise. + +- [ ] **Step 11.6: Commit the docs update** + +Run: + +```bash +git add CLAUDE.md docs/superpowers/postbox-refactor-log.md +git commit -m "$(cat <<'EOF' +docs: add wave 34 outcome (FoundPeer.peer Peer→EnginePeer) + +Co-Authored-By: Claude Opus 4.7 (1M context) +EOF +)" +``` + +- [ ] **Step 11.7: Update the next-wave memory** + +Update `/Users/isaac/.claude/projects/-Users-isaac-build-telegram-telegram-ios/memory/project_postbox_refactor_next_wave.md`: +- Add wave 34 to the "Latest commits" section +- Move FoundPeer migration from "Wave 34+ candidates" to landed +- Reframe remaining work: SendAsPeer, makePeerInfoController, etc., are now the next ring of Peer-typed-API waves +- Note any newly-surfaced bridge-add sites (the 3 `._asPeer()` bridges in ContactListNode point to `ContactListPeer.peer(peer:)` as the next downstream-API migration target) + +Use the Edit tool on the memory file. No git commit needed for the memory file (it's outside the repo). + +--- + +## Risks and notes + +- **Inner `peer` shadowing.** Several rewrites collapse `else if let peer = peer.peer as? TelegramChannel` patterns where the inner `peer` shadows the loop variable. The rewrites use `channel` as the new binding name to avoid double shadowing of the EnginePeer loop variable. Confirm subsequent uses of the bound name within the if-let scope are updated (e.g., `peer.info` becomes `channel.info`). +- **`replace_all` correctness.** Whenever the plan suggests `replace_all=true`, verify the count first via grep. If the count is unexpected, revert to per-site Edits with surrounding context. +- **Outflow-bridge surprises.** The plan enumerates 3 `._asPeer()` outflow bridges in ContactListNode. The build pass (Task 9) may surface 1–3 more in other touched files (e.g., ChatListSearchListPaneNode's `.localPeer` case). Apply the bridge pattern to each and iterate. +- **WIP isolation.** Pre-existing modifications to `ChatListFilterPresetController.swift`, `ChatListFilterPresetListController.swift`, the `sourcekit-bazel-bsp` submodule marker, and untracked `build-system/tulsi/` / `submodules/TgVoip/` / `third-party/libx264/` are user WIP — do NOT stage them. Use the explicit `git add ` form in Step 11.2. diff --git a/docs/superpowers/plans/2026-04-24-sendaspeer-engine-peer-migration.md b/docs/superpowers/plans/2026-04-24-sendaspeer-engine-peer-migration.md new file mode 100644 index 0000000000..56c804e215 --- /dev/null +++ b/docs/superpowers/plans/2026-04-24-sendaspeer-engine-peer-migration.md @@ -0,0 +1,666 @@ +# Wave 35: `SendAsPeer.peer: Peer → EnginePeer` Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Migrate the public field `SendAsPeer.peer` from the Postbox `Peer` protocol to the TelegramCore `EnginePeer` enum in a single atomic commit. Drops 3 `._asPeer()` bridges at construction sites, collapses 6 redundant `EnginePeer(peer.peer)` wraps, rewrites 1 `peer.peer as? TelegramChannel` downcast to an enum pattern, and adds `EnginePeer(channel)` wraps at 2 raw-`TelegramChannel` construction sites. No outflow `._asPeer()` bridges need to be added for this wave (unlike wave 34's `ContactListPeer.peer(peer:)` bridge). + +**Architecture:** One atomic commit. The field-type change is necessarily atomic (half-migrated SendAsPeer doesn't compile), so all edits land together. TelegramCore's `_internal_*SendAsAvailablePeers` functions keep `import Postbox` — only `SendAsPeer`'s public surface changes. No new wrappers, no new typealiases. The manual `==` body is replaced with synthesized Equatable (EnginePeer is Equatable). + +**Tech Stack:** Swift, Bazel build via Make.py wrapper. No tests — verification is build success + targeted grep checks. + +**Spec:** `docs/superpowers/specs/2026-04-24-sendaspeer-engine-peer-migration-design.md` + +--- + +## File Structure + +**Modified files (7 expected — 1 TelegramCore + 6 consumer. Plus 2 "verify no-edit" files.)** + +| File | Edit count | Category | +|---|---|---| +| `submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift` | ~7 spot edits (struct change + 4 constructor wraps + drop manual `==`) | α | +| `submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift` | ~5 (1 cast rewrite + 4 wrap drops) | γ | +| `submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift` | 3 (1 bridge-drop + 2 EnginePeer wraps on raw channel) | δ | +| `submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift` | 1 (bridge-drop) | δ | +| `submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift` | 1 (wrap collapse) | δ | +| `submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift` | ~4 (1 bridge-drop + 1 flatMap simplify + 1 map simplify) | δ | + +**Verify-only (no edits expected):** +| File | Reason | +|---|---| +| `submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift` | Holds `[SendAsPeer]?` at collection level, no `.peer` access. | +| `submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift` | Passes `currentSendAsPeer` through to `ChatSendAsPeerListContextItem` which keeps taking `[SendAsPeer]`. | + +**EnginePeer enum case mapping (used in cast rewrite):** + +| Postbox concrete | EnginePeer case | +|---|---| +| `TelegramChannel` | `.channel(TelegramChannel)` | +| `TelegramGroup` | `.legacyGroup(TelegramGroup)` | +| `TelegramUser` | `.user(TelegramUser)` | + +--- + +## Task 1: Edit `SendAsPeers.swift` — struct definition + constructor wraps + +**Files:** +- Modify: `submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift` + +Foundational change. Without it, none of the consumer edits compile. + +- [ ] **Step 1.1: Update the SendAsPeer struct field, init parameter, and drop manual `==`** + +Edit: + +```swift +// OLD +public struct SendAsPeer: Equatable { + public let peer: Peer + public let subscribers: Int32? + public let isPremiumRequired: Bool + + public init(peer: Peer, subscribers: Int32?, isPremiumRequired: Bool) { + self.peer = peer + self.subscribers = subscribers + self.isPremiumRequired = isPremiumRequired + } + + public static func ==(lhs: SendAsPeer, rhs: SendAsPeer) -> Bool { + return lhs.peer.isEqual(rhs.peer) && lhs.subscribers == rhs.subscribers && lhs.isPremiumRequired == rhs.isPremiumRequired + } +} +``` + +```swift +// NEW +public struct SendAsPeer: Equatable { + public let peer: EnginePeer + public let subscribers: Int32? + public let isPremiumRequired: Bool + + public init(peer: EnginePeer, subscribers: Int32?, isPremiumRequired: Bool) { + self.peer = peer + self.subscribers = subscribers + self.isPremiumRequired = isPremiumRequired + } +} +``` + +Use the Edit tool with the OLD block as `old_string` and the NEW block as `new_string`. Swift synthesizes Equatable for structs where every stored property is Equatable: `EnginePeer` is Equatable, `Int32?` is Equatable, `Bool` is Equatable — so the manual `==` is no longer needed. + +- [ ] **Step 1.2: Wrap raw Postbox `Peer` values at the four constructor sites** + +Sites at lines 64, 170, 236, 330. Each binds a raw Postbox `Peer` (from `transaction.getPeer(peerId)` or `peers.map { ... }`) and passes it to the `SendAsPeer(peer: ...)` init. Wrap each with `EnginePeer(...)`. + +Edit (line 64, inside `_internal_cachedPeerSendAsAvailablePeers`, cache-hit branch): + +```swift +// OLD + peers.append(SendAsPeer(peer: peer, subscribers: subscribers, isPremiumRequired: cached.premiumRequiredPeerIds.contains(peer.id))) +``` + +```swift +// NEW + peers.append(SendAsPeer(peer: EnginePeer(peer), subscribers: subscribers, isPremiumRequired: cached.premiumRequiredPeerIds.contains(peer.id))) +``` + +Edit (line 170, inside `_internal_peerSendAsAvailablePeers`, network-response map): + +```swift +// OLD + return peers.map { SendAsPeer(peer: $0, subscribers: subscribers[$0.id], isPremiumRequired: premiumRequiredPeerIds.contains($0.id)) } +``` + +```swift +// NEW + return peers.map { SendAsPeer(peer: EnginePeer($0), subscribers: subscribers[$0.id], isPremiumRequired: premiumRequiredPeerIds.contains($0.id)) } +``` + +Edit (line 236, inside `_internal_cachedLiveStorySendAsAvailablePeers`, cache-hit branch): + +```swift +// OLD + peers.append(SendAsPeer(peer: peer, subscribers: subscribers, isPremiumRequired: cached.premiumRequiredPeerIds.contains(peer.id))) +``` + +```swift +// NEW + peers.append(SendAsPeer(peer: EnginePeer(peer), subscribers: subscribers, isPremiumRequired: cached.premiumRequiredPeerIds.contains(peer.id))) +``` + +Note: lines 64 and 236 have identical text. If you prefer `replace_all=true`, do a grep first to confirm the count is exactly 2, then apply once. + +Edit (line 330, inside `_internal_liveStorySendAsAvailablePeers`, network-response map): + +```swift +// OLD + return peers.map { SendAsPeer(peer: $0, subscribers: subscribers[$0.id], isPremiumRequired: premiumRequiredPeerIds.contains($0.id)) } +``` + +```swift +// NEW + return peers.map { SendAsPeer(peer: EnginePeer($0), subscribers: subscribers[$0.id], isPremiumRequired: premiumRequiredPeerIds.contains($0.id)) } +``` + +Same remark as above: lines 170 and 330 are identical — one `replace_all=true` covers both if the count is exactly 2. + +- [ ] **Step 1.3: Verify** — read the updated file and confirm: + - The struct's `peer` field is now `EnginePeer` + - The init parameter is `peer: EnginePeer` + - Manual `==` has been removed + - All 4 constructor sites wrap with `EnginePeer(...)` + - `peer.peer.id` accesses inside the caching loops (lines 87, 90, 259, 262) remain unchanged (`EnginePeer.id` typealias to `PeerId` keeps them valid) + +Do not commit yet. + +--- + +## Task 2: Edit `ChatSendAsPeerListContextItem.swift` — cast rewrite + wrap collapse + +**Files:** +- Modify: `submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift` + +1 Postbox-concrete downcast rewrite + 4 `EnginePeer(peer.peer)` wrap drops. + +- [ ] **Step 2.1: Rewrite the `peer.peer as? TelegramChannel` downcast at line 73** + +Edit: + +```swift +// OLD + } else if let subscribers = peer.subscribers { + if let peer = peer.peer as? TelegramChannel { + if case .broadcast = peer.info { +``` + +```swift +// NEW + } else if let subscribers = peer.subscribers { + if case let .channel(channel) = peer.peer { + if case .broadcast = channel.info { +``` + +Note: the original `if let peer = peer.peer as? TelegramChannel` shadows the outer `peer: SendAsPeer` loop variable. The rewrite uses `channel` to avoid shadowing. Any subsequent uses of `peer.info`, `peer.flags`, etc. inside the inner `if let peer = ...` block must be renamed to `channel.*`. + +Read lines 70–90 before editing to see the full extent of the shadowed-`peer` scope, and ensure every reference to `peer.info` (and any sibling field access like `peer.flags`, `peer.username`, etc.) within the inner block is rewritten to `channel.*`. The snippet above captures the only `peer.info` site from the inventory. + +- [ ] **Step 2.2: Drop `EnginePeer(peer.peer)` wraps at lines 89, 110, 116, 121** + +The field `peer.peer` is now `EnginePeer`, so `EnginePeer(peer.peer)` becomes a type error. Drop the wrap. + +Read the full lines first to confirm each site's shape. Expected patterns (edit one at a time with enough surrounding context to make each unique — the four sites likely differ in surrounding tokens): + +For each of the four sites, the pattern to eliminate is `EnginePeer(peer.peer)` → `peer.peer`. Example: + +```swift +// OLD + let title = EnginePeer(peer.peer).displayTitle(strings: strings, displayOrder: nameDisplayOrder) +``` + +```swift +// NEW + let title = peer.peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder) +``` + +Identify each of the four sites (lines 89, 110, 116, 121) by reading the file, then apply one Edit per site using enough surrounding context (usually 1–2 tokens before/after the `EnginePeer(peer.peer)` subexpression) to make the `old_string` unique. + +If all four lines reduce to the same substring pattern (e.g., `EnginePeer(peer.peer)` as a standalone subexpression), `replace_all=true` on the substring `EnginePeer(peer.peer)` → `peer.peer` is safe — but **first** grep to confirm the count is exactly 4 and no other meaning is captured. + +Run before: `grep -cE "EnginePeer\(peer\.peer\)" submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift` + +Expected: 4. + +- [ ] **Step 2.3: Verify** — grep: + +Run: `grep -nE "peer\.peer\s+(as\?|is)\s+Telegram|EnginePeer\(peer\.peer\)" submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift` + +Expected: zero matches. + +--- + +## Task 3: Edit `ChatControllerLoadDisplayNode.swift` — bridge-drop + raw-channel wraps + +**Files:** +- Modify: `submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift` + +1 `._asPeer()` bridge-drop at line 772 + 2 `EnginePeer(channel)` wraps for raw `TelegramChannel` at lines 805 and 823. + +- [ ] **Step 3.1: Bridge-drop at line 772** + +Edit: + +```swift +// OLD + return SendAsPeer(peer: peer._asPeer(), subscribers: nil, isPremiumRequired: false) +``` + +```swift +// NEW + return SendAsPeer(peer: peer, subscribers: nil, isPremiumRequired: false) +``` + +Verification: the surrounding signal chain binds `peer` as `EnginePeer` (from `context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: ...))`). The `._asPeer()` bridge is no longer needed. + +If the line text differs from the OLD block above (e.g., different field order or trailing arguments), read the file around line 772 and adjust the `old_string` to match byte-for-byte before editing. + +- [ ] **Step 3.2: Wrap raw `TelegramChannel` at line 805** + +Read lines 800–812 to see the bound `channel` variable. The construction site should be `SendAsPeer(peer: channel, ...)` where `channel: TelegramChannel` is raw Postbox. + +Edit: + +```swift +// OLD + SendAsPeer(peer: channel, subscribers: subscribers, isPremiumRequired: isPremiumRequired) +``` + +```swift +// NEW + SendAsPeer(peer: EnginePeer(channel), subscribers: subscribers, isPremiumRequired: isPremiumRequired) +``` + +If the surrounding context differs (different field values), match the actual line text when writing `old_string`. + +- [ ] **Step 3.3: Wrap raw `TelegramChannel` at line 823** + +Same pattern as Step 3.2. Read lines 818–830 first, identify the `SendAsPeer(peer: channel, ...)` construction site, and wrap `channel` with `EnginePeer(...)`. + +If the line text at 805 and 823 is identical, `replace_all=true` on the substring `SendAsPeer(peer: channel,` → `SendAsPeer(peer: EnginePeer(channel),` covers both. **First** grep to confirm the count: + +Run before: `grep -cE "SendAsPeer\(peer: channel," submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift` + +Expected: 2. + +- [ ] **Step 3.4: Verify** — grep: + +Run: `grep -nE "SendAsPeer\(peer:\s+\w+\._asPeer\(\)|SendAsPeer\(peer:\s+channel," submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift` + +Expected: zero matches. Lines 792, 826, 835, 844 retaining `.peer.id` accesses are expected and correct. + +--- + +## Task 4: Edit `ChatTextInputPanelComponent.swift` — bridge-drop + +**Files:** +- Modify: `submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift` + +1 `._asPeer()` bridge-drop. + +- [ ] **Step 4.1: Bridge-drop at line 847** + +Read lines 843–853 to confirm the surrounding signal chain and the type of `sendAsConfiguration.currentPeer` (expected: `EnginePeer`). + +Edit: + +```swift +// OLD + let sendAsPeers = [SendAsPeer(peer: sendAsConfiguration.currentPeer._asPeer(), subscribers: nil, isPremiumRequired: false)] +``` + +```swift +// NEW + let sendAsPeers = [SendAsPeer(peer: sendAsConfiguration.currentPeer, subscribers: nil, isPremiumRequired: false)] +``` + +If the actual line text wraps across multiple lines or uses different field values, match the real text byte-for-byte when writing `old_string`. + +- [ ] **Step 4.2: Verify** — grep: + +Run: `grep -nE "SendAsPeer\(peer:.*\._asPeer\(\)" submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift` + +Expected: zero matches. + +--- + +## Task 5: Edit `ChatTextInputPanelNode.swift` — wrap collapse + +**Files:** +- Modify: `submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift` + +1 `EnginePeer(peer)` wrap collapse at line 1625. + +- [ ] **Step 5.1: Collapse `EnginePeer(peer)` wrap** + +Read lines 1615–1630 to see the full context. `peer` is bound from a preceding `var currentPeer = sendAsPeers.first(where: { $0.peer.id == ... })?.peer` (lines 1620–1622). After migration, `.peer` returns `EnginePeer`, so `EnginePeer(peer)` on an `EnginePeer` is a type error. + +Exact edit depends on the actual line text. Example shape: + +```swift +// OLD (at or near line 1625) + let enginePeer = EnginePeer(peer) +``` + +```swift +// NEW + let enginePeer = peer +``` + +Read lines 1623–1628 first and write the Edit with byte-accurate `old_string`. If the bound variable is then used as `enginePeer.displayTitle(...)`, consider whether the rename can be eliminated entirely (e.g., rename `peer` uses downstream), but prefer the minimal edit for commit clarity. + +Lines 1616, 1620, 1622, 2948, 5370 should remain unchanged — they perform `.peer.id` comparisons or `.first(where:)` lookups that work identically on `[SendAsPeer]` with `EnginePeer`-typed `.peer`. + +- [ ] **Step 5.2: Verify** — grep: + +Run: `grep -nE "EnginePeer\(peer\)" submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift` + +Expected: zero matches. If any remain, inspect each — they may be unrelated wraps on non-SendAsPeer-sourced `peer` variables (in which case they must stay). + +--- + +## Task 6: Edit `StoryItemSetContainerViewSendMessage.swift` — multi-site cleanup + +**Files:** +- Modify: `submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift` + +1 bridge-drop + 1 flatMap simplify + 1 map simplify. Many other `.peer.id` / `.peer` accesses remain unchanged. + +- [ ] **Step 6.1: Bridge-drop at line 249** + +Read lines 244–254 to confirm `accountPeer` is typed as `EnginePeer` upstream. + +Edit: + +```swift +// OLD + availablePeers.append(SendAsPeer( + peer: accountPeer._asPeer(), + subscribers: nil, + isPremiumRequired: false + )) +``` + +```swift +// NEW + availablePeers.append(SendAsPeer( + peer: accountPeer, + subscribers: nil, + isPremiumRequired: false + )) +``` + +If the actual layout (whitespace, line breaks) differs from the OLD block, match the real text byte-for-byte when writing `old_string`. + +- [ ] **Step 6.2: Simplify flatMap at line 4080** + +`EnginePeer.init` as a function reference expects a raw `Peer` and returns `EnginePeer`. After migration, `sendAsPeer?.peer` is already `EnginePeer?`, so `.flatMap(EnginePeer.init)` is both unnecessary and a type error. + +Edit: + +```swift +// OLD + myPeer: (sendAsPeer?.peer).flatMap(EnginePeer.init), +``` + +```swift +// NEW + myPeer: sendAsPeer?.peer, +``` + +Read lines 4078–4082 first to confirm the surrounding labeled-argument layout and match byte-for-byte. + +- [ ] **Step 6.3: Simplify map at line 4081** + +`.map({ EnginePeer($0.peer) })` wraps each already-`EnginePeer` value in `EnginePeer(...)` — a type error. Drop the wrap. + +Edit: + +```swift +// OLD + availableSendAsPeers: component.isEmbeddedInCamera ? [] : (self.sendAsData?.availablePeers.map({ EnginePeer($0.peer) }) ?? []), +``` + +```swift +// NEW + availableSendAsPeers: component.isEmbeddedInCamera ? [] : (self.sendAsData?.availablePeers.map({ $0.peer }) ?? []), +``` + +Read lines 4079–4083 first to confirm the exact line text. + +- [ ] **Step 6.4: Verify** — grep: + +Run: `grep -nE "SendAsPeer\(peer:.*\._asPeer\(\)|EnginePeer\(\$0\.peer\)|\(sendAsPeer\?\.peer\)\.flatMap\(EnginePeer\.init\)" submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift` + +Expected: zero matches. + +Retained-as-is accesses (inventory-verified correct after migration): `.peer.id` at lines 254, 688, 4088, 4089, 4327, 4333, 4340, 4356, 4372; optional chaining at 4050, 4068, 4069. These should NOT be edited. + +--- + +## Task 7: Verify "no-edit" consumer files + +**Files:** +- Read: `submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift` +- Read: `submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift` + +Sanity-check: confirm neither file contains `.peer as?`/`is`, `EnginePeer(.peer)`, or `._asPeer()` patterns tied to SendAsPeer. If any such pattern is found, fold the fix into the relevant task above before the build pass. + +- [ ] **Step 7.1: Grep ChatPresentationInterfaceState.swift** + +Run: `grep -nE "SendAsPeer|sendAsPeers" submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift` + +Expected shape: field declaration, init param, assignment, equality comparison, `updatedSendAsPeers(_:)` method — all at the `[SendAsPeer]?` collection level. No `.peer` field access. + +- [ ] **Step 7.2: Grep StoryItemSetContainerComponent.swift** + +Run: `grep -nE "SendAsPeer|currentSendAsPeer|\.peer\b" submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift | grep -iE "sendAsPeer|\.peer"` + +Read lines 3056–3072 to confirm `sendMessageContext.currentSendAsPeer` is only passed through to `ChatSendAsPeerListContextItem` (which keeps `[SendAsPeer]`) or accessed for `.peer.id` comparisons — neither requires an edit. + +If the verification shows an edit is needed, add the edit as an additional step under the relevant Task 2–6. Do not edit here silently. + +--- + +## Task 8: Build verification (first pass) + +- [ ] **Step 8.1: Run the full build with `--continueOnError`** + +Run: + +```bash +source ~/.zshrc 2>/dev/null && python3 build-system/Make/Make.py --overrideXcodeVersion --cacheDir ~/telegram-bazel-cache build --configurationPath build-system/appstore-configuration.json --gitCodesigningRepository git@gitlab.com:peter-iakovlev/fastlanematch.git --gitCodesigningType development --gitCodesigningUseCurrent --buildNumber=1 --configuration=debug_sim_arm64 --continueOnError 2>&1 | tee /tmp/wave35-build.log +``` + +Expected outcome: ideally clean. Realistic outcome: 0–5 errors at sites the inventory missed. + +- [ ] **Step 8.2: Triage build errors** + +Likely error patterns and their fixes: + +| Error | Fix | +|---|---| +| `cannot convert value of type 'EnginePeer' to expected argument type 'Peer'` at site passing `peer.peer` | Add `._asPeer()` bridge: `peer.peer._asPeer()` | +| `cannot convert value of type 'Peer' to expected argument type 'EnginePeer'` at `SendAsPeer(peer: ...)` | Add wrap: `SendAsPeer(peer: EnginePeer(), ...)` | +| `value of type 'EnginePeer' has no member 'isEqual'` | Replace with `==` | +| `pattern of type 'TelegramChannel' cannot match values of type 'EnginePeer'` | Missed C2 — rewrite to `if case .channel(let channel) = peer.peer` form | +| `cannot invoke initializer for type 'EnginePeer' with an argument list of type '(EnginePeer)'` | Missed wrap collapse — drop `EnginePeer(...)` | +| `extraneous argument label 'peer:' in call` or similar on `SendAsPeer(...)` | Check that the construction arg is `EnginePeer`, not raw — add `EnginePeer(...)` wrap | + +For each error, identify the file:line, apply the appropriate fix, and re-run the build until clean. + +- [ ] **Step 8.3: Iterate to clean build** + +Re-run the build after each batch of fixes. The wave is complete when the build returns 0 errors for the targeted configuration. + +If 10+ unexpected errors surface, halt and reassess: the inventory was significantly incomplete and the wave may need to be split into pre-cleanup commits. Discuss with user before continuing. + +--- + +## Task 9: Post-build grep validations + +- [ ] **Step 9.1: Bridge-drop validation** + +Run: + +```bash +grep -rn "SendAsPeer(peer:.*\._asPeer()" submodules/ --include="*.swift" | grep -v "^submodules/TelegramCore/" | grep -v "^submodules/Postbox/" +``` + +Expected: zero hits. If any remain, those are missed bridge-drops — fix and re-run Task 8. + +- [ ] **Step 9.2: Wrap-collapse validation** + +Run: + +```bash +for f in submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift \ + submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift \ + submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift \ + submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift \ + submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift; do + echo "=== $f ===" + grep -nE "EnginePeer\(peer\.peer\)|EnginePeer\(\$0\.peer\)|\(sendAsPeer\?\.peer\)\.flatMap\(EnginePeer\.init\)" "$f" +done +``` + +Expected: zero hits across all 5 files. + +- [ ] **Step 9.3: C2 cast validation** + +Run: + +```bash +grep -nE "peer\.peer\s+(as\?|is)\s+Telegram" submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift +``` + +Expected: zero hits. + +- [ ] **Step 9.4: Construction-site validation** + +Ensure all `SendAsPeer(peer: ...)` construction sites outside TelegramCore provide `EnginePeer`: + +```bash +grep -rnE "SendAsPeer\(peer:" submodules/ --include="*.swift" | grep -v "^submodules/TelegramCore/" +``` + +Inspect each hit. Expected forms: `SendAsPeer(peer: , ...)` or `SendAsPeer(peer: EnginePeer(), ...)`. Anything of the form `SendAsPeer(peer: , ...)` is a miss — fix. + +If any of the validations fail, return to Task 8 to fix. + +--- + +## Task 10: Atomic commit + memory + log update + +- [ ] **Step 10.1: Stage and review** + +Run: + +```bash +git status --short +git diff --stat +``` + +Confirm exactly 6 modified Swift files (1 TelegramCore + 5 consumer — or 7 if Task 7 surfaced a needed edit). Files expected: +- `submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift` +- `submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift` +- `submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift` +- `submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift` +- `submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift` +- `submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift` + +WIP from earlier (`build-system/bazel-rules/sourcekit-bazel-bsp`, `ChatListFilterPresetController.swift`, `ChatListFilterPresetListController.swift`, untracked `build-system/tulsi/` / `submodules/TgVoip/` / `third-party/libx264/`) should NOT be staged. + +The `docs/superpowers/plans/2026-04-22-claude-md-reorganization.md` untracked file should ALSO remain unstaged. + +- [ ] **Step 10.2: Stage only the wave-35 files** + +Run: + +```bash +git add submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift \ + submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift \ + submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift \ + submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift \ + submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift \ + submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +``` + +If Task 7 surfaced an additional file, append it here. + +- [ ] **Step 10.3: Commit** + +Run: + +```bash +git commit -m "$(cat <<'EOF' +Postbox -> TelegramEngine wave 35: SendAsPeer.peer Peer -> EnginePeer + +Migrates the public field `SendAsPeer.peer` from the Postbox `Peer` +protocol to the TelegramCore `EnginePeer` enum. Internal +`_internal_*SendAsAvailablePeers` bodies keep `import Postbox` (they still +call `postbox.transaction`) and wrap raw peer values with `EnginePeer(peer)` +at the SendAsPeer constructor sites. Manual `==` body dropped in favor of +synthesized Equatable. + +Consumer-side cascade in 5 files: + - 3 `._asPeer()` bridge-drops at SendAsPeer constructor sites + - 6 redundant `EnginePeer(peer.peer)` / `EnginePeer($0.peer)` wrap + drops (the field is now EnginePeer, so the wrap fails to compile) + - 1 `peer.peer as? TelegramChannel` downcast rewritten to + `if case let .channel(channel) = peer.peer` enum-pattern form + - 2 `EnginePeer(channel)` wraps added where raw `TelegramChannel` is + passed into `SendAsPeer(peer: ...)` + - 1 `(sendAsPeer?.peer).flatMap(EnginePeer.init)` simplified to + `sendAsPeer?.peer` (already `EnginePeer?`) + +Files modified: + submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift + submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift + submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift + submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift + submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift + submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift + +Plan: docs/superpowers/plans/2026-04-24-sendaspeer-engine-peer-migration.md +Spec: docs/superpowers/specs/2026-04-24-sendaspeer-engine-peer-migration-design.md + +Co-Authored-By: Claude Opus 4.7 (1M context) +EOF +)" +``` + +- [ ] **Step 10.4: Update CLAUDE.md wave counter** + +Edit `CLAUDE.md` to bump the "Waves landed so far" line from "34 waves" to "35 waves" and update the "as of" date if the commit lands after 2026-04-24. + +- [ ] **Step 10.5: Append wave outcome to the postbox-refactor-log** + +Append a "Wave 35 outcome" section to `docs/superpowers/postbox-refactor-log.md` documenting: +- Actual files touched and edit counts vs. plan +- Any inventory undercounts surfaced by Task 8 +- Any lessons learned (e.g., whether the flatMap/map simplifications were actually type-required or whether they could have been left as redundant-but-compiling wraps) + +Keep concise. + +- [ ] **Step 10.6: Commit the docs update** + +Run: + +```bash +git add CLAUDE.md docs/superpowers/postbox-refactor-log.md +git commit -m "$(cat <<'EOF' +docs: add wave 35 outcome (SendAsPeer.peer Peer→EnginePeer) + +Co-Authored-By: Claude Opus 4.7 (1M context) +EOF +)" +``` + +- [ ] **Step 10.7: Update the next-wave memory** + +Update `/Users/isaac/.claude/projects/-Users-isaac-build-telegram-telegram-ios/memory/project_postbox_refactor_next_wave.md`: +- Add wave 35 to the "Latest commits" section +- Move SendAsPeer migration from "Wave 34+ candidates → Downstream Peer-typed APIs" to landed +- Record the inventory undercount ratio (actual-files-touched ÷ pre-flight-file-count) for calibration of future Peer-typed-API waves +- Update the "Recommended wave 35" section to reflect the new wave 36 recommendation. Candidates to promote: `makePeerInfoController` (largest Peer-typed-API remaining), `ContactListPeer.peer(peer:)` case payload, `canSendMessagesToPeer(_:)` parameter, accountManager-side engine path, Shape-C resourceData module pick + +Use the Edit tool on the memory file. No git commit needed (memory lives outside the repo). + +--- + +## Risks and notes + +- **Inner `peer` shadowing in ChatSendAsPeerListContextItem:73.** The original `if let peer = peer.peer as? TelegramChannel` shadows the outer `peer: SendAsPeer` loop variable. The rewrite uses `channel` to avoid shadowing. Verify every reference to `peer.info` (and any sibling field access) within the old inner-if scope is updated to `channel.*` — Step 2.1's instructions cover this, but it's easy to miss a field reference. +- **`replace_all` correctness.** Whenever the plan suggests `replace_all=true`, verify the count first via grep. If the count is unexpected, revert to per-site Edits with surrounding context. +- **Inventory undercount.** Wave 34 undercounted by ~30%. The Explore agent for wave 35 explicitly included `.peer as?`/`is`/outflow-helper patterns, so the expected ratio is lower, but budget for 1–3 inventory-missed sites surfacing in Task 8. +- **Name collisions (do NOT touch).** `[EnginePeer]` arrays in `LiveStreamSettingsScreen.swift`, `ShareWithPeersScreen.swift`, and `ChatSendStarsScreen.swift` named `sendAsPeers` / `availableSendAsPeers` are unrelated. `ChatPanelInterfaceInteraction` callbacks named `openSendAsPeer` take `(ASDisplayNode, ContextGesture?)`, not `SendAsPeer`. `initialSendAsPeerId` parameters are `PeerId`-typed. If Task 8 surfaces errors in any of these files, the fix likely indicates a wrong cascade from a real SendAsPeer site — do NOT migrate those files as part of this wave. +- **WIP isolation.** Pre-existing modifications to `ChatListFilterPresetController.swift`, `ChatListFilterPresetListController.swift`, the `sourcekit-bazel-bsp` submodule marker, and untracked `build-system/tulsi/` / `submodules/TgVoip/` / `third-party/libx264/` / `docs/superpowers/plans/2026-04-22-claude-md-reorganization.md` are user WIP — do NOT stage them. Use the explicit `git add ` form in Step 10.2. diff --git a/docs/superpowers/postbox-refactor-log.md b/docs/superpowers/postbox-refactor-log.md index 4729e51063..3794de2b50 100644 --- a/docs/superpowers/postbox-refactor-log.md +++ b/docs/superpowers/postbox-refactor-log.md @@ -632,6 +632,32 @@ Net: 3 consumer files + 1 TelegramCore file + CLAUDE.md. TelegramEngineResources Plan / record: (no plan doc this wave — mechanical sweep). +## Wave 27 outcome (2026-04-22) + +`preferencesView` consumer sweep (wave-9 pattern continuation). No new TelegramCore facades — leverages existing `TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key:)`. + +**Shape.** Replace `context.account.postbox.preferencesView(keys: [])` — returning `Signal` — with `context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: ))` — returning `Signal`. Downstream, rename `.values[]?.get(.self)` → `?.get(.self)` at each closure parameter. + +**30 consumer files, ~40 call sites migrated** across ChatListUI, ContactListUI, DebugSettingsUI, GalleryUI, PeersNearbyUI, SettingsUI, TelegramCallsUI, TelegramUI, TelegramUI/Components, WebSearchUI. Full list in `git show --stat `. + +**Multi-key sites (PresentationCallManager).** 3 sites used `preferencesView(keys: [voipConfiguration, appConfiguration])`. Migrated via the two-arg `engine.data.subscribe(itemA, itemB) |> take(1)` overload, which returns `Signal<(PreferencesEntry?, PreferencesEntry?), NoError>`. Closures that accessed `preferences.values[X]?.get(...)` rewritten to `preferences.0?.get(...)` and `preferences.1?.get(...)`. + +**Direct-postbox-param helper migrated.** `AccountContext.swift`'s `getAppConfiguration(postbox: Postbox)` helper (one internal caller only) was rewritten to `getAppConfiguration(engine: TelegramEngine)` in the same commit, switching its single call site from `getAppConfiguration(postbox: account.postbox)` to `getAppConfiguration(engine: self.engine)`. + +**Annotation update in NotificationExceptionControllerNode.swift.** An explicit signal type `Signal<(…, PreferencesView, …), NoError>` in a `mapToSignal` return was updated to `Signal<(…, PreferencesEntry?, …), NoError>`. The file still imports Postbox because `PreferencesEntry` is (for now) a Postbox-defined type surfaced through TelegramCore's `EnginePreferencesEntry` typealias — a future wave-6-style `import Postbox` sweep would clean up such imports where they're now the only Postbox reference. + +**Deliberately skipped in this wave.** +- `TelegramPermissionsUI/Sources/PermissionSplitTest.swift:100` — `permissionUISplitTest(postbox: Postbox)` is a public API whose product value `PermissionUISplitTest` itself stores `postbox: Postbox` to satisfy the `SplitTest` protocol. Proper migration requires a protocol-level refactor (or wholesale rewrite of the SplitTest abstraction) beyond this wave's scope. +- 5 TelegramCore-internal `postbox.preferencesView(...)` sites (ChatListFiltering × 3, ContentSettings × 1, ManagedGlobalNotificationSettings × 1) — the refactor only migrates consumer modules, not TelegramCore internals. + +**Build validation.** Clean first-pass build (748 processes, 227s, 0 errors). No new facades to test, shape was validated across 30 files on the first attempt. + +**Lesson — multi-key preferencesView migration.** `engine.data.subscribe(itemA, itemB)` exists and returns a Swift tuple. When a Postbox `preferencesView(keys: [K1, K2])` call is inside a `combineLatest(...)` whose downstream closure accesses `.values[K1]` and `.values[K2]`, prefer the two-arg subscribe form (vs. two separate subscribes combined externally) — it preserves `combineLatest` arity exactly. Rewrite `.values[K1]?.get(T.self)` → `.0?.get(T.self)`, `.values[K2]?.get(T.self)` → `.1?.get(T.self)`. The closure parameter name stays (e.g., `preferences`) because the tuple destructure preserves the variable-name semantics at the call site. + +Net: 30 consumer files. No TelegramCore changes. CLAUDE.md facade-inventory table unchanged (no new facades). + +Plan / record: `memory/project_postbox_wave27_plan.md` (deleted post-wave). + ## Wave 26 outcome (2026-04-21) `resourceRangesStatus` + `removeCachedResources` facade additions + consumer sweep. Combines two independent small sweeps into one commit. @@ -657,6 +683,267 @@ Net: 3 consumer files + 1 TelegramCore file + CLAUDE.md. TelegramEngineResources Plan / record: (no plan doc this wave — mechanical sweep). +## Wave 31 outcome (2026-04-23) + +Second build-verified `^import Postbox$` sweep on consumer modules since wave 6 (2026-04-19). Same methodology: speculative-drop + `--continueOnError` build loop with pattern-based preemptive restores. + +**Candidate set narrowing.** Initial candidate grep `grep -rl "^import Postbox$" submodules --include="*.swift"` returned **1184** files. 606 of those live in `submodules/TelegramCore/Sources/` — TelegramCore legitimately `import Postbox`; the TelegramCore files were accidentally included and had to be reverted via `git checkout -- submodules/TelegramCore/Sources/` before re-seeding the drop. Final consumer candidate set: **578** files. **Lesson for future sweep invocations: the candidate-set grep must filter out `submodules/TelegramCore/` as well as `submodules/Postbox/` / `submodules/TelegramApi/`.** Wave 6's methodology note at step 1 (line 37) already calls this out, but the TelegramCore carve-out is easy to miss because TelegramCore doesn't `@_exported import Postbox`, so from a pure re-exports perspective it's indistinguishable from a consumer. + +**9 build iterations to convergence** (plus 1 aborted first iteration for the TelegramCore scope error). Per-iteration failure counts: 18 → 2 → 9 → 12 → 1 → 1 → 3 → 1 → 4 → 0. Surfacing pattern was typical of a speculative-drop sweep: errors bubble one dependency-graph layer at a time. + +**Per-iteration symbol expansion.** The wave-6 preemptive-restore symbol list (CLAUDE.md's "Unused-import sweeps" guidance) needed extensions for this sweep: +- Iter 3 surfaced `CodableEntry`, `CachedMediaResourceRepresentation`, `CachedMediaRepresentationKeepDuration`. +- Iter 4 surfaced `PostboxViewKey`, `OrderedItemListView`, `UnreadMessageCountsItem`, `ChatListEntrySummaryComponents`, `PeerStoryStats`, `ItemCollectionId` (note: typealias `EngineItemCollectionId` exists but raw name still requires `import Postbox`), and broadened `\bMedia\b`, `\bMessage\b`, `\bPeer\b`. +- Iter 5 surfaced `FetchResourceSourceType` (same typealias caveat). +- Iter 6 surfaced `StoryId`. +- Iter 7 surfaced `ChatListIndex`. +- Iter 8 surfaced `PreferencesEntry` (typealias caveat), `PeerView`, `RenderedPeer`. +- Iter 9 surfaced `declareEncodable`. +- Iter 10 surfaced `ItemCollectionItemIndex`, `ValueBoxEncryptionParameters`, `fileSize`, plus a restore-script bug (see below). + +**Restore-script bug: `#if canImport(...)` blocks.** The naive restore inserter picks the last `^import ` line and appends `import Postbox` after it. If the last import sits inside an `#if canImport(AppCenter) ... #endif` preprocessor block, the restored `import Postbox` lands inside that block and is only active under that configuration. `AppDelegate.swift` in `submodules/TelegramUI/Sources/` hit this (original had `import Postbox` at line 7; drop + restore put it inside the `#if canImport(AppCenter)` block at line 51); the build failed in iter10 on `cannot find type 'Postbox' in scope` errors even though a literal `grep ^import Postbox$` matched. Fixed by manually moving the import out of the `#if` block. **Lesson for future restore-script work: insert the restored `import Postbox` BEFORE the first `#if` or `#endif` line, not after the last `import` line, to avoid preprocessor-scope traps.** + +**Results: 9 source-level surviving drops + 2 duplicate-import dedups.** Final diff: 11 files changed, +2 / -13. + +Surviving drops: +- `submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift` +- `submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift` +- `submodules/DebugSettingsUI/Sources/DebugAccountsController.swift` +- `submodules/LegacyDataImport/Sources/LegacyPreferencesImport.swift` +- `submodules/MediaPlayer/Sources/ChunkMediaPlayerDirectFetchSourceImpl.swift` +- `submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemImageView.swift` +- `submodules/TelegramUI/Sources/ChatLinkPreview.swift` +- `submodules/TelegramUI/Sources/ChatSearchResultsController.swift` +- `submodules/TelegramUI/Sources/MediaManager.swift` + +Duplicate-import dedups (files had two `^import Postbox$` lines; kept exactly one — unrelated-but-latent cleanup surfaced incidentally by the sweep): +- `submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift` (2 imports → 1) +- `submodules/TelegramUI/Sources/ChatHistoryListNode.swift` (2 imports → 1) + +**Spurious-diff cleanup step (new procedure, adopted this wave).** After convergence, `git diff --numstat` showed 564 modified files but only 9 were genuine drops. The other 553 were "1 addition + 1 deletion" — files where the original `import Postbox` at line X was deleted by the drop and re-inserted at line Y by the restore (different position because restore inserts after "last import line" regardless of original placement). These aren't semantic changes but do produce noisy diffs. Identified via `git diff --numstat | awk '$1 == 1 && $2 == 1 {print $3}'` and reverted via `xargs -I{} git checkout -- {}`. **Lesson: the wave-6 methodology should add a post-convergence "revert 1-add-1-del spurious diffs" step before committing. Alternative: improve the restore script to insert at the exact original line. Either way, the final diff should be limited to real semantic changes.** + +**No modules became fully Postbox-free this wave.** Each of the five containing modules still has other files importing Postbox (TelegramUI: 350 remaining, LegacyDataImport: 4, MediaPlayer: 9, AuthorizationUI: 2, DebugSettingsUI: 1). By this point most trivially-droppable imports have been drained; the remaining Postbox-importing files mostly carry real usage. **Re-run cadence lesson: yield per re-run is declining.** Wave 6 yielded 183 drops + 189 modules freed; wave 31 yielded 9 drops + 0 modules freed. Consider spacing future sweeps to every 4–6 facade waves rather than 2–3. + +**Wave 14 BUILD-dep sweep companion: 0 drops.** Ran the wave-14-style `find submodules -name BUILD | filter-by-no-source-import` check: **0 BUILD candidates**. The 191 BUILDs still listing `//submodules/Postbox` all have at least one Sources/*.swift that actually imports Postbox. One outlier (`submodules/SpotlightSupport/BUILD`) has zero source files but a non-trivial `deps = [...]` list including `//submodules/Postbox`; deliberately left alone (stale-BUILD-on-empty-module is a different class of cleanup and carries unknown side effects). + +Net: 11 files changed (9 + 2), +2 / -13 lines. Clean first-attempt verification build without `--continueOnError` (880 actions, 1354 action cache hits, 262s). + +Plan / record: (no plan doc this wave — mechanical sweep). + +## Wave 32 outcome (2026-04-24) + +`resourceStatus` residue sweep. One new facade overload (`status(id:resourceSize:)`) + 4 migrated sites across 2 consumer files. Commit `289fc908bc`. + +**Facade added** in `TelegramEngineResources.swift`: +- `status(id: EngineMediaResource.Id, resourceSize: Int64) -> Signal` wraps Postbox's `resourceStatus(MediaResourceId, resourceSize:)` overload. Body mirrors the existing `status(resource:)` facade, converting id via `MediaResourceId(id.stringRepresentation)` and mapping the result via `EngineMediaResource.FetchStatus.init`. + +**4 migrated sites (2 files):** +- `ChatListSearchContainerNode.swift:1059` — new `status(id:resourceSize:)` overload. Caller supplies `EngineMediaResource.Id(downloadResource.id)` directly (String initializer; `downloadResource.id: String`) — no raw `MediaResourceId(...)` wrap needed. Mirrors the pre-existing `EngineMediaResource.Id(downloadResource.id)` usage at line 1107. +- `ChatMessageInteractiveMediaNode.swift:1769` — existing `status(resource:)` facade (wave 3). +- `ChatMessageInteractiveMediaNode.swift:1799` — same. +- `ChatMessageInteractiveMediaNode.swift:1809` — existing `resourceRangesStatus(resource:)` facade (wave 26). + +**Local preserved deliberately.** `let postbox = context.account.postbox` at `ChatMessageInteractiveMediaNode.swift:1767` stays because line 1793 feeds `postbox` to `HLSVideoContent.minimizedHLSQualityPreloadData(postbox: Postbox, ...)` — that is a third-party-function boundary needing raw `Postbox`. Only the `resourceStatus`/`resourceRangesStatus` call sites within that scope migrate. + +**Case-pattern sharing.** `MediaResourceStatus` (raw Postbox) and `EngineMediaResource.FetchStatus` (engine wrapper) have identical case names (`.Fetching`, `.Paused`, `.Local`, `.Remote`). The inner `switch status` at 1770-1779 keeps its `MediaResourceStatus` return type annotation — input case matching works for the engine type, constructed `MediaResourceStatus` return values still compile (`MediaResourceStatus` is in scope via `import Postbox` on line 4). This is the wave-29/30 lesson in action: no enum-case edits required. + +**Inventory scope narrowing from memory's prediction.** The memory's `wave 32+ candidates` section predicted ~12 Shape-B/C sites in the residue sweep. Execution-time re-grep reclassified most of them: +- **Coupled to `accountManager.mediaBox.resourceStatus` siblings (6 sites in 3 files):** `ThemePreviewControllerNode:271+277`, `WallpaperGalleryItem:799+805+834+840`, `SettingsThemeWallpaperNode:284+285`. Each pair has an `accountManager`-sourced fallback whose return type is raw `Signal`. Migrating only the `account.postbox` branch breaks the shared sibling type at the `mapToSignal`/`combineLatest` merge point. Deferred until accountManager-side has an engine facade. +- **Shape-C init-param refactor (3 sites in 3 files):** `LegacyWebSearchGallery:248` (free function `legacyWebSearchItem(account: Account, ...)`), `NativeVideoContent:455` (init takes `postbox: Postbox`), `VerticalListContextResultsChatInputPanelItem:229` (item stores `account: Account`). Each needs an init-param change + caller threading — per-module mini-refactor, not wave-shape-G territory. +- **`approximateSynchronousValue` overload:** only call site (`SettingsThemeWallpaperNode:284`) is in the accountManager-coupled bucket above. Adding the facade now would land dead code. + +Effective wave scope: 4 sites (the uncoupled subset). Still worth committing as its own wave — closes the `resourceStatus` arc for every site where migration is currently unblocked. + +**Build validation.** Clean build (558 processes, 236s, 0 errors). No `--continueOnError` needed — first attempt green. + +**Lesson — siblings-define-scope in resource-status migrations.** When an assignment uses `A.resourceStatus(...)` in one branch and `B.resourceStatus(...)` in another (via `if`/`mapToSignal`/`combineLatest`), the branches' return types must match. If `A` has an engine facade but `B` does not (e.g., `accountManager.mediaBox` has no engine wrapper yet), neither branch is migratable in isolation — the whole group must wait. Pre-flight sibling-check for each `resourceStatus` hit: is the enclosing `statusSignal = ...` expression a single source or a multi-source merge? + +**Lesson — Shape-B/C classification requires read, not grep.** The memory's wave-32 candidate table classified sites by single-line grep ("`account.postbox.mediaBox.resourceStatus`"). That pattern matches both the fully-migratable `context.account.postbox.mediaBox.X` form (Shape-A via AccountContext) AND the `(local) account.postbox.mediaBox.X` Shape-C form (requires init-param refactor). Distinguishing requires reading 5-10 lines of context to find the `account` binding: field? local? init param? closure capture? Add this as a mandatory step in the per-site inventory for future residue waves. + +Plan / record: (no plan doc this wave — small residue sweep). + +--- + +## Wave 33 outcome (2026-04-24) + +`loadedPeerWithId` consumer sweep. 60 sites migrated across 37 consumer files. No new facades, no typealiases. Commit `16d017853a`. + +**Migration pattern** (per user's explicit direction): + +```swift +context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) +|> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } +} +``` + +This replaces `context.account.postbox.loadedPeerWithId(peerId)` while preserving signature shape. The `mapToSignal` wrapper is critical: Postbox's `loadedPeerWithId` returns `.never()` (signal never emits) when the peer is missing — it does NOT wait for loading. The engine-data equivalent `get(Peer.Peer(id:))` returns `Signal` (optional snapshot). Unwrapping with `.never()`-on-nil preserves original semantics exactly, while keeping the outer shape `Signal` non-optional so callers' closures don't have to cascade new optional handling. + +**Category distribution (per pre-flight Explore catalog, 60 sites):** + +| Category | Count | Body change | +|---|---|---| +| Cat-A (trivial) | 22 | Only EnginePeer-compatible members; type swap only. | +| Cat-B (concrete-type cast) | 25 | `peer as? TelegramUser/Group/Channel/SecretChat` → `if case let .user(user)` (etc.). | +| Cat-C (feeds Peer-typed API) | 13 | `peer._asPeer()` at call point (`makePeerInfoController`, `makeChatRecentActionsController`, `makeChatQrCodeScreen`, `FoundPeer.init`, `SendAsPeer.init`). | + +(Cat-B + Cat-C bumped slightly from Explore's catalog after in-edit reclassifications.) + +**Engine-access variations:** +- Most consumer modules use `context.engine.data.get(...)` on `AccountContext`. +- `ShareSearchContainerNode.swift` uses `context.engineData.get(...)` because `ShareControllerAccountContext` exposes `engineData: TelegramEngine.EngineData` but not a full `engine`. +- `CallStatusBarNode.swift` (has raw `account: Account` from switch case) constructs `TelegramEngine(account: account)` inline. +- `PresentationGroupCall.swift` uses `self.accountContext.engine.data` instead of the stored `self.account.postbox`. + +**TelegramCore internal sites (36) unchanged.** `Postbox.swift` (2 defs), `State/AccountViewTracker.swift`, `State/FetchChatList.swift`, `State/SynchronizePeerReadState.swift`, `Suggestions.swift`, and all `TelegramCore/Sources/TelegramEngine/` internal `_internal_*` helpers still call `postbox.loadedPeerWithId(...)` — they are the Postbox-facing layer. + +**Pre-flight efficiency.** An Explore subagent cataloged all 60 sites by category from a single prompt (one-line-per-site output). That catalog made the sweep straightforward: most files fell into identical patterns, enabling template-substitution Edits. Total context spent on discovery was small compared to doing 60 per-site full reads in the main thread. + +**Build validation.** First-pass clean build (47 actions, 70s) after sweep completion. Earlier pilot (2 sites, 20s) validated pattern before scaling to all 60. + +**Lessons:** + +- **`loadedPeerWithId` returns `.never()` on missing peer, not a pending Signal.** Old common misreading: treating it as a "wait-until-loaded" primitive. Actual Postbox source at `Postbox.swift:3925`: `if let peer = self.peerTable.get(id) { return .single(peer) } else { return .never() }`. Preserve this by wrapping `engine.data.get` in `mapToSignal` with the `.never()` fallback — don't replace with plain `|> compactMap { $0 }` (which would drop the signal entirely rather than completing immediately when peer exists). + +- **"Keep the signatures to help the typechecker" as a migration principle.** The user (2026-04-24) explicitly directed: keep call-site outer Signal signatures stable (`Signal` non-optional), even at the cost of a 6-line inline `mapToSignal` wrapper at each site. Rationale: 60 sites × optional-cascade body changes > 60 × 6-line wrapper. This is a general principle for sweeps — if the alternative is rewriting every body to handle optionals, prefer the signal-level wrapper to contain the change. + +- **Pre-flight cataloging via Explore subagent.** For sweeps with variable per-site body shapes (unlike facade-migration-with-identical-call-expression sweeps), a dispatch to `Explore` with a category-classification prompt collapses inventory cost. Explore's output is small (~60 one-line entries); avoids pulling 60 file fragments into the main thread's context. Required for wave shapes where inventory is non-uniform. + +- **Shape-C peer-fed-to-API pattern needs `_asPeer()` at call, not facade.** Because `makePeerInfoController(peer: Peer)` / `FoundPeer(peer: Peer, ...)` / `SendAsPeer(peer: Peer, ...)` / `makeChatQrCodeScreen(peer: Peer, ...)` all stay on raw `Peer` (they're AccountContext-protocol or TelegramCore struct-init APIs whose migration is its own multi-wave effort), the bridge is a single `._asPeer()` at the call. Don't try to also migrate those APIs in the sweep — blast radius too large. + +- **Engine-access varies by containing context.** Plain `context.engine.data` works for ~85% of sites; the remainder need `TelegramEngine(account: account)` construction or `engineData` protocol property. Build a per-site `context` type check into pre-flight for call-site categories where `AccountContext` isn't guaranteed. + +Plan / record: no plan doc this wave — user specified the migration pattern directly; the Explore catalog + commit message captured decisions. + +--- + +## Wave 34 outcome (2026-04-24) + +`FoundPeer.peer: Peer → EnginePeer`. Public field-type migration on the struct in `submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift`. Atomic 12-file commit `fdd5b93998`. ~135 insertions / ~134 deletions. + +**Migration shape.** The field-type change is necessarily atomic (half-migrated FoundPeer doesn't compile across consumers), so all edits land in one commit. `_internal_searchPeers` keeps `import Postbox` (still calls `postbox.transaction` etc.) and wraps raw peer values with `EnginePeer(peer)` at the FoundPeer constructor sites. `==` body changes from `lhs.peer.isEqual(rhs.peer)` to `lhs.peer == rhs.peer`. + +**Final scope (vs planned ~70 semantic edits → actual ~135 line insertions):** +- 5 `._asPeer()` bridge-drops at FoundPeer constructor sites (e.g., `FoundPeer(peer: peer._asPeer(), ...)` → `FoundPeer(peer: peer, ...)`) +- 22+ redundant `EnginePeer(peer.peer)` wrap drops (the field is now EnginePeer; `EnginePeer.init(_ peer: Peer)` doesn't accept an EnginePeer argument so the wrap fails to compile) +- 30+ Postbox-concrete-type downcasts (`peer.peer as? TelegramX` / `is TelegramX`) rewritten to `if case let .X(x) = peer.peer` enum-pattern form +- ~10 `._asPeer()` outflow bridges added where `peer.peer` flows into APIs that still take raw `Peer`: `ContactListPeer.peer(peer:)`, `canSendMessagesToPeer(_:)`, `EngineRenderedPeer(peer:)` legacy paths + +**Inventory undercounting — pattern.** Original Explore inventory pass missed 4 of 12 final consumer files. The grep `grep -rln "FoundPeer\b"` only catches files that name `FoundPeer` as a literal type. Files that USE `peer.peer` access on FoundPeer values without naming the type itself were invisible to that grep. The build verification pass surfaced them: + +| File | Surfaced by | Edits needed | +|---|---|---| +| `TelegramCore/Calls/GroupCalls.swift` | iter 1 | 2 internal FoundPeer constructors needed `EnginePeer(peer)` wraps | +| `ShareController/ShareSearchContainerNode.swift` | iter 2 | 4 errors: 2 C2 downcasts + 2 outflow-bridge needs | +| `ContactListUI/ContactsSearchContainerNode.swift` | iter 3 | 7 errors: nested `if !(peer is X)` rewrite + multiple downcasts/outflows | +| `PeerInfoUI/ChannelMembersSearchContainerNode.swift` | iter 4 | 6 errors across 2 near-identical loop blocks | +| `ChatListUI/ChatListSearchListPaneNode.swift` (extra site) | iter 5 | 1 missed C2 site at line 3723 (in `.globalPeer(foundPeer, …)` enum case body, far from the other ChatListUI edits) | + +5 build iterations total before clean (each iteration: edit → re-build, ~50–60s incremental). First-pass would have needed a much wider pre-flight grep — see lessons. + +**Lessons:** + +- **Inventory grep must include the access pattern, not just the type name.** For a field-type migration, ALL of: + - `(peer:` constructors + - `.peer.` reads (verify `` type is ``, not RenderedPeer/SendAsPeer/etc.) + - `.peer as?` / `.peer is` downcasts + - `(.peer)` arg passes (where `` may take the old protocol) + + Use `for x in Y` binding-tracing to determine if `` is the migrated type. The wave-34 pre-flight ran the first three but not the fourth (outflow-arg sites), and partially missed the second (because the Explore agent classified by literal `FoundPeer` token rather than by `peer.peer` semantics in context). + +- **`if !(peer is A || peer is B)` rewrite uses `switch case A, B: break / default: ...`.** When the original Postbox code uses a negated disjunction of type-checks, the cleanest enum-pattern equivalent is a `switch` with combined cases in one arm — not nested `if case`s. (Used in ChatListSearchListPaneNode:1024 and ContactsSearchContainerNode:502/544.) + +- **Inner `peer` shadowing.** Many `else if let peer = peer.peer as? TelegramChannel` Postbox patterns shadow the loop variable. The enum-pattern rewrite renames the inner binding to `channel` to avoid double-shadowing the EnginePeer outer loop var. Block-internal references to `.info` etc. then move from `peer.info` to `channel.info`. + +- **Build iteration = inventory completion.** When the inventory undercounting becomes apparent (build surfaces 5+ unexpected sites), don't abandon — iterate. Each build is fast (~50s incremental) and each error is actionable (`error: cast from EnginePeer to unrelated type X always fails` → C2 rewrite; `argument type EnginePeer does not conform to expected type Peer` → outflow bridge). The inventory grows by file, fix-then-rebuild converges in 5 iterations even when ~30% of sites were missed up front. + +- **Bridge sites generated by this wave point to next-ring migration targets.** The ~10 `._asPeer()` outflow bridges land at `ContactListPeer.peer(peer:)`, `canSendMessagesToPeer(_:)`, and `EngineRenderedPeer(peer:)` (legacy raw-Peer constructor in some paths — e.g., `EngineRenderedPeer(peer: foundPeer.peer)` doesn't need a bridge in newer EnginePeer-aware paths but does where the local var was already raw-Peer-extracted). These three signatures are the obvious wave-35+ candidates for the next ring of migration. + +**Plan / record:** `docs/superpowers/plans/2026-04-24-foundpeer-engine-peer-migration.md`. Spec: `docs/superpowers/specs/2026-04-24-foundpeer-engine-peer-migration-design.md`. + +--- + +## Wave 35 outcome (2026-04-24) + +`SendAsPeer.peer: Peer → EnginePeer`. Public field-type migration on the struct in `submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift`. Atomic 7-file commit `583c8b1f7c`. 22 insertions / 26 deletions. + +**Migration shape.** Same atomic-field-type pattern as wave 34 but scoped to a smaller consumer surface. The `_internal_*SendAsAvailablePeers` functions keep `import Postbox` and wrap raw peer values with `EnginePeer(peer)` at the 4 SendAsPeer constructor sites. Manual `==` body dropped in favor of synthesized Equatable (`EnginePeer: Equatable`, `Int32?` and `Bool` already Equatable). + +**Final scope (vs planned ~15 semantic edits → actual 22/26 line diff):** +- 3 `._asPeer()` bridge-drops at SendAsPeer constructor sites (ChatControllerLoadDisplayNode:772, ChatTextInputPanelComponent:848, StoryItemSetContainerViewSendMessage:249) +- 7 redundant `EnginePeer(peer.peer)` / `EnginePeer($0.peer)` / `EnginePeer(value.peer)` wrap drops across ChatSendAsPeerListContextItem (4 sites), ChatTextInputPanelNode (1), StoryItemSetContainerViewSendMessage (1), StoryItemSetContainerComponent (1) +- 1 `peer.peer as? TelegramChannel` downcast rewritten to `if case let .channel(channel) = peer.peer` (ChatSendAsPeerListContextItem:73) with `peer.info → channel.info` rename in the shadowed scope +- 2 `EnginePeer(channel)` wraps added where raw `TelegramChannel` is constructed into `SendAsPeer(peer: ...)` (ChatControllerLoadDisplayNode:805, 823) +- 1 signal-chain simplification: `(sendAsPeer?.peer).flatMap(EnginePeer.init)` → `sendAsPeer?.peer` at StoryItemSetContainerViewSendMessage:4080 +- 1 signal-chain simplification: `.map({ EnginePeer($0.peer) })` → `.map({ $0.peer })` at StoryItemSetContainerViewSendMessage:4081 + +**Inventory undercount = 1 site (vs wave 34's 5).** The pre-flight Explore catalog missed `StoryItemSetContainerComponent.swift:3069` (`currentPeer: EnginePeer(value.peer)` → `value.peer`). The implementer caught it during the edit phase before the build, so no iteration was needed. The wave-34 explicit pattern grep (including `.peer as?`/`is`/outflow-args/`EnginePeer(.peer)`/`._asPeer()`) dramatically reduced undercounting — 1/7 sites missed (~14%) vs wave 34's 4/12 (~33%). + +**First-pass clean build.** No errors surfaced by the Bazel build at all. 461 total actions, 196.583s elapsed, `INFO: Build completed successfully`. Contrast with wave 34's 5 build-iterations-to-converge. + +**Lessons:** + +- **Wave 34's explicit-pattern pre-flight inventory works.** For future Peer-typed-API waves, the minimum grep pattern set is: `\b` literal token, `\.\s+(as\?|is)\s+Telegram`, `EnginePeer\(\w+\.\)`, `\(\.` for known outflow APIs, and `\._asPeer\(\)` (to catch bridge-drop opportunities). Wave 35 used this full pattern set and hit ~14% undercount vs wave 34's ~33%. + +- **Smaller target + validated pattern = faster wave.** Wave 35 went from spec-commit (`72d4384af0`) to outcome-commit in a single session with one clean build, versus wave 34's multi-iteration convergence. When the wave is a replay of a just-validated pattern on a smaller surface, expect minimal iteration. + +- **Inner-peer shadowing rename works.** The wave-34 lesson about renaming `peer` → `channel` in `if case let .channel(channel) = peer.peer` applied cleanly. Single instance this wave (ChatSendAsPeerListContextItem:73) — no issues. + +- **Name collisions remain a scope hazard.** Pre-flight identified `sendAsPeers: [EnginePeer]` (LiveStreamSettingsScreen, ShareWithPeersScreen) and `availableSendAsPeers: [EnginePeer]` (ChatSendStarsScreen) as name-only collisions — different type, same identifier. Confirmed these stayed untouched and out of scope. Future Peer-typed-API waves should continue the name-collision disambiguation pass. + +- **Bridge sites generated by this wave — zero new outflow bridges.** Unlike wave 34 (which added ~10 `._asPeer()` outflow bridges pointing to `ContactListPeer.peer(peer:)` / `canSendMessagesToPeer(_:)` / `EngineRenderedPeer(peer:)` as next-ring targets), wave 35 added no outflow bridges. All consumer-side `.peer` flows either stayed as `.peer.id` accesses (PeerId unchanged) or were simplifications of existing `EnginePeer(.peer)` wraps. Net: no new next-ring targets surfaced from wave 35. + +**Plan / record:** `docs/superpowers/plans/2026-04-24-sendaspeer-engine-peer-migration.md`. Spec: `docs/superpowers/specs/2026-04-24-sendaspeer-engine-peer-migration-design.md`. + +--- + +## Wave 36 outcome (2026-04-24) + +`ContactListPeer.peer(peer: Peer, isGlobal:, participantCount:) → peer: EnginePeer`. Enum-case payload migration on the public type in `submodules/AccountContext/Sources/ContactSelectionController.swift`. Atomic 15-file commit `069a060de1`. 57 insertions / 59 deletions. + +**Migration shape.** Same atomic-payload-type pattern as wave 34/35 but wider: 15 consumer files vs wave 35's 7, vs wave 34's 12. Beyond the payload change, the cascading `ContactListPeer.indexName` return type changed from `PeerIndexNameRepresentation` to `EnginePeer.IndexName` — an unexpected discovery during plan-writing that dropped 2 additional `EnginePeer.IndexName(...)` wraps at ContactListNode:517. + +**Final scope (vs planned 8 files / ~41 semantic edits → actual 15 files / 57/59 diff):** + +- **Definition (1 file):** `AccountContext/ContactSelectionController.swift` — case payload type, indexName return type, `==` operator body (`lhsPeer.isEqual(rhsPeer)` → `lhsPeer == rhsPeer`). +- **20 `._asPeer()` outflow bridge-drops** across ContactListNode (12), ContactsSearchContainerNode (3), ContactMultiselectionController (2), ContactMultiselectionControllerNode (1), ContactSelectionControllerNode (2). `replace_all=true` on `._asPeer(), isGlobal:` was the unifying substring. +- **20+ `EnginePeer(peer)` inflow wrap-drops** at destructure sites across ContactListNode (4), ContactsController (1), ContactsSearchContainerNode (4), ContactMultiselectionController (4), ContactMultiselectionControllerNode (1), ContactSelectionController (2), PeerSelectionControllerNode (3), SharedAccountContext (2). +- **2 `EnginePeer.IndexName(...)` wrap-drops** at the sort-comparator at ContactListNode:517 (enabled by the cascading return-type change). +- **8 Postbox-concrete cast rewrites** to EnginePeer case patterns across ContactListNode:182-186/1968 (4 sites, including the 3-branch user/group/channel cast-chain), CallController:524/542 (the intermediate `let peer = EnginePeer(peer)` lines became redundant after migration), StoryItemSetContainerViewSendMessage:2041/2074, DeviceContactInfoController:1419, ChatSendAudioMessageContextPreview:89, ChatControllerOpenAttachmentMenu:557/610/1746/1788 (4 identical sites, `replace_all` on the full line). +- **2 `._asPeer()` outflow bridges ADDED** at ContactMultiselectionController:386/403 where the destructured peer flows into `peerTokenTitle(peer: Peer)` (out-of-scope callee; future-wave bridge target). + +**Inventory undercount = 7 files / ~20 sites (vs wave 35's 1 site).** Much higher miss rate than wave 35 — ~46% by file count. Root cause: the pre-flight grep for ContactListPeer destructures used literal `\(peer, _, _\)` binding; binding names varied in practice (`contact`, `lhsPeer`, `rhsPeer`, `contactPeer`, `id`). Files missed: + +1. `DeviceContactInfoController.swift:1418/1419` — `case let .peer(contact, _, _)` + `contact as? TelegramUser` +2. `CallController.swift:523/541` — `case let .peer(peer, _, _)` + redundant `let peer = EnginePeer(peer)` pattern +3. `ChatSendAudioMessageContextPreview.swift:88/89` — `case let .peer(contact, _, _)` + `contact as? TelegramUser` +4. `PeerSelectionControllerNode.swift:901-903/1590-1592` — 2 destructures with `EnginePeer(peer)` inflow wraps +5. `StoryItemSetContainerViewSendMessage.swift:2040-2041/2073-2074` — 2 `contact as? TelegramUser` casts +6. `ChatControllerOpenAttachmentMenu.swift:556-1787` — 4 `contact as? TelegramUser` casts +7. `SharedAccountContext.swift:3295-3302` — `case let .peer(peer, _, _)` + 2 `EnginePeer(peer)` inflow wraps + +**Six build iterations to converge** vs wave 35's single first-pass-clean. Iterations 1-6 surfaced errors in batches of 2-8 errors; each was a mechanical fix (drop wrap, rewrite cast, add `._asPeer()` bridge for outflow to out-of-scope `peerTokenTitle`). Final iteration (#6) clean. + +**Lessons:** + +- **Pre-flight grep must use `\(\w+, _, _\)` not `\(peer, _, _\)` for enum-payload destructures.** Swift destructure patterns bind the payload to any legal identifier; the variable name is not semantic. Future Peer-typed-enum-payload waves should use `case let \.\((\w+),` (or similar wildcard binding) and then per-destructure scan the next ~15 lines for ` as\?`/` is`/`EnginePeer\(\)` / outflow-arg patterns. + +- **"No-edit consumer" claims need stricter verification.** Wave 36's "verify-only" list included ChatSendAudioMessageContextPreview because the initial inventory found only `[ContactListPeer]` at collection level. The deeper scan missed a `case let .peer(contact, _, _)` + `contact as? TelegramUser` pattern inside the file's `update(...)` method. For future waves, "no-edit" claims should run the wildcard-binding destructure grep described above, not just a construction-site grep. + +- **Outflow-to-out-of-scope-API bridges may need addition during the wave.** ContactMultiselectionController:386/403 needed `._asPeer()` bridges added where none existed pre-migration — the pre-migration code passed raw `Peer` to `peerTokenTitle(peer: Peer)` because the destructured peer was raw Peer. Post-migration, the destructured peer is EnginePeer, so a bridge is required. Future waves with same-scope outflow to not-yet-migrated Peer-typed APIs should pre-flight expect to add bridges. + +- **Cascading computed-property return type migration** (here: `ContactListPeer.indexName` from `PeerIndexNameRepresentation` to `EnginePeer.IndexName`) is a legitimate scope expansion when the enum's properties leak Postbox-typed values. Wave 36 caught this during plan-writing, not execution — a successful plan-review win. Future waves should grep the enum's definition file for computed properties returning Postbox-defined types. + +- **Build-iteration convergence is acceptable** when the wave's surface is large and pre-flight undercount is non-trivial. The cost of 6 build iterations (~5-20 minutes each in the Telegram-iOS build) is real but manageable. The alternative — exhaustive pre-flight to achieve first-pass-clean — is more expensive in plan-writing tokens and controller wall time. For waves expected to have >5 file touches, plan should explicitly budget for 3-5 build iterations. + +- **Ratchet effect confirmed.** Wave 36 was predominantly bridge-removal (20 outflow + 20 inflow + 2 IndexName) with only 2 bridge additions. Matches the expected ratchet behavior: earlier waves 33/34/35 added bridges at Peer/EnginePeer boundaries precisely so wave 36 could drop them atomically. The 2 new bridges added (ContactMultiselectionController:386/403 → peerTokenTitle) become next-wave drop candidates once `peerTokenTitle(peer: Peer)` migrates. + +**Plan / record:** `docs/superpowers/plans/2026-04-24-contactlistpeer-engine-peer-migration.md`. Spec: `docs/superpowers/specs/2026-04-24-contactlistpeer-engine-peer-migration-design.md`. + --- ## Modules currently free of `import Postbox` (running tally) diff --git a/docs/superpowers/specs/2026-04-24-contactlistpeer-engine-peer-migration-design.md b/docs/superpowers/specs/2026-04-24-contactlistpeer-engine-peer-migration-design.md new file mode 100644 index 0000000000..9c8b8a3c6d --- /dev/null +++ b/docs/superpowers/specs/2026-04-24-contactlistpeer-engine-peer-migration-design.md @@ -0,0 +1,227 @@ +# Wave 36 — `ContactListPeer.peer` `Peer` → `EnginePeer` + +Date: 2026-04-24 +Status: approved design, awaiting plan +Wave shape: Peer-typed-API enum-case payload migration, single atomic commit (waves 34/35 pattern) + +## Goal + +Eliminate the Postbox-protocol `Peer` leak in the `ContactListPeer.peer(peer:isGlobal:participantCount:)` case payload by migrating the `peer` field from `Peer` to `EnginePeer`. Drop the outflow `._asPeer()` bridges that waves 33/34 installed at construction sites, and the inflow `EnginePeer(...)` wrappings at destructure sites. Apply wave 35's validated pre-flight pattern set (literal token + `.peer as?`/`is` + outflow-args + `EnginePeer(.peer)` + `._asPeer()`) to keep undercount below wave 35's 14%. + +## Non-goals + +- `ContactListPeerId.peer(PeerId)` (sibling enum, different payload) — unchanged; `PeerId == EnginePeer.Id` makes it already-clean. +- `canSendMessagesToPeer(_ peer: Peer, ignoreDefault: Bool) -> Bool` parameter migration — broader blast radius, deferred. +- `makePeerInfoController` / `makeChatQrCodeScreen` / `makeChatRecentActionsController` protocol-method migrations — broader blast radius, deferred. +- `openPeer(peer: Peer, ...)` / other Peer-typed APIs called from destructured bodies — if any destructured `peer` outflows into a raw-`Peer`-typed API after migration, add a `._asPeer()` bridge at that call site. Migrating those APIs is its own future wave. +- No new engine wrappers, typealiases, or facades introduced in this wave. +- No `import Postbox` drops in this wave — deferred to a follow-on unused-import sweep. + +## Type change + +```swift +// Before +public enum ContactListPeer: Equatable { + case peer(peer: Peer, isGlobal: Bool, participantCount: Int32?) + case deviceContact(DeviceContactStableId, DeviceContactBasicData) + + public var id: ContactListPeerId { … } + public var indexName: PeerIndexNameRepresentation { … } + + 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.isEqual(rhsPeer), // Postbox protocol method + 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 } + } + } +} + +// After +public enum ContactListPeer: Equatable { + case peer(peer: EnginePeer, isGlobal: Bool, participantCount: Int32?) + case deviceContact(DeviceContactStableId, DeviceContactBasicData) + + public var id: ContactListPeerId { … } // body unchanged; peer.id is EnginePeer.Id == PeerId + public var indexName: EnginePeer.IndexName { … } // return type changed — body unchanged but type flows from EnginePeer.indexName + + 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, // EnginePeer is Equatable + 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 } + } + } +} +``` + +The custom `==` is retained (rather than relying on synthesis) because `DeviceContactStableId` / `DeviceContactBasicData` conformance to Equatable is not verified here; minimising unrelated change. Only the `lhsPeer.isEqual(rhsPeer)` clause is rewritten. + +## In-scope files + +Scope based on the pre-flight Explore inventory plus a manual deep-scan pass that caught additional inflow wraps and Postbox-concrete casts the Explore agent missed. One definition file plus nine consumer files; seven of the consumer files need edits. Two (ComposeController, ChatSendAudioMessageContextPreview) have only `.id`-level accesses and should need no body change — plan verifies each during implementation. + +### Category α — Definition (`AccountContext`) + +**`submodules/AccountContext/Sources/ContactSelectionController.swift`** +- Line 62: enum case signature change `peer: Peer` → `peer: EnginePeer`. +- Line 74: computed property return type change `PeerIndexNameRepresentation` → `EnginePeer.IndexName`. Rationale: after the payload migration, `peer.indexName` at line 77 returns `EnginePeer.IndexName` (from `EnginePeer.indexName`), not `PeerIndexNameRepresentation`. Changing the return type up rather than re-bridging via `peer._asPeer().indexName` eliminates a Postbox-typed API from AccountContext and incidentally lets two `EnginePeer.IndexName(...)` wraps at ContactListNode:517 drop. The two enum case shapes match exactly — `EnginePeer.IndexName.title(title:addressNames:)` and `EnginePeer.IndexName.personName(first:last:addressNames:phoneNumber:)` are defined at `submodules/TelegramCore/Sources/TelegramEngine/Peers/Peer.swift:145-147` with the same parameter labels and types as `PeerIndexNameRepresentation`'s cases. +- Line 77: `return peer.indexName` — body unchanged; type now flows `EnginePeer → EnginePeer.IndexName`. +- Line 79: `return .personName(first: contact.firstName, last: contact.lastName, addressNames: [], phoneNumber: "")` — body unchanged; case resolution retargets to `EnginePeer.IndexName.personName`. +- Line 86: `==` operator — rewrite `lhsPeer.isEqual(rhsPeer)` to `lhsPeer == rhsPeer`. +- Line 67: `peer.id` same-type access (EnginePeer.id returns EnginePeer.Id ≡ PeerId) — unchanged. + +### Category β — Outflow-bridge drops (the dominant pattern) + +Every site below is `.peer(peer: ._asPeer(), isGlobal: …, participantCount: …)` → `.peer(peer: , …)`, because `` is already `EnginePeer` at the call site. + +**`submodules/ContactListUI/Sources/ContactListNode.swift`** — 12 sites: 632, 690, 701, 747, 765, 1365, 1647, 1656, 1693, 1731, 1942, 1944. + +**`submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift`** — 3 sites: 494, 535, 569. + +**`submodules/TelegramUI/Sources/ContactMultiselectionController.swift`** — 2 bridged sites: 451, 459. + +**`submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift`** — 1 site: 317. + +**`submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift`** — 2 sites: 160, 230. + +Total: 20 outflow-bridge drops. + +### Category γ — Removed + +Earlier draft flagged `TelegramUI/ContactMultiselectionController.swift:379` as a raw-`Peer` construction needing `EnginePeer(peer)` promotion. Rechecked: line 379 is inside a destructure at line 347 (`case let .peer(peer, _, _) = peer`), so post-migration the inner `peer` is already `EnginePeer` and the existing `.peer(peer: peer, ...)` continues to compile without wrapping. No edit needed. + +### Category δ — Inflow-wrapping drops at destructure sites + +Every site is `EnginePeer(peer)` applied to a destructured peer that becomes `EnginePeer` directly post-migration → drop each wrap. + +- **ContactListNode.swift**: 4 wraps total. + - Line 204 wraps `peer` twice inside `.peer(peer: EnginePeer(peer), chatPeer: EnginePeer(peer))` (inside destructure at line 177). + - Line 252 wraps once inside `interaction.openDisabledPeer(EnginePeer(peer), …)` (inside destructure at line 251). + - Line 844 wraps once inside `isPeerEnabled(EnginePeer(peer))` (inside destructure at line 833). +- **ContactsController.swift**: 1 wrap — line 294 `chatLocation: .peer(EnginePeer(peer))` where `peer` is destructured at line 287. +- **ContactsSearchContainerNode.swift**: 4 wraps total. + - Line 164 `peerItem = .peer(peer: EnginePeer(peer), chatPeer: EnginePeer(peer))` (2 wraps, inside destructure at line 163). + - Line 165 `nativePeer = EnginePeer(peer)` (1 wrap, same destructure). + - Line 181 `openDisabledPeer(EnginePeer(peer), …)` (1 wrap, inside destructure at line 180). +- **TelegramUI/Sources/ContactMultiselectionController.swift**: 4 wraps total. + - Line 386 `subject: .peer(EnginePeer(peer))` (inside destructure at line 347). + - Line 403 `subject: .peer(EnginePeer(peer))` (same destructure). + - Line 481 `self.params.sendMessage?(EnginePeer(peer))` (inside destructure at line 468). + - Line 491 `self.params.openProfile?(EnginePeer(peer))` (same destructure). +- **TelegramUI/Sources/ContactMultiselectionControllerNode.swift**: 1 wrap — line 492 `EnginePeer(peer).compactDisplayTitle` (inside destructure at line 491). +- **TelegramUI/Sources/ContactSelectionController.swift**: 2 wraps total. + - Line 517 `self.sendMessage?(EnginePeer(peer))` (inside destructure at line 504). + - Line 527 `self.openProfile?(EnginePeer(peer))` (same destructure). + +Total: 16 inflow-wrap drops. + +### Category φ — Postbox-concrete cast rewrites + +Destructured `peer` post-migration is `EnginePeer`. Existing `peer as? TelegramUser`/`TelegramGroup`/`TelegramChannel` casts no longer compile; rewrite to `EnginePeer` case-pattern matches. Both sites are in `ContactListNode.swift`. + +- **ContactListNode.swift:182-186** — inside destructure at line 177. Rewrite the `if let _ = peer as? TelegramUser { … } else if let group = peer as? TelegramGroup { … } else if let channel = peer as? TelegramChannel { … }` chain to `switch peer { case .user: … case let .legacyGroup(group): … case let .channel(channel): … default: break }`, or equivalently to the `if case .user = peer / if case let .legacyGroup(group) = peer / if case let .channel(channel) = peer` chain. Inner `group.participantCount`, `channel.info`, `case .group = channel.info` continue to compile unchanged because `EnginePeer.channel` / `.legacyGroup` wrap the exact same concrete types (`TelegramChannel`, `TelegramGroup`) and `.user` wraps `TelegramUser`. Note: the original `if let _ = peer as? TelegramUser` branch doesn't bind the user — rewrite keeps that (either `case .user = peer` or `if case .user = peer`). +- **ContactListNode.swift:1968** — inside destructure at line 1966. Rewrite `let user = peer as? TelegramUser` to `case let .user(user) = peer`. Inner `user.phone` continues to compile (`EnginePeer.user` wraps `TelegramUser`). + +EnginePeer enum case mapping (reference): + +| Postbox concrete | EnginePeer case | +|---|---| +| `TelegramUser` | `.user(TelegramUser)` | +| `TelegramGroup` | `.legacyGroup(TelegramGroup)` | +| `TelegramChannel` | `.channel(TelegramChannel)` | + +Lines 1802, 1818, 1820 in ContactListNode.swift also contain `peer as? TelegramChannel`/`peer is TelegramGroup` casts but these are on `peer` values sourced from `entryData.renderedPeer.peer` (raw Postbox `Peer`), not from a ContactListPeer destructure. They stay unchanged — out of wave scope. + +### Category ε′ — `ContactListPeer.indexName` return-type cascade + +Because category α changes the return type of `ContactListPeer.indexName` to `EnginePeer.IndexName`, call sites that currently wrap that return in `EnginePeer.IndexName(...)` can drop the wrap: + +- **ContactListNode.swift:517** — `let result = EnginePeer.IndexName(lhs.indexName).isLessThan(other: EnginePeer.IndexName(rhs.indexName), ordering: sortOrder)` → `let result = lhs.indexName.isLessThan(other: rhs.indexName, ordering: sortOrder)`. Two wraps drop. The `isLessThan(other:ordering:)` extension is defined on `EnginePeer.IndexName` only (see `submodules/LocalizedPeerData/Sources/PeerTitle.swift:64`), so the existing wrap idiom was required pre-migration. + +- **ContactListNode.swift:539, 590** — `switch peer.indexName` / `switch orderedPeers[i].indexName` with `case let .title(…)` and `case let .personName(…)` — continues to compile unchanged. Same case names and shapes. + +### Category ε — Same-type field access (no edit) + +Destructured peer bindings whose only uses are `.id`, `.addressName`, value equality via `.id`, etc. All of these exist on `EnginePeer` with identical semantics. + +Known sites from inventory (accept as same-type): +- **ContactSelectionController.swift**: 67, 76 — `.id`, `.indexName`. +- **ContactListNode.swift**: 121, 177, 209, 216, 251, 255, 491, 505, 519, 520, 782, 787, 827, 833, 1636, 1966 — `.id`/`.addressName`/value comparisons on `.id`. Sites 204 and 251 also appear in category δ because the same binding is used both ways in the same block. +- **ContactsSearchContainerNode.swift**: 151 — `.addressName`. +- **ContactMultiselectionController.swift**: 347, 468 — `.id`. +- **ContactMultiselectionControllerNode.swift**: 491 — `selectedPeers.first` destructure to access `.id`. +- **ContactSelectionController.swift (TelegramUI)**: 504 — context-action passthrough. +- **ComposeController.swift**: 120, 160 — `.id` for chat creation. +- **ChatSendAudioMessageContextPreview.swift**: 88 — `.contact`/name accessors. + +These need no code edits; they are listed only to record coverage. + +### Category ζ — Outflow-to-`Peer`-typed-API (bridge required) + +Any destructured `peer` (now `EnginePeer`) passed to a function that takes raw `Peer` needs `._asPeer()` appended at the call site. + +Known candidate from inventory: +- **ContactsSearchContainerNode.swift:180** — `isPeerEnabled(peer)`. Verify the parameter type at edit time. If it is `(EnginePeer) -> Bool`, no bridge needed; if `(ContactListPeer) -> Bool`, also no bridge (the destructured value is discarded for the overall `peer` value anyway). If `(Peer) -> Bool`, add `._asPeer()`. + +Plan-time step 7 verifies each category-ε site against the API it feeds into; any surprise is resolved by adding `._asPeer()` inline. + +## Out-of-scope — name collisions + +Files listed in the 20-file grep but not touched in this wave: +- **PeerInfoUI/ChannelMembersController.swift**, **PeerInfoUI/ChannelVisibilityController.swift**, **SettingsUI/…/GlobalAutoremoveScreen.swift**, **IncomingMessagePrivacyScreen.swift**, **SelectivePrivacySettingsController.swift**, **SelectivePrivacySettingsPeersController.swift**, **PresentAddMembers.swift**, **ComposeController.swift (TelegramUI)**, **OpenResolvedUrl.swift**, **ChatSendAudioMessageContextPreview.swift** — the inventory found only `ContactListPeerId.peer(…)` destructures or pass-throughs of the entire `ContactListPeer` enum value, not `ContactListPeer.peer` payload access. The payload-type migration does not affect these. + +Plan-time verification: re-grep these files for `case .peer(let peer`, `case let .peer(peer,`, and `.peer(peer:` before declaring "no edits needed". If a missed payload destructure surfaces, promote the file into scope. + +## Execution plan outline (for writing-plans) + +Single atomic commit ordering: + +1. Edit `AccountContext/ContactSelectionController.swift` — change case payload type (L62); change `indexName` property return type to `EnginePeer.IndexName` (L74); rewrite `lhsPeer.isEqual(rhsPeer)` to `lhsPeer == rhsPeer` (L86). +2. Edit `ContactListNode.swift` — drop 12 `._asPeer()` bridges (outflow); drop 4 inflow `EnginePeer(peer)` wraps (2 on L204, 1 on L252, 1 on L844); rewrite cast chain at L182-186 to EnginePeer case patterns; rewrite cast at L1968; drop 2 `EnginePeer.IndexName(...)` wraps on L517. +3. Edit `ContactsController.swift` — drop 1 inflow `EnginePeer(peer)` wrap at L294. +4. Edit `ContactsSearchContainerNode.swift` — drop 3 `._asPeer()` bridges at L494/535/569; drop 4 inflow `EnginePeer(peer)` wraps (2 on L164, 1 on L165, 1 on L181). Do NOT drop `._asPeer()` at L488/528/562 (these feed `canSendMessagesToPeer(_: Peer)` — deferred wave). +5. Edit `TelegramUI/ContactMultiselectionController.swift` — drop 2 outflow bridges at L451/459; drop 4 inflow wraps at L386/403/481/491. Do NOT edit L171/201/748 (these feed `peerTokenTitle(peer: Peer)` — deferred). +6. Edit `TelegramUI/ContactMultiselectionControllerNode.swift` — drop 1 outflow bridge at L317; drop 1 inflow wrap at L492. +7. Edit `TelegramUI/ContactSelectionController.swift` — drop 2 inflow wraps at L517/527. +8. Edit `TelegramUI/ContactSelectionControllerNode.swift` — drop 2 outflow bridges at L160/230. +9. Verify `ComposeController.swift` and `ChatSendAudioMessageContextPreview.swift` need no body edits. If build surfaces a leak, fold the fix into an additional task step. +10. Build: `source ~/.zshrc 2>/dev/null; python3 build-system/Make/Make.py --overrideXcodeVersion --cacheDir ~/telegram-bazel-cache build --configurationPath build-system/appstore-configuration.json --gitCodesigningRepository git@gitlab.com:peter-iakovlev/fastlanematch.git --gitCodesigningType development --gitCodesigningUseCurrent --buildNumber=1 --configuration=debug_sim_arm64 --continueOnError`. +11. Address undercount misses (expected ≤3 — pre-flight was thorough but file count is large) and commit once build is green. + +## Risk register + +| Risk | Mitigation | +|------|------------| +| Inventory undercount (wave 35 had 14%; trend decreasing) | Pre-flight already uses validated pattern set. `--continueOnError` on the build surfaces all misses in one pass. Expected ≤2 missed sites. | +| Destructure sites that flow a peer into a raw-`Peer`-typed API (category ζ) not caught by inventory | Build will flag the type mismatch; fix inline with `._asPeer()` at the flagged call site. Plan step 8 is the explicit verification gate. | +| `ContactListPeer` Equatable semantic regression | Replacing `lhsPeer.isEqual(rhsPeer)` (Postbox dynamic dispatch) with `lhsPeer == rhsPeer` (EnginePeer synthesized `==`) compares the same underlying concrete types (`.user(TelegramUser)`, `.channel(TelegramChannel)`, etc.) via their own Equatable conformances. Truth table preserved. | +| `ContactListPeer.indexName` return-type change cascades beyond ContactListNode:517/539/590 | Consumers of `ContactListPeer.indexName` enumerated via `grep -rn "\.indexName" submodules/ --include="*.swift"` filtered for ContactListPeer-typed receivers: only ContactListNode has such uses. No other submodule destructures or pattern-matches on this property. Build will flag any miss immediately. | +| `peer.isEqual` used elsewhere in scope files but on non-ContactListPeer bindings | Inventory confirmed ContactListNode:306 uses `!=` on a `ContactListNodeEntry.peer` binding, not `ContactListPeer.peer`. Scope boundary respected. No other `isEqual` call on a ContactListPeer-destructured binding was found. | +| Files flagged "no ContactListPeer.peer payload access" turn out to have one | Plan step 8 re-greps these files; any hit gets promoted into scope without rerunning the wave. | +| Pre-existing WIP on `ChatListFilterPresetController.swift` / `ChatListFilterPresetListController.swift` | Out of wave scope — untouched. No ContactListPeer reference expected in those files. | + +## Validation + +- Full Bazel build (`--configuration=debug_sim_arm64 --continueOnError`). +- No TelegramCore/Postbox/TelegramApi errors (scope boundary check — halt if they surface). +- Grep post-commit: `rg "ContactListPeer\.peer\(peer: .*\._asPeer" submodules/` returns empty. +- Grep post-commit: `rg "case \.peer\(peer: .*\._asPeer" submodules/` returns empty (catch shortcut constructions). +- Grep post-commit: no surviving `EnginePeer\(peer\)` in the 10 touched files where `peer` was destructured from a `ContactListPeer.peer` case (manual spot-check — automated grep too noisy). + +## Lessons to carry forward + +- Wave 35's pre-flight pattern set (literal token + `.peer as?`/`is` + outflow-args + `EnginePeer(.peer)` + `._asPeer()`) applied to this wave; record the post-commit undercount percentage to continue the calibration trend (wave 34: ~33%, wave 35: ~14%). +- This wave is dominated by **bridge removal** — 20 outflow `._asPeer()` drops + 16 inflow `EnginePeer(peer)` drops + 2 `EnginePeer.IndexName(...)` drops + 1 `.isEqual` → `==` fix + 2 Postbox-cast chain rewrites. Zero bridge additions. Updated tallies supersede earlier draft counts in this spec. Confirms the ratchet effect: earlier waves added bridges at Peer/EnginePeer boundaries precisely so future waves like this one can drop them atomically. Record the ratio (bridge drops : bridge additions) as a health metric across Peer-typed-API waves. +- Custom enum `==` operators using `Peer.isEqual(_:)` are a predictable Category-F leak in every Peer-payload migration. Future Peer-typed-API waves should grep the enum's defining module for `\.isEqual\(` specifically. +- **Computed properties on the enum that return Postbox types (e.g., `PeerIndexNameRepresentation`) are a second predictable leak** — discovered mid-spec for `ContactListPeer.indexName`. Future Peer-typed-enum waves should grep the enum's definition file for `public var` / `public func` returning any Postbox-defined type (`PeerIndexNameRepresentation`, `PeerNameIndex`, `MessageId`, etc.) before committing to the inventory — changing the return type to the Engine equivalent frequently cascades into consumer-side wrap drops (here, 2 wraps at ContactListNode:517). diff --git a/docs/superpowers/specs/2026-04-24-foundpeer-engine-peer-migration-design.md b/docs/superpowers/specs/2026-04-24-foundpeer-engine-peer-migration-design.md new file mode 100644 index 0000000000..6172953547 --- /dev/null +++ b/docs/superpowers/specs/2026-04-24-foundpeer-engine-peer-migration-design.md @@ -0,0 +1,193 @@ +# Wave 34 Design: `FoundPeer.peer: Peer → EnginePeer` + +**Date:** 2026-04-24 +**Wave:** 34 (Postbox → TelegramEngine refactor) +**Predecessor:** Wave 33 (loadedPeerWithId consumer sweep, commit `16d017853a`) + +## Goal + +Migrate the public field `FoundPeer.peer` from the Postbox `Peer` protocol to the TelegramCore `EnginePeer` enum. Drops 4 of the 5 `._asPeer()` bridges introduced by wave 33 and eliminates one Postbox-protocol leak from a `TelegramEngine.Contacts` / `TelegramEngine.Calls` return type. + +## Non-Goals + +- Migrating other Peer-typed-API surfaces (`SendAsPeer`, `makePeerInfoController`, `makeChatRecentActionsController`, `makeChatQrCodeScreen`, `FoundPeer` is the smallest probe in this class — those are separate future waves). +- Dropping `import Postbox` from `SearchPeers.swift`. The `_internal_*` functions in that file still call `postbox.transaction`, `parseTelegramGroupOrChannel`, `AccumulatedPeers`, `updatePeers`. They remain the Postbox-facing layer per project rule. +- Dropping `import Postbox` from any consumer module. None of the touched files reach zero Postbox use through this change alone. +- Auto-synthesizing `Equatable` for `FoundPeer`. Manual `==` is preserved per user decision. + +## Scope + +One atomic commit. Approximately 46 semantic edits plus type-name continuations across: + +- `submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift` (definition + `_internal_searchPeers` body) +- 7 consumer files in `submodules/`: + - `submodules/TelegramCallsUI/Sources/VideoChatScreen.swift` + - `submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift` + - `submodules/ContactListUI/Sources/ContactListNode.swift` + - `submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift` + - `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenCallActions.swift` + - `submodules/TelegramBaseController/Sources/TelegramBaseController.swift` + - `submodules/SettingsUI/Sources/Data and Storage/StorageUsageExceptionsScreen.swift` + +The remaining ~10 files identified by `grep -rln "FoundPeer\b"` (StorageUsageExceptionsScreen field-only refs aside, the file IS in the touched list above) contain only C5 type-name mentions or unrelated `.peer.peer` accesses on other types and require no edit. + +**Verification (performed 2026-04-24)** that nearby `EnginePeer(peer.peer)` patterns in other files are NOT FoundPeer access: those sites bind `peer` to `SelectivePrivacyPeer`, `SendAsPeer`, `InactiveChannel`, `RenderedChannelParticipant`, or `RenderedPeer` — all of which still expose `.peer: Peer`. They remain unchanged by this wave. + +## Changes + +### 1. `submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift` + +**Struct:** + +```swift +public struct FoundPeer: Equatable { + public let peer: EnginePeer // was: Peer + public let subscribers: Int32? + + public init(peer: EnginePeer, subscribers: Int32?) { // was: peer: Peer + self.peer = peer + self.subscribers = subscribers + } + + public static func ==(lhs: FoundPeer, rhs: FoundPeer) -> Bool { + return lhs.peer == rhs.peer && lhs.subscribers == rhs.subscribers + // was: lhs.peer.isEqual(rhs.peer) && lhs.subscribers == rhs.subscribers + } +} +``` + +**`_internal_searchPeers` body changes:** + +- All four `FoundPeer(peer: peer, subscribers: …)` constructions (lines 70, 72, 85, 87) wrap the raw `peer` value with `EnginePeer(peer)`. +- Six scope-filter expressions (2 per non-trivial scope × 3 scopes — `.channels` lines 96–109, `.groups` lines 110–128, `.privateChats` lines 129–143) rewrite to enum pattern matching: + - `as? TelegramChannel, case .broadcast = channel.info` → `if case let .channel(channel) = item.peer, case .broadcast = channel.info` + - `as? TelegramChannel, case .group = channel.info` plus `else if item.peer is TelegramGroup` → `if case let .channel(channel) = item.peer, case .group = channel.info` plus `else if case .legacyGroup = item.peer` + - `if item.peer is TelegramUser` → `if case .user = item.peer` + +Filter behavior is preserved exactly; only the destructuring form changes. + +### 2. Consumer-side edits (by category) + +Inventory was performed on 2026-04-24 via Explore agent against the 10 files identified by `grep -rln "FoundPeer\b" submodules/ Telegram/`. An additional 3 files surfaced (`ShareControllerNode.swift`, `SharePeersContainerNode.swift`, `PeerSelectionControllerNode.swift`, `ContactSelectionControllerNode.swift`, `ChatListNode.swift`) — most are C5 type-name mentions or false positives in field names that don't reference the type. + +**C1 — peer-protocol method reads (~28 sites): no edit required.** +`peer.peer.id`, `peer.peer.displayTitle`, `peer.peer.namespace`, `peer.peer.debugDisplayTitle`, `peer.peer.smallProfileImage` — all available on `EnginePeer` with the same signatures. + +**C5 — type-signature mentions (~60 sites): no edit required.** +`[FoundPeer]`, `Signal<([FoundPeer], [FoundPeer]), NoError>`, `Atomic<([FoundPeer], [FoundPeer])?>`, `case globalPeer(FoundPeer, …)`, etc. The type continues to compile under the new field. + +**C2 — downcast rewrites (30 sites).** + +EnginePeer is an enum, so `peer.peer as? TelegramX` / `peer.peer is TelegramX` patterns must rewrite to `if case .X = peer.peer` (or `if case let .X(x) = peer.peer` when the bound value is reused). Case mapping: + +- `TelegramUser` → `.user` +- `TelegramSecretChat` → `.secretChat` +- `TelegramGroup` → `.legacyGroup` +- `TelegramChannel` → `.channel` + +| File | Line | Current pattern | After (representative) | +|---|---|---|---| +| `TelegramCallsUI/VideoChatScreenMoreMenu.swift` | 628 | `peer.peer is TelegramGroup` | `if case .legacyGroup = peer.peer` | +| `TelegramCallsUI/VideoChatScreenMoreMenu.swift` | 631 | `as? TelegramChannel, case .group = peer.info` | `if case let .channel(channel) = peer.peer, case .group = channel.info` | +| `TelegramCallsUI/VideoChatScreenMoreMenu.swift` | 648 | `as? TelegramChannel, case .broadcast = peer.info` | `if case let .channel(channel) = peer.peer, case .broadcast = channel.info` | +| `ContactListUI/ContactListNode.swift` | 1501 | `if let _ = peer.peer as? TelegramChannel` | `if case .channel = peer.peer` | +| `ContactListUI/ContactListNode.swift` | 1563, 1569, 1574 | `if let user = peer.peer as? TelegramUser, user.flags.contains(.requirePremium)` | `if case let .user(user) = peer.peer, user.flags.contains(.requirePremium)` | +| `ContactListUI/ContactListNode.swift` | 1658, 1665, 1695, 1703, 1733 | `let user = peer.peer as? TelegramUser` (in if-let chains) | `if case let .user(user) = peer.peer, …` | +| `ContactListUI/ContactListNode.swift` | 1673, 1711 | `if peer.peer is TelegramGroup` (with possible `&& `) | `if case .legacyGroup = peer.peer` (with `, `) | +| `ContactListUI/ContactListNode.swift` | 1675, 1713 | `else if let channel = peer.peer as? TelegramChannel` | `else if case let .channel(channel) = peer.peer` | +| `ChatListUI/ChatListSearchListPaneNode.swift` | 1024 | `!(peer.peer is TelegramUser \|\| peer.peer is TelegramSecretChat)` | rewrite to combined enum-pattern (×2 within the line) | +| `ChatListUI/ChatListSearchListPaneNode.swift` | 1029, 1030 | `if let _ = peer.peer as? TelegramGroup` / `else if let peer = peer.peer as? TelegramChannel, case .group = peer.info` | `if case .legacyGroup = peer.peer` / `else if case let .channel(channel) = peer.peer, case .group = channel.info` | +| `ChatListUI/ChatListSearchListPaneNode.swift` | 1038, 1040 | `if peer.peer is TelegramUser` / `else if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info` | `if case .user = peer.peer` / `else if case let .channel(channel) = peer.peer, case .broadcast = channel.info` | +| `ChatListUI/ChatListSearchListPaneNode.swift` | 1500, 1507 | `if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info` | `if case let .channel(channel) = peer.peer, case .broadcast = channel.info` | +| `PeerInfoScreen/PeerInfoScreenCallActions.swift` | 175, 178, 193 | (see prior lines, same pattern set) | (same) | +| `TelegramBaseController/TelegramBaseController.swift` | 243, 246, 258 | `peer.peer is TelegramGroup` / `as? TelegramChannel, case .group = peer.info` / `as? TelegramChannel, case .broadcast = peer.info` | (same enum-pattern rewrites as above) | + +Two name-shadowing notes: + +- **Inner `peer` shadowing.** Several rewrites (e.g., `else if let peer = peer.peer as? TelegramChannel`) currently shadow the loop variable with a new `peer` of type `TelegramChannel`. After rewrite these become `else if case let .channel(channel) = peer.peer` — the binding name moves from `peer` to `channel` to avoid further shadowing of the EnginePeer loop variable. Adjust subsequent body references inside the if-let scope (they currently say `peer.info` referring to `TelegramChannel.info`; they become `channel.info`). Spot-check each rewrite within its block. +- **`channel.info` references.** When a downcast block uses the bound `peer` for `.info` access (e.g., line 178: `peer.info`), update those references to use the new binding name (`channel.info`). Block-internal-only — no cascade. + +Plus 6 filter sites inside `SearchPeers.swift` `_internal_searchPeers` body (already counted under §1). + +**C4 — constructor edits (6 sites):** + +Bridge-drop sites — wave-33 added `._asPeer()` because the value was already `EnginePeer`; with this wave the field accepts EnginePeer directly: + +| File | Line | Current | After | +|---|---|---|---| +| `TelegramCallsUI/VideoChatScreen.swift` | 1833 | `FoundPeer(peer: peer._asPeer(), subscribers: nil)` | `FoundPeer(peer: peer, subscribers: nil)` | +| `ContactListUI/ContactListNode.swift` | 1485 | `FoundPeer(peer: mainPeer._asPeer(), subscribers: nil)` | `FoundPeer(peer: mainPeer, subscribers: nil)` | +| `ContactListUI/ContactListNode.swift` | 1517 | `FoundPeer(peer: $0._asPeer(), subscribers: nil)` (inside `peers.map { … }`) | `FoundPeer(peer: $0, subscribers: nil)` | +| `TelegramBaseController/TelegramBaseController.swift` | 208 | `FoundPeer(peer: peer._asPeer(), subscribers: nil)` | `FoundPeer(peer: peer, subscribers: nil)` | +| `PeerInfoScreen/PeerInfoScreenCallActions.swift` | 156 | `FoundPeer(peer: peer._asPeer(), subscribers: nil)` | `FoundPeer(peer: peer, subscribers: nil)` | +| `PeerInfoScreen/PeerInfoScreenCallActions.swift` | 265 | `FoundPeer(peer: peer._asPeer(), subscribers: nil)` | `FoundPeer(peer: peer, subscribers: nil)` | + +Wrap-needed sites — value at the call site is raw `Peer`, must be wrapped: + +| File | Line | Current | After | +|---|---|---|---| +| `ContactListUI/ContactListNode.swift` | 1506 | `mappedPeers.append(FoundPeer(peer: peer.peer, subscribers: subscribers))` | already-EnginePeer (since `peer: FoundPeer` after migration) → `mappedPeers.append(FoundPeer(peer: peer.peer, subscribers: subscribers))` — **no edit** | +| `SettingsUI/StorageUsageExceptionsScreen.swift` | 288 | `FoundPeer(peer: peer, subscribers: subscriberCount)` | `FoundPeer(peer: EnginePeer(peer), subscribers: subscriberCount)` | + +Note: ContactListNode:1506 is inside a `for peer in mappedPeers` over `[FoundPeer]`, so `peer.peer` is already `EnginePeer` after migration. No edit. Re-classified from C4-wrap-needed to no-op. + +So: 4 bridge-drop edits + 1 actual wrap (StorageUsageExceptionsScreen:288) = 5 C4 edits, not 6. + +**C3 — drop redundant `EnginePeer(peer.peer)` wrap (22 sites).** + +After migration `peer.peer` is already `EnginePeer`, and `EnginePeer.init(_ peer: Peer)` does not accept an EnginePeer argument — so each `EnginePeer(peer.peer)` wrap MUST be dropped to just `peer.peer` or the build fails. + +| File | Line | Wraps | Pattern (representative) | +|---|---|---|---| +| `SettingsUI/StorageUsageExceptionsScreen.swift` | 173 | 1 | `EnginePeer(peer.peer).displayTitle(…)` → `peer.peer.displayTitle(…)` | +| `SettingsUI/StorageUsageExceptionsScreen.swift` | 176 | 1 | `iconPeer: EnginePeer(peer.peer)` → `iconPeer: peer.peer` | +| `TelegramBaseController/TelegramBaseController.swift` | 265 | 2 | `peer: EnginePeer(peer.peer), title: EnginePeer(peer.peer).displayTitle(…)` → `peer: peer.peer, title: peer.peer.displayTitle(…)` | +| `PeerInfoScreen/PeerInfoScreenCallActions.swift` | 201 | 1 | `peerAvatarCompleteImage(… peer: EnginePeer(peer.peer), …)` → `peerAvatarCompleteImage(… peer: peer.peer, …)` | +| `PeerInfoScreen/PeerInfoScreenCallActions.swift` | 202 | 1 | `text: EnginePeer(peer.peer).displayTitle(…)` → `text: peer.peer.displayTitle(…)` | +| `PeerInfoScreen/PeerInfoScreenCallActions.swift` | 288 | 2 | `.secondLineWithValue(EnginePeer(peer.peer).displayTitle(…))` and `peerAvatarCompleteImage(… peer: EnginePeer(peer.peer), …)` | +| `ChatListUI/ChatListSearchListPaneNode.swift` | 1075 | 2 | `peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer))` | +| `ChatListUI/ChatListSearchListPaneNode.swift` | 1076 | 1 | `interaction.peerSelected(EnginePeer(peer.peer), nil, nil, nil, false)` | +| `ChatListUI/ChatListSearchListPaneNode.swift` | 1078 | 1 | `interaction.disabledPeerSelected(EnginePeer(peer.peer), nil, …)` | +| `ChatListUI/ChatListSearchListPaneNode.swift` | 1081 | 1 | `peerContextAction(EnginePeer(peer.peer), .search(nil), node, gesture, location)` | +| `ChatListUI/ChatListSearchListPaneNode.swift` | 3088 | 1 | `filteredPeer(EnginePeer(peer.peer), EnginePeer(accountPeer))` (only the FoundPeer wrap drops; the `EnginePeer(accountPeer)` wrap stays — `accountPeer` is a raw Peer) | +| `ChatListUI/ChatListSearchListPaneNode.swift` | 3096 | 1 | same pattern as 3088 | +| `ChatListUI/ChatListSearchListPaneNode.swift` | 3214 | 1 | same pattern as 3088 | +| `ChatListUI/ChatListSearchListPaneNode.swift` | 3216 | 1 | `entries.append(.localPeer(EnginePeer(peer.peer), …))` | +| `ChatListUI/ChatListSearchListPaneNode.swift` | 3241 | 1 | same pattern as 3088 | +| `TelegramCallsUI/VideoChatScreenMoreMenu.swift` | 171 | 2 | `.secondLineWithValue(EnginePeer(peer.peer).displayTitle(…))` and `peerAvatarCompleteImage(… peer: EnginePeer(peer.peer), …)` | +| `TelegramCallsUI/VideoChatScreenMoreMenu.swift` | 658 | 1 | `peerAvatarCompleteImage(… peer: EnginePeer(peer.peer), …)` | +| `TelegramCallsUI/VideoChatScreenMoreMenu.swift` | 679 | 1 | `text: EnginePeer(peer.peer).displayTitle(…)` | +| **Total** | | **22** | | + +Note: only the inner `EnginePeer(peer.peer)` is dropped. Adjacent `EnginePeer()` wraps (e.g., `EnginePeer(accountPeer)` at lines 3088/3096/3214/3241) are unrelated to this wave and remain. + +### Total semantic-edit count + +- §1 (TelegramCore): struct (3 lines) + 6 filter rewrites + 4 constructor wraps = ~13 spot edits in one file +- §2 C2: 30 consumer-site downcast rewrites +- §2 C4: 5 consumer-site constructor edits (4 bridge-drops + 1 wrap) +- §2 C3: 22 consumer-site `EnginePeer(peer.peer)` wrap drops + +**Total: ~70 semantic edits** across 1 TelegramCore file + 7 consumer files. Type-name mentions in signal/collection signatures need no edit; the type continues to compile. + +## Verification + +- **Build:** `source ~/.zshrc 2>/dev/null; python3 build-system/Make/Make.py --overrideXcodeVersion --cacheDir ~/telegram-bazel-cache build --configurationPath build-system/appstore-configuration.json --gitCodesigningRepository git@gitlab.com:peter-iakovlev/fastlanematch.git --gitCodesigningType development --gitCodesigningUseCurrent --buildNumber=1 --configuration=debug_sim_arm64 --continueOnError` +- **Expected outcome:** first-pass-clean build. Errors that surface most likely indicate (a) a missed C2 site, (b) a FoundPeer field-access I missed in the inventory, or (c) a downstream API receiving `peer.peer` that requires raw `Peer` (would need a `._asPeer()` bridge added). +- **Post-build grep validations:** + - `grep -rn "FoundPeer(peer:.*\._asPeer()" submodules/` → expect zero hits in production code (the 4 bridge-drops succeeded). + - `grep -nE "peer\.peer\s+(as\?|is)\s+Telegram" ` → expect zero hits in the 7 touched consumer files (FoundPeer-relevant downcasts all rewritten). Other unrelated `something_else.peer.peer as?` patterns may remain on `RenderedPeer` etc. + - `grep -rn "EnginePeer(peer\.peer)" submodules/ --include="*.swift" | grep -v "^submodules/TelegramCore/"` → expect zero hits in the 7 touched consumer files (other files keep their wraps because their `peer` is non-FoundPeer). + +## Risks and mitigations + +- **Misnamed enum case bindings (C2).** A wrong binding name (e.g. `if case let .channel(c) = peer.peer` then accessing `channel.info`) compiles but is a typo. *Mitigation:* the rewrites are mechanical and each table-row in §2 above shows the exact target form. Each binding is reused inside the same `if case let` clause. +- **Hidden field accesses missed by the inventory.** *Mitigation:* `--continueOnError` build catches everything in one pass. If 5+ unexpected error sites surface, abandon and re-inventory. If only 1–2 surface, fix in place. +- **Downstream APIs requiring raw `Peer`.** Some consumer code may pass `foundPeer.peer` to a function taking the `Peer` protocol. Inventory found 2 such sites already simplified (C3), but unknown sites may exist. *Mitigation:* if surfaced by build errors, bridge with `._asPeer()` at the call site (acceptable transitional pattern — these become next-wave candidates for downstream migration). +- **Equatable behavior change.** `Peer.isEqual(_:)` is the protocol's polymorphic identity test; `EnginePeer.==` is the synthesized-or-manual enum equality. *Mitigation:* `EnginePeer.==` is the canonical equality on the enum and is used throughout the engine codebase. The two should agree on identity-relevant fields (peer id, namespace), and FoundPeer equality is used in `Equatable` set/array dedup contexts where both forms produce the same answer for distinct peers. If tests existed, this would be the place to add one — they don't, so we accept the substitution. + +## Out-of-scope cleanups (for future waves) + +- The downstream `peerAvatarCompleteImage(account:peer:size:)` in `PeerInfoScreenCallActions.swift:202` accepts `EnginePeer` — no change needed there. +- Wave 33's 5th `._asPeer()` bridge (the one not at a `FoundPeer` constructor) remains. It is at a different downstream API — separate wave. +- `SendAsPeer`, `makePeerInfoController`, `makeChatRecentActionsController`, `makeChatQrCodeScreen` migrations — each is its own wave, larger blast radius. diff --git a/docs/superpowers/specs/2026-04-24-sendaspeer-engine-peer-migration-design.md b/docs/superpowers/specs/2026-04-24-sendaspeer-engine-peer-migration-design.md new file mode 100644 index 0000000000..6a89aa635c --- /dev/null +++ b/docs/superpowers/specs/2026-04-24-sendaspeer-engine-peer-migration-design.md @@ -0,0 +1,141 @@ +# Wave 35 — `SendAsPeer.peer` `Peer` → `EnginePeer` + +Date: 2026-04-24 +Status: approved design, awaiting plan +Wave shape: Peer-typed-API single atomic commit (wave 34 pattern replayed on a smaller target) + +## Goal + +Eliminate the Postbox-protocol `Peer` leak in the public `SendAsPeer` struct by migrating its `peer` field from `Peer` to `EnginePeer`. Apply wave 34's lessons — comprehensive pre-flight grep including `.peer as?`/`is` casts, outflow-arg patterns, and loop-body `.peer` accesses — to keep post-commit build iterations low. + +## Non-goals + +- `ContactListPeer.peer(peer: Peer, ...)` case-payload migration — broader blast radius, deferred. +- `canSendMessagesToPeer(_:)` parameter migration — broader blast radius, deferred. +- `makePeerInfoController` / `makeChatQrCodeScreen` / `makeChatRecentActionsController` protocol-method migrations — broader blast radius, deferred. +- `CachedSendAsPeers` cache entry — already `PeerId`-based, entirely inside TelegramCore; no change needed. +- No new engine wrappers, typealiases, or facades introduced in this wave. + +## Type change + +```swift +// Before +public struct SendAsPeer: Equatable { + public let peer: Peer // Postbox protocol + public let subscribers: Int32? + public let isPremiumRequired: Bool + public init(peer: Peer, subscribers: Int32?, isPremiumRequired: Bool) { … } + public static func ==(lhs: SendAsPeer, rhs: SendAsPeer) -> Bool { + return lhs.peer.isEqual(rhs.peer) && lhs.subscribers == rhs.subscribers && lhs.isPremiumRequired == rhs.isPremiumRequired + } +} + +// After +public struct SendAsPeer: Equatable { + public let peer: EnginePeer // TelegramCore value type + public let subscribers: Int32? + public let isPremiumRequired: Bool + public init(peer: EnginePeer, subscribers: Int32?, isPremiumRequired: Bool) { … } + // Equatable synthesized — EnginePeer is Equatable. +} +``` + +## In-scope files + +### Category α — TelegramCore (definition + internal construction) + +**`submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift`** +- Lines 7–21: struct definition. Change `peer: Peer` → `peer: EnginePeer`. Remove manual `==`; rely on synthesized Equatable. +- Line 64 (`_internal_cachedPeerSendAsAvailablePeers`): `SendAsPeer(peer: peer, …)` — wrap raw Postbox `Peer` with `EnginePeer(peer)`. +- Line 170 (`_internal_peerSendAsAvailablePeers`): same wrap. +- Line 236 (`_internal_cachedLiveStorySendAsAvailablePeers`): same wrap. +- Line 330 (`_internal_liveStorySendAsAvailablePeers`): same wrap. +- Lines 87, 90, 259, 262: `peer.peer.id` accesses inside the caching loop — `EnginePeer.id` returns `EnginePeer.Id` which is a typealias for `PeerId`; code keeps compiling. + +No other TelegramCore files reference `SendAsPeer`. + +### Category β — Pure token/init/access (no body edits expected) + +**`submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift`** +- Line 553: `public let sendAsPeers: [SendAsPeer]?` — field typed at collection level, no `.peer` access in this file. +- Lines 751–752 / 848 / 1068 / 1408: init parameter, assignment, equality comparison at `[SendAsPeer]?` level, and `updatedSendAsPeers(_:)` method. None reference the inner `.peer` field. +- Expected edits: zero. This file should remain untouched if the field-type migration is clean. + +**`submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift`** +- Out of scope: its `openSendAsPeer: (ASDisplayNode, ContextGesture?) -> Void` callback does NOT take a `SendAsPeer`; name-collision only. + +### Category γ — Cast-downstream + +**`submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift`** +- Lines 20, 26: `peers: [SendAsPeer]` field and constructor — no edit needed. +- Lines 68–82: iteration body. + - Line 70: `peer.peer.id.namespace == Namespaces.Peer.CloudUser` — unchanged (EnginePeer.Id retains `.namespace`). + - Line 73: **`if let peer = peer.peer as? TelegramChannel`** → rewrite as `if case let .channel(channelData) = peer.peer`, matching on the `EnginePeer` enum case. Downstream `channelData.info` access behaves the same; `case .broadcast = channelData.info` continues to compile because `EnginePeer.channel` wraps the same `TelegramChannel.Info` enum. +- Lines 89 / 110 / 116 / 121: `EnginePeer(peer.peer)` — drop the wrap, use `peer.peer` directly. + +### Category δ — Outflow (construction and field access) + +**`submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift`** +- Line 772: `SendAsPeer(peer: peer._asPeer(), …)` — drop `._asPeer()`; construction now takes `EnginePeer` directly. `peer` at this site is already an `EnginePeer` upstream. +- Lines 805, 823: `SendAsPeer(peer: channel, …)` where `channel` is a raw `TelegramChannel` — wrap with `EnginePeer(channel)`. +- Lines 792 / 826 / 835 / 844: `allPeers` array ops and `.peer.id` filter/find — unchanged. + +**`submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift`** +- Line 847: `SendAsPeer(peer: sendAsConfiguration.currentPeer._asPeer(), …)` — drop `._asPeer()`. `sendAsConfiguration.currentPeer` is `EnginePeer` upstream. +- Line 851: `updatedSendAsPeers([…])` — unchanged. + +**`submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift`** +- Line 1625: `EnginePeer(peer)` where `peer` is now `EnginePeer` → collapses to `peer`. +- Lines 1616 / 1620 / 1622 / 2948 / 5370: `.peer.id` comparisons, `sendAsPeers.first(where:)` — unchanged. + +**`submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift`** +- Line 249: `SendAsPeer(peer: accountPeer._asPeer(), …)` — drop `._asPeer()`. +- Line 4080: `(sendAsPeer?.peer).flatMap(EnginePeer.init)` → simplifies to `sendAsPeer?.peer` (already `EnginePeer?`). +- Line 4081: `.map({ EnginePeer($0.peer) })` → `.map({ $0.peer })`. +- Line 254 / 688 / 701 / 702 / 705 / 4050 / 4068 / 4069 / 4088 / 4089 / 4327 / 4333 / 4340 / 4356 / 4372: `.peer.id` accesses, variable bindings, optional access — unchanged. +- Line 4340: `call.sendStars(fromId: sendAsPeer?.peer.id, …)` — `EnginePeer.Id == PeerId`, unchanged. + +**`submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift`** +- Lines 3056–3072: `sendMessageContext.currentSendAsPeer` pass-through to context-menu item. Verify call-site type expectations during implementation; likely no edit needed since `ChatSendAsPeerListContextItem` keeps taking `[SendAsPeer]`. + +## Out-of-scope — name collisions (do not touch) + +- `submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/LiveStreamSettingsScreen.swift:271-272` — `screenState.sendAsPeers` is `[EnginePeer]` (see `ShareWithPeersScreen.swift:1114`). Different type, same name. +- `submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift:1515,2749,2958` — `availableSendAsPeers: [EnginePeer]` enum-case payload. Different type, same name. +- `submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift:7070`, `ShareWithPeersScreen.swift:39,57,74,817,1301,2352,3284,3453` — `initialSendAsPeerId: EnginePeer.Id?` / method names containing "SendAsPeer". PeerId parameter, not the struct. +- Callback declarations in `ChatPanelInterfaceInteraction.swift`, `AttachmentPanel.swift`, `PeerSelectionControllerNode.swift`, `ChatRecentActionsController.swift`, `PeerInfoSelectionPanelNode.swift` named `updateShowSendAsPeers` / `openSendAsPeer` — these take `(Bool)`/`(ASDisplayNode, ContextGesture?)`, not `SendAsPeer` values. + +## Execution plan outline (for writing-plans) + +Single atomic commit ordering: + +1. Edit `SendAsPeers.swift` — change field type, init parameter, drop manual `==`, wrap raw `Peer` at the 4 construction sites with `EnginePeer(peer)`. +2. Edit `ChatSendAsPeerListContextItem.swift` — rewrite line 73 cast to EnginePeer case match; drop `EnginePeer(peer.peer)` wraps at 89/110/116/121. +3. Edit `ChatControllerLoadDisplayNode.swift` — drop `._asPeer()` at 772; wrap `channel` with `EnginePeer(channel)` at 805/823. +4. Edit `ChatTextInputPanelComponent.swift` — drop `._asPeer()` at 847. +5. Edit `ChatTextInputPanelNode.swift` — collapse `EnginePeer(peer)` at 1625 to `peer`. +6. Edit `StoryItemSetContainerViewSendMessage.swift` — drop `._asPeer()` at 249; simplify flatMap at 4080; simplify map at 4081. +7. Verify `ChatPresentationInterfaceState.swift` and `StoryItemSetContainerComponent.swift` need no body edits. +8. Build: `source ~/.zshrc 2>/dev/null; python3 build-system/Make/Make.py --overrideXcodeVersion --cacheDir ~/telegram-bazel-cache build --configurationPath build-system/appstore-configuration.json --gitCodesigningRepository git@gitlab.com:peter-iakovlev/fastlanematch.git --gitCodesigningType development --gitCodesigningUseCurrent --buildNumber=1 --configuration=debug_sim_arm64 --continueOnError`. +9. Fix any files the inventory undercounted (expect scalar `.peer` accesses in closure bodies). Commit once build is green. + +## Risk register + +| Risk | Mitigation | +|------|------------| +| Inventory undercount (wave 34 lost ~30%) | Pre-flight grep already includes `.peer as?`/`is`/outflow; use `--continueOnError` on first build to surface all sites in one pass. | +| Cast at `ChatSendAsPeerListContextItem:73` doesn't round-trip | `EnginePeer.channel(TelegramChannel)` wraps the exact same concrete type; the `if case let .channel(ch)` rewrite preserves all `ch.info`/`ch.flags`/etc. semantics. | +| `SendAsPeer` Equatable synthesis regression | `EnginePeer` and `Int32?` and `Bool` are all Equatable; synthesized `==` produces the same truth table modulo replacing `Peer.isEqual` with `EnginePeer ==` (which for `.channel(a)` vs `.channel(b)` compares the underlying `TelegramChannel` via its own Equatable). No behavior change expected. | +| `StoryItemSetContainerComponent.swift:3056-3072` outflow missed | Plan step 7 verifies this during implementation; if a wrap/unwrap is needed at the context-menu boundary, add it inline. | + +## Validation + +- Full Bazel build (`--configuration=debug_sim_arm64 --continueOnError`). +- No TelegramCore/Postbox/TelegramApi errors (scope boundary check — halt if they surface). +- Grep post-commit: `rg "SendAsPeer\(peer: .*\._asPeer" submodules/` returns empty. +- Grep post-commit: `rg "EnginePeer\(.*\.peer\b" submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu` returns empty. + +## Lessons to carry forward + +- Wave 34's grep pattern (``-literal token only) undercounted ~30%. This wave's Explore inventory explicitly included `.peer as?`/`is`/outflow-helper/`EnginePeer(.peer)` / `._asPeer()` patterns. Record the post-commit file count vs. pre-commit inventory to calibrate future Peer-typed-API waves. +- Name collisions (different types, same identifier) are a recurring scoping hazard — confirmed in this wave for `sendAsPeers: [EnginePeer]` and `availableSendAsPeers: [EnginePeer]`. Future Peer-typed-API waves should include a name-collision disambiguation pass during inventory. diff --git a/submodules/AccountContext/Sources/ContactSelectionController.swift b/submodules/AccountContext/Sources/ContactSelectionController.swift index 54d5cb44ad..11d15ac6a7 100644 --- a/submodules/AccountContext/Sources/ContactSelectionController.swift +++ b/submodules/AccountContext/Sources/ContactSelectionController.swift @@ -59,9 +59,9 @@ public enum ContactListAction: Equatable { } public enum ContactListPeer: Equatable { - case peer(peer: Peer, isGlobal: Bool, participantCount: Int32?) + case peer(peer: EnginePeer, isGlobal: Bool, participantCount: Int32?) case deviceContact(DeviceContactStableId, DeviceContactBasicData) - + public var id: ContactListPeerId { switch self { case let .peer(peer, _, _): @@ -70,8 +70,8 @@ public enum ContactListPeer: Equatable { return .deviceContact(id) } } - - public var indexName: PeerIndexNameRepresentation { + + public var indexName: EnginePeer.IndexName { switch self { case let .peer(peer, _, _): return peer.indexName @@ -79,11 +79,11 @@ public enum ContactListPeer: Equatable { 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.isEqual(rhsPeer), lhsIsGlobal == rhsIsGlobal, lhsParticipantCount == rhsParticipantCount { + if case let .peer(rhsPeer, rhsIsGlobal, rhsParticipantCount) = rhs, lhsPeer == rhsPeer, lhsIsGlobal == rhsIsGlobal, lhsParticipantCount == rhsParticipantCount { return true } else { return false diff --git a/submodules/AccountContext/Sources/FetchMediaUtils.swift b/submodules/AccountContext/Sources/FetchMediaUtils.swift index d23937221a..805ae7a967 100644 --- a/submodules/AccountContext/Sources/FetchMediaUtils.swift +++ b/submodules/AccountContext/Sources/FetchMediaUtils.swift @@ -78,7 +78,8 @@ public func messageMediaFileStatus(context: AccountContext, messageId: MessageId var thumbnailStatus: Signal = .single(nil) if let videoThumbnail = file.videoThumbnails.first { - thumbnailStatus = context.account.postbox.mediaBox.resourceStatus(videoThumbnail.resource) + thumbnailStatus = context.engine.resources.status(resource: EngineMediaResource(videoThumbnail.resource)) + |> map { $0._asStatus() } |> map(Optional.init) } diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift index c28585b3de..9f9d886649 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import SwiftSignalKit import TelegramCore -import Postbox import TelegramPresentationData import PresentationDataUtils import ProgressNavigationButtonNode diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift index c422320ddf..9056e2daab 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SSignalKit import SwiftSignalKit diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 6e2cbd0c81..330cedc81e 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -736,9 +736,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.reloadFilters() } - self.storiesPostingAvailabilityDisposable = (self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + self.storiesPostingAvailabilityDisposable = (self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> map { view -> AppConfiguration in - let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue + let appConfiguration: AppConfiguration = view?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue return appConfiguration } |> distinctUntilChanged @@ -2698,11 +2698,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } if !self.processedFeaturedFilters { - let initializedFeatured = self.context.account.postbox.preferencesView(keys: [ - PreferencesKeys.chatListFiltersFeaturedState - ]) + let initializedFeatured = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.chatListFiltersFeaturedState)) |> mapToSignal { view -> Signal in - if let entry = view.values[PreferencesKeys.chatListFiltersFeaturedState]?.get(ChatListFiltersFeaturedState.self) { + if let entry = view?.get(ChatListFiltersFeaturedState.self) { return .single(!entry.filters.isEmpty && !entry.isSeen) } else { return .complete() diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift index 2d595caf56..bf072d9898 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift @@ -580,18 +580,18 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch pushControllerImpl?(controller) }) - let featuredFilters = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFiltersFeaturedState]) + let featuredFilters = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.chatListFiltersFeaturedState)) |> map { preferences -> [ChatListFeaturedFilter] in - guard let state = preferences.values[PreferencesKeys.chatListFiltersFeaturedState]?.get(ChatListFiltersFeaturedState.self) else { + guard let state = preferences?.get(ChatListFiltersFeaturedState.self) else { return [] } return state.filters } |> distinctUntilChanged - + let updatedFilterOrder = Promise<[Int32]?>(nil) - - let preferences = context.account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.chatListFilterSettings]) + + let preferences = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: ApplicationSpecificPreferencesKeys.chatListFilterSettings)) let previousDisplayTags = Atomic(value: nil) diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 99c67bfb19..1e4769d81f 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -437,16 +437,23 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo suggestedPeers = .single([]) } - let accountPeer = self.context.account.postbox.loadedPeerWithId(self.context.account.peerId) + let accountPeer = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> take(1) - + self.suggestedFiltersDisposable.set((combineLatest(suggestedPeers, self.suggestedDates.get(), self.selectedFilterPromise.get(), self.searchQuery.get(), accountPeer) |> mapToSignal { peers, dates, selectedFilter, searchQuery, accountPeer -> Signal<([EnginePeer], [(Date?, Date, String?)], ChatListSearchFilterEntryId?, String?, EnginePeer?), NoError> in if searchQuery?.isEmpty ?? true { - return .single((peers, dates, selectedFilter?.id, searchQuery, EnginePeer(accountPeer))) + return .single((peers, dates, selectedFilter?.id, searchQuery, accountPeer)) } else { return (.complete() |> delay(0.25, queue: Queue.mainQueue())) - |> then(.single((peers, dates, selectedFilter?.id, searchQuery, EnginePeer(accountPeer)))) + |> then(.single((peers, dates, selectedFilter?.id, searchQuery, accountPeer))) } } |> map { peers, dates, selectedFilter, searchQuery, accountPeer -> ([ChatListSearchFilter], Bool) in var suggestedFilters: [ChatListSearchFilter] = [] @@ -1056,7 +1063,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo if paneKey == .downloads { let isCachedValue: Signal if let downloadResource = downloadResource { - isCachedValue = self.context.account.postbox.mediaBox.resourceStatus(MediaResourceId(downloadResource.id), resourceSize: downloadResource.size) + isCachedValue = self.context.engine.resources.status(id: EngineMediaResource.Id(downloadResource.id), resourceSize: downloadResource.size) |> map { status -> Bool in switch status { case .Local: diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 4e526a205e..08e7883a41 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -1015,19 +1015,22 @@ public enum ChatListSearchEntry: Comparable, Identifiable { case let .globalPeer(peer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType, storyStats, requiresPremiumForMessaging, query): var enabled = true if filter.contains(.onlyWriteable) { - enabled = canSendMessagesToPeer(peer.peer) + enabled = canSendMessagesToPeer(peer.peer._asPeer()) if requiresPremiumForMessaging { enabled = false } } if filter.contains(.onlyPrivateChats) { - if !(peer.peer is TelegramUser || peer.peer is TelegramSecretChat) { + switch peer.peer { + case .user, .secretChat: + break + default: enabled = false } } if filter.contains(.onlyGroups) { - if let _ = peer.peer as? TelegramGroup { - } else if let peer = peer.peer as? TelegramChannel, case .group = peer.info { + if case .legacyGroup = peer.peer { + } else if case let .channel(channel) = peer.peer, case .group = channel.info { } else { enabled = false } @@ -1035,9 +1038,9 @@ public enum ChatListSearchEntry: Comparable, Identifiable { var suffixString = "" if let subscribers = peer.subscribers, subscribers != 0 { - if peer.peer is TelegramUser { + if case .user = peer.peer { suffixString = ", \(strings.Conversation_StatusBotSubscribers(subscribers))" - } else if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info { + } else if case let .channel(channel) = peer.peer, case .broadcast = channel.info { suffixString = ", \(strings.Conversation_StatusSubscribers(subscribers))" } else { suffixString = ", \(strings.Conversation_StatusMembers(subscribers))" @@ -1072,13 +1075,13 @@ public enum ChatListSearchEntry: Comparable, Identifiable { isSavedMessages = true } - return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch(isSavedMessages: isSavedMessages), peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .addressName(suffixString), badge: badge, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, searchQuery: query, isAd: false, action: { _ in - interaction.peerSelected(EnginePeer(peer.peer), nil, nil, nil, false) + return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch(isSavedMessages: isSavedMessages), peer: .peer(peer: peer.peer, chatPeer: peer.peer), status: .addressName(suffixString), badge: badge, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, searchQuery: query, isAd: false, action: { _ in + interaction.peerSelected(peer.peer, nil, nil, nil, false) }, disabledAction: { _ in - interaction.disabledPeerSelected(EnginePeer(peer.peer), nil, requiresPremiumForMessaging ? .premiumRequired : .generic) + interaction.disabledPeerSelected(peer.peer, nil, requiresPremiumForMessaging ? .premiumRequired : .generic) }, contextAction: peerContextAction.flatMap { peerContextAction in return { node, gesture, location in - peerContextAction(EnginePeer(peer.peer), .search(nil), node, gesture, location) + peerContextAction(peer.peer, .search(nil), node, gesture, location) } }, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer, storyStats: storyStats.flatMap { stats in return (stats.totalCount, stats.unseenCount, stats.hasUnseenCloseFriends, stats.hasLiveItems) @@ -1497,14 +1500,14 @@ private func filteredPeerSearchQueryResults(value: ([FoundPeer], [FoundPeer]), s case .channels: return ( value.0.filter { peer in - if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = peer.peer, case .broadcast = channel.info { return true } else { return false } }, value.1.filter { peer in - if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = peer.peer, case .broadcast = channel.info { return true } else { return false @@ -2192,7 +2195,16 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } } - let accountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) |> take(1) + let accountPeer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map { $0._asPeer() } + |> take(1) let foundLocalPeers: Signal<(peers: [EngineRenderedPeer], unread: [EnginePeer.Id: (Int32, Bool)], recentlySearchedPeerIds: Set), NoError> if case .savedMessagesChats = location { @@ -3076,7 +3088,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } } for peer in foundRemotePeers.0 { - if !existingPeerIds.contains(peer.peer.id), filteredPeer(EnginePeer(peer.peer), EnginePeer(accountPeer)) { + if !existingPeerIds.contains(peer.peer.id), filteredPeer(peer.peer, EnginePeer(accountPeer)) { existingPeerIds.insert(peer.peer.id) totalNumberOfLocalPeers += 1 } @@ -3084,7 +3096,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { var totalNumberOfGlobalPeers = 0 for peer in foundRemotePeers.1 { - if !existingPeerIds.contains(peer.peer.id), filteredPeer(EnginePeer(peer.peer), EnginePeer(accountPeer)) { + if !existingPeerIds.contains(peer.peer.id), filteredPeer(peer.peer, EnginePeer(accountPeer)) { totalNumberOfGlobalPeers += 1 } } @@ -3202,9 +3214,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { break } - if !existingPeerIds.contains(peer.peer.id), filteredPeer(EnginePeer(peer.peer), EnginePeer(accountPeer)) { + if !existingPeerIds.contains(peer.peer.id), filteredPeer(peer.peer, EnginePeer(accountPeer)) { existingPeerIds.insert(peer.peer.id) - entries.append(.localPeer(EnginePeer(peer.peer), nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType, nil, false, false)) + entries.append(.localPeer(peer.peer, nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType, nil, false, false)) index += 1 numberOfLocalPeers += 1 } @@ -3229,7 +3241,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { break } - if !existingPeerIds.contains(peer.peer.id), filteredPeer(EnginePeer(peer.peer), EnginePeer(accountPeer)) { + if !existingPeerIds.contains(peer.peer.id), filteredPeer(peer.peer, EnginePeer(accountPeer)) { existingPeerIds.insert(peer.peer.id) entries.append(.globalPeer(peer, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, globalExpandType, nil, false, finalQuery)) @@ -3708,7 +3720,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } case let .globalPeer(foundPeer, _, _, _, _, _, _, _, _, _, _): storyStatsIds.append(foundPeer.peer.id) - if let user = foundPeer.peer as? TelegramUser, user.flags.contains(.requirePremium) { + if case let .user(user) = foundPeer.peer, user.flags.contains(.requirePremium) { requiresPremiumForMessagingPeerIds.append(foundPeer.peer.id) } case let .message(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _): diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 75a63892a7..3b8c1dbcd8 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -1884,18 +1884,22 @@ public final class ChatListNode: ListViewImpl { let savedMessagesPeer: Signal if case let .peers(filter, _, _, _, _, _, _) = mode, filter.contains(.onlyWriteable), case .chatList = location, self.chatListFilter == nil { - savedMessagesPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) - |> map(Optional.init) - |> map { peer in - return peer.flatMap(EnginePeer.init) + savedMessagesPeer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } } + |> map(Optional.init) } else { savedMessagesPeer = .single(nil) } - let hideArchivedFolderByDefault = context.account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.chatArchiveSettings]) + let hideArchivedFolderByDefault = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: ApplicationSpecificPreferencesKeys.chatArchiveSettings)) |> map { view -> Bool in - let settings: ChatArchiveSettings = view.values[ApplicationSpecificPreferencesKeys.chatArchiveSettings]?.get(ChatArchiveSettings.self) ?? .default + let settings: ChatArchiveSettings = view?.get(ChatArchiveSettings.self) ?? .default return settings.isHiddenByDefault } |> distinctUntilChanged diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift index 20fda30dbf..81b7efae46 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift @@ -987,7 +987,7 @@ final class ChatSendMessageContextScreenComponent: Component { loadEffectAnimationSignal = Signal { subscriber in let fetchDisposable = freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: customEffectResourceFileReference, resource: customEffectResource).start() - let dataDisposabke = (context.account.postbox.mediaBox.resourceStatus(customEffectResource) + let dataDisposabke = (context.engine.resources.status(resource: EngineMediaResource(customEffectResource)) |> filter { status in if status == .Local { return true diff --git a/submodules/Components/ReactionImageComponent/Sources/ReactionImageComponent.swift b/submodules/Components/ReactionImageComponent/Sources/ReactionImageComponent.swift index 9c4dc38f91..5754fbb833 100644 --- a/submodules/Components/ReactionImageComponent/Sources/ReactionImageComponent.swift +++ b/submodules/Components/ReactionImageComponent/Sources/ReactionImageComponent.swift @@ -158,13 +158,13 @@ public final class ReactionImageNode: ASDisplayNode { super.init() - self.disposable = (context.account.postbox.mediaBox.resourceData(file.resource) + self.disposable = (context.engine.resources.data(resource: EngineMediaResource(file.resource)) |> deliverOnMainQueue).start(next: { [weak self] data in guard let strongSelf = self else { return } - - if data.complete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { + + if data.isComplete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { if let image = WebP.convert(fromWebP: dataValue) { strongSelf.iconNode.image = image } diff --git a/submodules/ContactListUI/Sources/ContactListNode.swift b/submodules/ContactListUI/Sources/ContactListNode.swift index 7989395ca3..69e68d1fbe 100644 --- a/submodules/ContactListUI/Sources/ContactListNode.swift +++ b/submodules/ContactListUI/Sources/ContactListNode.swift @@ -179,11 +179,11 @@ private enum ContactListNodeEntry: Comparable, Identifiable { if isGlobal, let _ = peer.addressName { status = .addressName("") } else { - if let _ = peer as? TelegramUser { + if case .user = peer { status = .presence(presence ?? EnginePeer.Presence(status: .longTimeAgo, lastActivity: 0), dateTimeFormat) - } else if let group = peer as? TelegramGroup { + } else if case let .legacyGroup(group) = peer { status = .custom(string: NSAttributedString(string: strings.Conversation_StatusMembers(Int32(group.participantCount))), multiline: false, isActive: false, icon: nil) - } else if let channel = peer as? TelegramChannel { + } else if case let .channel(channel) = peer { if case .group = channel.info { if let participantCount = participantCount, participantCount != 0 { status = .custom(string: NSAttributedString(string: strings.Conversation_StatusMembers(participantCount)), multiline: false, isActive: false, icon: nil) @@ -201,7 +201,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable { status = .none } } - itemPeer = .peer(peer: EnginePeer(peer), chatPeer: EnginePeer(peer)) + itemPeer = .peer(peer: peer, chatPeer: peer) case let .deviceContact(id, contact): status = .none itemPeer = .deviceContact(stableId: id, contact: contact) @@ -249,7 +249,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable { interaction.openPeer(peer, .generic, nil, nil) }, disabledAction: { _ in if case let .peer(peer, _, _) = peer { - interaction.openDisabledPeer(EnginePeer(peer), requiresPremiumForMessaging ? .premiumRequired : .generic) + interaction.openDisabledPeer(peer, requiresPremiumForMessaging ? .premiumRequired : .generic) } }, itemHighlighting: interaction.itemHighlighting, contextAction: itemContextAction, storyStats: nil, openStories: { peer, sourceNode in if case let .peer(peerValue, _) = peer, let peerValue { @@ -514,7 +514,7 @@ private func contactListNodeEntries( } case let .natural(options, _, _): let sortedPeers = peers.sorted(by: { lhs, rhs in - let result = EnginePeer.IndexName(lhs.indexName).isLessThan(other: EnginePeer.IndexName(rhs.indexName), ordering: sortOrder) + let result = lhs.indexName.isLessThan(other: rhs.indexName, ordering: sortOrder) if result == .orderedSame { if case let .peer(lhsPeer, _, _) = lhs, case let .peer(rhsPeer, _, _) = rhs { return lhsPeer.id < rhsPeer.id @@ -629,7 +629,7 @@ private func contactListNodeEntries( } let presence = presences[peer.id] - entries.append(.peer(index, .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, false, true, nil, false, nil)) + entries.append(.peer(index, .peer(peer: peer, isGlobal: false, participantCount: nil), presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, false, true, nil, false, nil)) index += 1 } @@ -687,7 +687,7 @@ private func contactListNodeEntries( } let presence = presences[peer.id] - entries.append(.peer(index, .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, hasActions, true, nil, false, nil)) + entries.append(.peer(index, .peer(peer: peer, isGlobal: false, participantCount: nil), presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, hasActions, true, nil, false, nil)) index += 1 } @@ -698,7 +698,7 @@ private func contactListNodeEntries( if showSelf, let accountPeer { if let peer = topPeers.first(where: { $0.id == accountPeer.id }) { let header = ChatListSearchItemHeader(type: .text(strings.Premium_Gift_ContactSelection_ThisIsYou.uppercased(), AnyHashable(10)), theme: theme, strings: strings) - entries.append(.peer(index, .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), nil, header, .none, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, false, true, nil, false, selfSubtitle)) + entries.append(.peer(index, .peer(peer: peer, isGlobal: false, participantCount: nil), nil, header, .none, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, false, true, nil, false, selfSubtitle)) existingPeerIds.insert(.peer(peer.id)) } } @@ -744,7 +744,7 @@ private func contactListNodeEntries( } let presence = presences[peer.id] - entries.append(.peer(index, .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, false, true, peersWithStories[peer.id].flatMap { + entries.append(.peer(index, .peer(peer: peer, isGlobal: false, participantCount: nil), presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, false, true, peersWithStories[peer.id].flatMap { ContactListNodeEntry.StoryData(count: $0.totalCount, unseenCount: $0.unseenCount, hasUnseenCloseFriends: $0.hasUnseenCloseFriends) }, false, nil)) @@ -762,7 +762,7 @@ private func contactListNodeEntries( let header: ListViewItemHeader? = ChatListSearchItemHeader(type: .text("HIDDEN STORIES", AnyHashable(0)), theme: theme, strings: strings) for item in storySubscriptions.items { - entries.append(.peer(index, .peer(peer: item.peer._asPeer(), isGlobal: false, participantCount: nil), nil, header, .none, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, true, ContactListNodeEntry.StoryData(count: item.storyCount, unseenCount: item.unseenCount, hasUnseenCloseFriends: item.hasUnseenCloseFriends))) + entries.append(.peer(index, .peer(peer: item.peer, isGlobal: false, participantCount: nil), nil, header, .none, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, true, ContactListNodeEntry.StoryData(count: item.storyCount, unseenCount: item.unseenCount, hasUnseenCloseFriends: item.hasUnseenCloseFriends))) index += 1 }*/ } @@ -841,7 +841,7 @@ private func contactListNodeEntries( enabled = false } - if let isPeerEnabled, !isPeerEnabled(EnginePeer(peer)) { + if let isPeerEnabled, !isPeerEnabled(peer) { enabled = false } default: @@ -1362,7 +1362,7 @@ public final class ContactListNode: ASDisplayNode { state = state.withToggledPeerId(.peer(peer.id)) } if value { - selectedPeerMap[id] = .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil) + selectedPeerMap[id] = .peer(peer: peer, isGlobal: false, participantCount: nil) } else { selectedPeerMap.removeValue(forKey: id) } @@ -1482,7 +1482,7 @@ public final class ContactListNode: ASDisplayNode { matches = isPeerEnabled(mainPeer) } if matches { - resultPeers.append(FoundPeer(peer: mainPeer._asPeer(), subscribers: nil)) + resultPeers.append(FoundPeer(peer: mainPeer, subscribers: nil)) } } } @@ -1498,7 +1498,7 @@ public final class ContactListNode: ASDisplayNode { if let maybePresence = presenceMap[peer.peer.id], let presence = maybePresence { resultPresences[peer.peer.id] = presence } - if let _ = peer.peer as? TelegramChannel { + if case .channel = peer.peer { var subscribers: Int32? if let maybeMemberCount = participantCountMap[peer.peer.id], let memberCount = maybeMemberCount { subscribers = Int32(memberCount) @@ -1514,7 +1514,7 @@ public final class ContactListNode: ASDisplayNode { } else { foundLocalContacts = context.engine.contacts.searchContacts(query: query.lowercased()) |> map { peers, presences -> ([FoundPeer], [EnginePeer.Id: EnginePeer.Presence]) in - return (peers.map({ FoundPeer(peer: $0._asPeer(), subscribers: nil) }), presences) + return (peers.map({ FoundPeer(peer: $0, subscribers: nil) }), presences) } } var foundRemoteContacts: Signal<([FoundPeer], [FoundPeer]), NoError> = .single(([], [])) @@ -1560,18 +1560,18 @@ public final class ContactListNode: ASDisplayNode { var result = Set() for peer in foundPeers.foundLocalContacts.0 { - if let user = peer.peer as? TelegramUser, user.flags.contains(.requirePremium) { + if case let .user(user) = peer.peer, user.flags.contains(.requirePremium) { result.insert(user.id) } } for peer in foundPeers.foundRemoteContacts.0 { - if let user = peer.peer as? TelegramUser, user.flags.contains(.requirePremium) { + if case let .user(user) = peer.peer, user.flags.contains(.requirePremium) { result.insert(user.id) } } for peer in foundPeers.foundRemoteContacts.1 { - if let user = peer.peer as? TelegramUser, user.flags.contains(.requirePremium) { + if case let .user(user) = peer.peer, user.flags.contains(.requirePremium) { result.insert(user.id) } } @@ -1644,7 +1644,7 @@ public final class ContactListNode: ASDisplayNode { let lowercasedQuery = query.lowercased() if presentationData.strings.DialogList_SavedMessages.lowercased().hasPrefix(lowercasedQuery) || "saved messages".hasPrefix(lowercasedQuery) { existingPeerIds.insert(accountPeer.id) - peers.append(.peer(peer: accountPeer._asPeer(), isGlobal: false, participantCount: nil)) + peers.append(.peer(peer: accountPeer, isGlobal: false, participantCount: nil)) } } @@ -1655,14 +1655,14 @@ public final class ContactListNode: ASDisplayNode { existingPeerIds.insert(peer.peer.id) peers.append(.peer(peer: peer.peer, isGlobal: false, participantCount: peer.subscribers)) if searchDeviceContacts, - let user = peer.peer as? TelegramUser, + case let .user(user) = peer.peer, let phone = user.phone { existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) } } for peer in remotePeers.0 { let matches: Bool - if let user = peer.peer as? TelegramUser { + if case let .user(user) = peer.peer { let phone = user.phone ?? "" if requirePhoneNumbers && phone.isEmpty { matches = false @@ -1670,9 +1670,9 @@ public final class ContactListNode: ASDisplayNode { matches = true } } else if searchGroups || searchChannels { - if peer.peer is TelegramGroup && searchGroups { + if case .legacyGroup = peer.peer, searchGroups { matches = true - } else if let channel = peer.peer as? TelegramChannel { + } else if case let .channel(channel) = peer.peer { if case .group = channel.info { matches = searchGroups } else { @@ -1692,7 +1692,7 @@ public final class ContactListNode: ASDisplayNode { existingPeerIds.insert(peer.peer.id) peers.append(.peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers)) if searchDeviceContacts, - let user = peer.peer as? TelegramUser, + case let .user(user) = peer.peer, let phone = user.phone { existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) } @@ -1700,7 +1700,7 @@ public final class ContactListNode: ASDisplayNode { } for peer in remotePeers.1 { let matches: Bool - if let user = peer.peer as? TelegramUser { + if case let .user(user) = peer.peer { let phone = user.phone ?? "" if requirePhoneNumbers && phone.isEmpty { matches = false @@ -1708,9 +1708,9 @@ public final class ContactListNode: ASDisplayNode { matches = true } } else if searchGroups || searchChannels { - if peer.peer is TelegramGroup { + if case .legacyGroup = peer.peer { matches = searchGroups - } else if let channel = peer.peer as? TelegramChannel { + } else if case let .channel(channel) = peer.peer { if case .group = channel.info { matches = searchGroups } else { @@ -1730,7 +1730,7 @@ public final class ContactListNode: ASDisplayNode { existingPeerIds.insert(peer.peer.id) peers.append(.peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers)) if searchDeviceContacts, - let user = peer.peer as? TelegramUser, + case let .user(user) = peer.peer, let phone = user.phone { existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) } @@ -1939,9 +1939,9 @@ public final class ContactListNode: ASDisplayNode { context.account.viewTracker.refreshCanSendMessagesForPeerIds(peerIds: Array(view.2.keys)) } - var peers = view.0.peers.map({ ContactListPeer.peer(peer: $0._asPeer(), isGlobal: false, participantCount: nil) }) + var peers = view.0.peers.map({ ContactListPeer.peer(peer: $0, isGlobal: false, participantCount: nil) }) for (peer, memberCount) in chatListPeers { - peers.append(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: memberCount)) + peers.append(.peer(peer: peer, isGlobal: false, participantCount: memberCount)) } var existingPeerIds = Set() var disabledPeerIds = Set() @@ -1965,7 +1965,7 @@ public final class ContactListNode: ASDisplayNode { switch contact { case let .peer(peer, _, _): if requirePhoneNumbers, - let user = peer as? TelegramUser { + case let .user(user) = peer { let phone = user.phone ?? "" if phone.isEmpty { return false diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index 207c6e8e76..4fed930eed 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -192,9 +192,9 @@ public class ContactsController: ViewController { }).strict() if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { - self.authorizationDisposable = (combineLatest(DeviceAccess.authorizationStatus(subject: .contacts), combineLatest(context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.permissionWarningKey(permission: .contacts)!), context.account.postbox.preferencesView(keys: [PreferencesKeys.contactsSettings]), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.contactSynchronizationSettings])) + self.authorizationDisposable = (combineLatest(DeviceAccess.authorizationStatus(subject: .contacts), combineLatest(context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.permissionWarningKey(permission: .contacts)!), context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.contactsSettings)), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.contactSynchronizationSettings])) |> map { noticeView, preferences, sharedData -> (Bool, ContactsSortOrder) in - let settings: ContactsSettings = preferences.values[PreferencesKeys.contactsSettings]?.get(ContactsSettings.self) ?? ContactsSettings.defaultSettings + let settings: ContactsSettings = preferences?.get(ContactsSettings.self) ?? ContactsSettings.defaultSettings let synchronizeDeviceContacts: Bool = settings.synchronizeContacts let contactsSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.contactSynchronizationSettings]?.get(ContactSynchronizationSettings.self) @@ -291,7 +291,7 @@ public class ContactsController: ViewController { scrollToEndIfExists = true } - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)), purposefulAction: { [weak self] in + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), purposefulAction: { [weak self] in if fromSearch { self?.deactivateSearch(animated: false) self?.switchToChatsController?() @@ -465,10 +465,17 @@ public class ContactsController: ViewController { let controller = QrCodeScanScreen(context: strongSelf.context, subject: .peer) controller.showMyCode = { [weak self, weak controller] in if let strongSelf = self { - let _ = (strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.context.account.peerId) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self, weak controller] peer in if let strongSelf = self, let controller = controller { - controller.present(strongSelf.context.sharedContext.makeChatQrCodeScreen(context: strongSelf.context, peer: peer, threadId: nil, temporary: false), in: .window(.root)) + controller.present(strongSelf.context.sharedContext.makeChatQrCodeScreen(context: strongSelf.context, peer: peer._asPeer(), threadId: nil, temporary: false), in: .window(.root)) } }) } diff --git a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift index 01e8689d63..8d89e277d5 100644 --- a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift @@ -161,8 +161,8 @@ private enum ContactListSearchEntry: Comparable, Identifiable { let peerItem: ContactsPeerItemPeer switch peer { case let .peer(peer, _, _): - peerItem = .peer(peer: EnginePeer(peer), chatPeer: EnginePeer(peer)) - nativePeer = EnginePeer(peer) + peerItem = .peer(peer: peer, chatPeer: peer) + nativePeer = peer case let .deviceContact(stableId, contact): peerItem = .deviceContact(stableId: stableId, contact: contact) } @@ -178,7 +178,7 @@ private enum ContactListSearchEntry: Comparable, Identifiable { openPeer(peer, .generic) }, disabledAction: { _ in if case let .peer(peer, _, _) = peer { - openDisabledPeer(EnginePeer(peer), requiresPremiumForMessaging ? .premiumRequired : .generic) + openDisabledPeer(peer, requiresPremiumForMessaging ? .premiumRequired : .generic) } }, contextAction: contextAction.flatMap { contextAction in return nativePeer.flatMap { nativePeer in @@ -404,12 +404,12 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo if let foundRemoteContacts = foundPeers.foundRemoteContacts { for peer in foundRemoteContacts.0 { - if let user = peer.peer as? TelegramUser, user.flags.contains(.requirePremium) { + if case let .user(user) = peer.peer, user.flags.contains(.requirePremium) { result.insert(user.id) } } for peer in foundRemoteContacts.1 { - if let user = peer.peer as? TelegramUser, user.flags.contains(.requirePremium) { + if case let .user(user) = peer.peer, user.flags.contains(.requirePremium) { result.insert(user.id) } } @@ -491,7 +491,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo enabled = false } } - entries.append(.peer(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), presence: localPeersAndPresences.1[peer.id], group: .contacts, enabled: enabled, requiresPremiumForMessaging: requiresPremiumForMessaging, displayCallIcons: displayCallIcons)) + entries.append(.peer(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer, isGlobal: false, participantCount: nil), presence: localPeersAndPresences.1[peer.id], group: .contacts, enabled: enabled, requiresPremiumForMessaging: requiresPremiumForMessaging, displayCallIcons: displayCallIcons)) if searchDeviceContacts, case let .user(user) = peer, let phone = user.phone { existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) } @@ -499,14 +499,13 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo } if let remotePeers = remotePeers { for peer in remotePeers.0 { - if !(peer.peer is TelegramUser) { - if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info, categories.contains(.channels) { - } else { - continue - } + if case .user = peer.peer { + } else if case let .channel(channel) = peer.peer, case .broadcast = channel.info, categories.contains(.channels) { + } else { + continue } - if let user = peer.peer as? TelegramUser { + if case let .user(user) = peer.peer { if requirePhoneNumbers { let phone = user.phone ?? "" if phone.isEmpty { @@ -517,59 +516,58 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo if user.botInfo != nil { continue } - } + } } - + if !existingPeerIds.contains(peer.peer.id) { existingPeerIds.insert(peer.peer.id) - + var enabled = true var requiresPremiumForMessaging = false if onlyWriteable { - enabled = canSendMessagesToPeer(peer.peer) + enabled = canSendMessagesToPeer(peer.peer._asPeer()) if let value = peerRequiresPremiumForMessaging[peer.peer.id], value { requiresPremiumForMessaging = true enabled = false } } - + entries.append(.peer(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers), presence: nil, group: .global, enabled: enabled, requiresPremiumForMessaging: requiresPremiumForMessaging, displayCallIcons: displayCallIcons)) - if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { + if searchDeviceContacts, case let .user(user) = peer.peer, let phone = user.phone { existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) } index += 1 } } for peer in remotePeers.1 { - if !(peer.peer is TelegramUser) { - if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info, categories.contains(.channels) { - } else { - continue - } + if case .user = peer.peer { + } else if case let .channel(channel) = peer.peer, case .broadcast = channel.info, categories.contains(.channels) { + } else { + continue } - - if let user = peer.peer as? TelegramUser, requirePhoneNumbers { + + if case let .user(user) = peer.peer, requirePhoneNumbers { let phone = user.phone ?? "" if phone.isEmpty { continue } } - + if !existingPeerIds.contains(peer.peer.id) { existingPeerIds.insert(peer.peer.id) - + var enabled = true var requiresPremiumForMessaging = false if onlyWriteable { - enabled = canSendMessagesToPeer(peer.peer) + enabled = canSendMessagesToPeer(peer.peer._asPeer()) if let value = peerRequiresPremiumForMessaging[peer.peer.id], value { requiresPremiumForMessaging = true enabled = false } } - + entries.append(.peer(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers), presence: nil, group: .global, enabled: enabled, requiresPremiumForMessaging: requiresPremiumForMessaging, displayCallIcons: displayCallIcons)) - if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { + if searchDeviceContacts, case let .user(user) = peer.peer, let phone = user.phone { existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) } index += 1 diff --git a/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift b/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift index cf3dfd46c0..13729fcd29 100644 --- a/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index 60d9804b22..aa97e6e2c7 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -1667,10 +1667,9 @@ public func debugController(sharedContext: SharedAccountContext, context: Accoun hasLegacyAppData = FileManager.default.fileExists(atPath: statusPath) } - let preferencesSignal: Signal + let preferencesSignal: Signal if let context = context { - preferencesSignal = context.account.postbox.preferencesView(keys: [PreferencesKeys.networkSettings]) - |> map(Optional.init) + preferencesSignal = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.networkSettings)) } else { preferencesSignal = .single(nil) } @@ -1693,7 +1692,7 @@ public func debugController(sharedContext: SharedAccountContext, context: Accoun let experimentalSettings: ExperimentalUISettings = sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings]?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings - let networkSettings: NetworkSettings? = preferences?.values[PreferencesKeys.networkSettings]?.get(NetworkSettings.self) + let networkSettings: NetworkSettings? = preferences?.get(NetworkSettings.self) var leftNavigationButton: ItemListNavigationButton? if modal { diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index 10399d446e..0162ae83fb 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -932,7 +932,7 @@ public class GalleryController: ViewController, StandalonePresentableController, var isFirstTime = true self.disposable.set(combineLatest( messageView, - self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) |> take(1), + self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> take(1), translateToLanguage |> take(1) ).start(next: { [weak self] view, preferencesView, translateToLanguage in let f: () -> Void = { @@ -940,7 +940,7 @@ public class GalleryController: ViewController, StandalonePresentableController, if let view = view { strongSelf.peerIsCopyProtected = view.peerIsCopyProtected - let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue + let appConfiguration: AppConfiguration = preferencesView?.get(AppConfiguration.self) ?? .defaultValue let configuration = GalleryConfiguration.with(appConfiguration: appConfiguration) strongSelf.configuration = configuration diff --git a/submodules/GalleryUI/Sources/Items/ChatAnimationGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatAnimationGalleryItem.swift index fd73a15a1b..e4a06bc893 100644 --- a/submodules/GalleryUI/Sources/Items/ChatAnimationGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatAnimationGalleryItem.swift @@ -93,7 +93,7 @@ final class ChatAnimationGalleryItemNode: ZoomableContentGalleryItemNode { private var disposable = MetaDisposable() private var fetchDisposable = MetaDisposable() private let statusDisposable = MetaDisposable() - private var status: MediaResourceStatus? + private var status: EngineMediaResource.FetchStatus? init(context: AccountContext, presentationData: PresentationData) { self.context = context @@ -195,7 +195,7 @@ final class ChatAnimationGalleryItemNode: ZoomableContentGalleryItemNode { } private func setupStatus(resource: MediaResource) { - self.statusDisposable.set((self.context.account.postbox.mediaBox.resourceStatus(resource) + self.statusDisposable.set((self.context.engine.resources.status(resource: EngineMediaResource(resource)) |> deliverOnMainQueue).start(next: { [weak self] status in if let strongSelf = self { let previousStatus = strongSelf.status diff --git a/submodules/GalleryUI/Sources/Items/ChatDocumentGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatDocumentGalleryItem.swift index 6ad34a65f6..85c8102112 100644 --- a/submodules/GalleryUI/Sources/Items/ChatDocumentGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatDocumentGalleryItem.swift @@ -110,7 +110,7 @@ class ChatDocumentGalleryItemNode: ZoomableContentGalleryItemNode, WKNavigationD private var fetchDisposable = MetaDisposable() private let statusDisposable = MetaDisposable() - private var status: MediaResourceStatus? + private var status: EngineMediaResource.FetchStatus? init(context: AccountContext, presentationData: PresentationData) { if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { @@ -188,7 +188,7 @@ class ChatDocumentGalleryItemNode: ZoomableContentGalleryItemNode, WKNavigationD } private func setupStatus(context: AccountContext, resource: MediaResource) { - self.statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(resource) + self.statusDisposable.set((context.engine.resources.status(resource: EngineMediaResource(resource)) |> deliverOnMainQueue).start(next: { [weak self] status in if let strongSelf = self { let previousStatus = strongSelf.status @@ -238,11 +238,11 @@ class ChatDocumentGalleryItemNode: ZoomableContentGalleryItemNode, WKNavigationD if let fileName = fileReference.media.fileName { pathExtension = (fileName as NSString).pathExtension } - let data = context.account.postbox.mediaBox.resourceData(fileReference.media.resource, pathExtension: pathExtension, option: .complete(waitUntilFetchStatus: false)) + let data = context.engine.resources.data(resource: EngineMediaResource(fileReference.media.resource), pathExtension: pathExtension) |> deliverOnMainQueue self.dataDisposable.set(data.start(next: { [weak self] data in if let strongSelf = self { - if data.complete { + if data.isComplete { if let webView = strongSelf.webView as? WKWebView { if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { let blockRules = """ diff --git a/submodules/GalleryUI/Sources/Items/ChatExternalFileGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatExternalFileGalleryItem.swift index 0b49726c75..faf906a909 100644 --- a/submodules/GalleryUI/Sources/Items/ChatExternalFileGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatExternalFileGalleryItem.swift @@ -84,7 +84,7 @@ class ChatExternalFileGalleryItemNode: GalleryItemNode { private var fetchDisposable = MetaDisposable() private let statusDisposable = MetaDisposable() - private var status: MediaResourceStatus? + private var status: EngineMediaResource.FetchStatus? init(context: AccountContext, presentationData: PresentationData) { self.containerNode = ASDisplayNode() @@ -183,7 +183,7 @@ class ChatExternalFileGalleryItemNode: GalleryItemNode { } private func setupStatus(context: AccountContext, resource: MediaResource) { - self.statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(resource) + self.statusDisposable.set((context.engine.resources.status(resource: EngineMediaResource(resource)) |> deliverOnMainQueue).start(next: { [weak self] status in if let strongSelf = self { let previousStatus = strongSelf.status diff --git a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift index 0cb2a14f9b..2da25c064e 100644 --- a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift @@ -249,7 +249,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { private let statusDisposable = MetaDisposable() private let dataDisposable = MetaDisposable() private let recognitionDisposable = MetaDisposable() - private var status: MediaResourceStatus? + private var status: EngineMediaResource.FetchStatus? private var fetchedDimensions: PixelDimensions? private let pagingEnabledPromise = ValuePromise(true) @@ -957,7 +957,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { } private func setupStatus(resource: MediaResource) { - self.statusDisposable.set((self.context.account.postbox.mediaBox.resourceStatus(resource) + self.statusDisposable.set((self.context.engine.resources.status(resource: EngineMediaResource(resource)) |> deliverOnMainQueue).start(next: { [weak self] status in if let strongSelf = self { let previousStatus = strongSelf.status diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 5419f6f58f..186db7fe7e 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -3704,7 +3704,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { allFiles.append(contentsOf: qualitySet.qualityFiles.values) let qualitySignals = allFiles.map { file -> Signal<(fileId: MediaId, isCached: Bool), NoError> in - return self.context.account.postbox.mediaBox.resourceStatus(file.media.resource) + return self.context.engine.resources.status(resource: EngineMediaResource(file.media.resource)) |> take(1) |> map { status -> (fileId: MediaId, isCached: Bool) in return (file.media.fileId, status == .Local) diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift index 1230e5abc0..ecfa845a06 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift @@ -79,16 +79,23 @@ public final class ImportStickerPackController: ViewController, StandalonePresen if case .image = self.stickerPack.type.contentType { } else { - let _ = (self.context.account.postbox.loadedPeerWithId(self.context.account.peerId) + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let strongSelf = self else { return } - + var signals: [Signal<(UUID, StickerVerificationStatus, EngineMediaResource?), NoError>] = [] for sticker in strongSelf.stickerPack.stickers { if let resource = strongSelf.controllerNode.stickerResources[sticker.uuid] { - signals.append(strongSelf.context.engine.stickers.uploadSticker(peer: EnginePeer(peer), resource: resource, thumbnail: nil, alt: sticker.emojis.first ?? "", dimensions: PixelDimensions(width: 512, height: 512), duration: nil, mimeType: sticker.mimeType) + signals.append(strongSelf.context.engine.stickers.uploadSticker(peer: peer, resource: resource, thumbnail: nil, alt: sticker.emojis.first ?? "", dimensions: PixelDimensions(width: 512, height: 512), duration: nil, mimeType: sticker.mimeType) |> map { result -> (UUID, StickerVerificationStatus, EngineMediaResource?) in switch result { case .progress: diff --git a/submodules/InstantPageUI/Sources/InstantPageImageNode.swift b/submodules/InstantPageUI/Sources/InstantPageImageNode.swift index 76fc85e0fc..6f83528578 100644 --- a/submodules/InstantPageUI/Sources/InstantPageImageNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageImageNode.swift @@ -116,10 +116,10 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { }) if interactive { - self.statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(largest.resource) |> deliverOnMainQueue).start(next: { [weak self] status in + self.statusDisposable.set((context.engine.resources.status(resource: EngineMediaResource(largest.resource)) |> deliverOnMainQueue).start(next: { [weak self] status in displayLinkDispatcher.dispatch { if let strongSelf = self { - strongSelf.fetchStatus = EngineMediaResource.FetchStatus(status) + strongSelf.fetchStatus = status strongSelf.updateFetchStatus() } } diff --git a/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift b/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift index 97a545133d..6817519046 100644 --- a/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift @@ -70,10 +70,10 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode, Galler if case let .file(file) = media.media { self.fetchedDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: userLocation, userContentType: .video, reference: AnyMediaReference.webPage(webPage: WebpageReference(webPage), media: file).resourceReference(file.resource)).start()) - self.statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(file.resource) |> deliverOnMainQueue).start(next: { [weak self] status in + self.statusDisposable.set((context.engine.resources.status(resource: EngineMediaResource(file.resource)) |> deliverOnMainQueue).start(next: { [weak self] status in displayLinkDispatcher.dispatch { if let strongSelf = self { - strongSelf.fetchStatus = EngineMediaResource.FetchStatus(status) + strongSelf.fetchStatus = status strongSelf.updateFetchStatus() } } diff --git a/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift b/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift index 79564f02cb..b4861ca025 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift @@ -624,10 +624,17 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio guard let inviteLink = invite?.link else { return } - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { peer in let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true diff --git a/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift b/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift index 9533a925ec..580562f972 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift @@ -411,13 +411,20 @@ public final class InviteLinkInviteController: ViewController { if let invite { if case let .groupOrChannel(peerId) = self.mode { - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let strongSelf = self else { return } let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true @@ -443,10 +450,17 @@ public final class InviteLinkInviteController: ViewController { return } - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true diff --git a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift index 1621dfc9f2..24b8c0660d 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift @@ -539,10 +539,17 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio }, action: { _, f in f(.dismissWithoutContent) - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { peer in let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true @@ -557,10 +564,17 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio }, action: { _, f in f(.dismissWithoutContent) - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { peer in let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true @@ -726,10 +740,17 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio }, action: { _, f in f(.default) - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { peer in let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true @@ -796,10 +817,17 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio }, action: { _, f in f(.default) - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { peer in let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true diff --git a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift index e60ff909ad..25df8e383c 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift @@ -741,13 +741,20 @@ public final class InviteLinkViewController: ViewController { }, action: { [weak self] _, f in f(.dismissWithoutContent) - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let strongSelf = self, let parentController = strongSelf.controller else { return } let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true @@ -763,10 +770,17 @@ public final class InviteLinkViewController: ViewController { }, action: { [weak self] _, f in f(.dismissWithoutContent) - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { peer in let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true @@ -823,11 +837,19 @@ public final class InviteLinkViewController: ViewController { } if case let .link(_, _, _, _, _, adminId, date, _, _, usageLimit, _, _, _) = invite { + let creatorPeerSignal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: adminId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } self.disposable = (combineLatest( self.presentationDataPromise.get(), self.importersContext.state, requestsState, - context.account.postbox.loadedPeerWithId(adminId) + creatorPeerSignal ) |> deliverOnMainQueue).start(next: { [weak self] presentationData, state, requestsState, creatorPeer in if let strongSelf = self { let usdRate = Double(configuration.usdWithdrawRate) / 1000.0 / 100.0 @@ -849,7 +871,7 @@ public final class InviteLinkViewController: ViewController { } entries.append(.creatorHeader(presentationData.theme, presentationData.strings.InviteLink_CreatedBy.uppercased())) - entries.append(.creator(presentationData.theme, presentationData.dateTimeFormat, EnginePeer(creatorPeer), date)) + entries.append(.creator(presentationData.theme, presentationData.dateTimeFormat, creatorPeer, date)) if !requestsState.importers.isEmpty || (state.isLoadingMore && requestsState.count > 0) { entries.append(.requestHeader(presentationData.theme, presentationData.strings.MemberRequests_PeopleRequested(Int32(requestsState.count)).uppercased(), "", false)) diff --git a/submodules/LegacyDataImport/Sources/LegacyPreferencesImport.swift b/submodules/LegacyDataImport/Sources/LegacyPreferencesImport.swift index d403653fb5..019bb540ac 100644 --- a/submodules/LegacyDataImport/Sources/LegacyPreferencesImport.swift +++ b/submodules/LegacyDataImport/Sources/LegacyPreferencesImport.swift @@ -3,7 +3,6 @@ import UIKit import TelegramCore import SyncCore import SwiftSignalKit -import Postbox import MtProtoKit import TelegramUIPreferences import LegacyComponents diff --git a/submodules/LocationUI/Sources/LocationViewController.swift b/submodules/LocationUI/Sources/LocationViewController.swift index df3b15b229..b36cdf05cd 100644 --- a/submodules/LocationUI/Sources/LocationViewController.swift +++ b/submodules/LocationUI/Sources/LocationViewController.swift @@ -265,17 +265,24 @@ public final class LocationViewController: ViewController { } strongSelf.controllerNode.setProximityIndicator(radius: 0) - let _ = (strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.subject.id.peerId) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.subject.id.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let strongSelf = self else { return } - + var compactDisplayTitle: String? - if let peer = peer as? TelegramUser { - compactDisplayTitle = EnginePeer(peer).compactDisplayTitle + if case .user = peer { + compactDisplayTitle = peer.compactDisplayTitle } - + let controller = LocationDistancePickerScreen(context: context, style: .default, compactDisplayTitle: compactDisplayTitle, distances: strongSelf.controllerNode.headerNode.mapNode.distancesToAllAnnotations, updated: { [weak self] distance in guard let strongSelf = self else { return @@ -370,17 +377,24 @@ public final class LocationViewController: ViewController { params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 30 * 60, proximityNotificationRadius: distance)) }) - let _ = (strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.subject.id.peerId) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.subject.id.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let strongSelf = self else { return } - + var compactDisplayTitle: String? - if let peer = peer as? TelegramUser { - compactDisplayTitle = EnginePeer(peer).compactDisplayTitle + if case .user = peer { + compactDisplayTitle = peer.compactDisplayTitle } - + var text: String let distanceString = shortStringForDistance(strings: strongSelf.presentationData.strings, distance: distance) if let compactDisplayTitle = compactDisplayTitle { @@ -407,7 +421,14 @@ public final class LocationViewController: ViewController { ) }) } else { - let _ = (context.account.postbox.loadedPeerWithId(subject.id.peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: subject.id.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { peer in let controller = ActionSheetController(presentationData: strongSelf.presentationData) var title: String @@ -415,8 +436,8 @@ public final class LocationViewController: ViewController { title = strongSelf.presentationData.strings.Map_LiveLocationExtendDescription } else { title = strongSelf.presentationData.strings.Map_LiveLocationGroupNewDescription - if let user = peer as? TelegramUser { - title = strongSelf.presentationData.strings.Map_LiveLocationPrivateNewDescription(EnginePeer(user).compactDisplayTitle).string + if case .user = peer { + title = strongSelf.presentationData.strings.Map_LiveLocationPrivateNewDescription(peer.compactDisplayTitle).string } } diff --git a/submodules/LocationUI/Sources/LocationViewControllerNode.swift b/submodules/LocationUI/Sources/LocationViewControllerNode.swift index 2dedaed1a4..04dfcfca16 100644 --- a/submodules/LocationUI/Sources/LocationViewControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationViewControllerNode.swift @@ -922,15 +922,22 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan return } - let _ = (self.context.account.postbox.loadedPeerWithId(self.subject.id.peerId) + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.subject.id.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let strongSelf = self else { return } - + var text: String = strongSelf.presentationData.strings.Location_ProximityGroupTip if peer.id.namespace == Namespaces.Peer.CloudUser { - text = strongSelf.presentationData.strings.Location_ProximityTip(EnginePeer(peer).compactDisplayTitle).string + text = strongSelf.presentationData.strings.Location_ProximityTip(peer.compactDisplayTitle).string } strongSelf.interaction.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: .plain(text: text), icon: nil, location: .point(location.offsetBy(dx: -9.0, dy: 0.0), .right), displayDuration: .custom(3.0), shouldDismissOnTouch: { _, _ in diff --git a/submodules/MediaPlayer/Sources/ChunkMediaPlayerDirectFetchSourceImpl.swift b/submodules/MediaPlayer/Sources/ChunkMediaPlayerDirectFetchSourceImpl.swift index 74fce2874b..1a5c25b737 100644 --- a/submodules/MediaPlayer/Sources/ChunkMediaPlayerDirectFetchSourceImpl.swift +++ b/submodules/MediaPlayer/Sources/ChunkMediaPlayerDirectFetchSourceImpl.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import FFMpegBinding import RangeSet diff --git a/submodules/PassportUI/Sources/SecureIdAuthController.swift b/submodules/PassportUI/Sources/SecureIdAuthController.swift index 6acb72a6af..a852eb30fb 100644 --- a/submodules/PassportUI/Sources/SecureIdAuthController.swift +++ b/submodules/PassportUI/Sources/SecureIdAuthController.swift @@ -328,12 +328,19 @@ public final class SecureIdAuthController: ViewController, StandalonePresentable guard let strongSelf = self else { return } - let _ = (strongSelf.context.account.postbox.loadedPeerWithId(mention.peerId) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: mention.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { peer in guard let strongSelf = self else { return } - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { (strongSelf.navigationController as? NavigationController)?.pushViewController(infoController) } }) diff --git a/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift b/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift index 86dd2e6d09..03e122b1b9 100644 --- a/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift @@ -313,9 +313,16 @@ public func channelBlacklistController(context: AccountContext, updatedPresentat } } } - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { channel in - guard let _ = channel as? TelegramChannel else { + guard case .channel = channel else { return } diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift index 13fd8cad95..29952eaef3 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift @@ -452,7 +452,14 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon state.revealedPeerId = nil return state } - let signal = context.account.postbox.loadedPeerWithId(memberId) + let signal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: memberId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue |> mapToSignal { peer -> Signal in let result = ValuePromise() @@ -918,27 +925,27 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon for foundPeer in foundRemotePeers.0 { let peer = foundPeer.peer - - if excludeBots, let user = peer as? TelegramUser, user.botInfo != nil { + + if excludeBots, case let .user(user) = peer, user.botInfo != nil { continue } - - if !existingPeerIds.contains(peer.id) && peer is TelegramUser { + + if !existingPeerIds.contains(peer.id), case .user = peer { existingPeerIds.insert(peer.id) - entries.append(ChannelMembersSearchEntry(index: index, content: .peer(EnginePeer(peer)), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) + entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) index += 1 } } - + for foundPeer in foundRemotePeers.1 { let peer = foundPeer.peer - if excludeBots, let user = peer as? TelegramUser, user.botInfo != nil { + if excludeBots, case let .user(user) = peer, user.botInfo != nil { continue } - - if !existingPeerIds.contains(peer.id) && peer is TelegramUser { + + if !existingPeerIds.contains(peer.id), case .user = peer { existingPeerIds.insert(peer.id) - entries.append(ChannelMembersSearchEntry(index: index, content: .peer(EnginePeer(peer)), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) + entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) index += 1 } } @@ -1160,28 +1167,28 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon for foundPeer in foundRemotePeers.0 { let peer = foundPeer.peer - - if excludeBots, let user = peer as? TelegramUser, user.botInfo != nil { + + if excludeBots, case let .user(user) = peer, user.botInfo != nil { continue } - - if !existingPeerIds.contains(peer.id) && peer is TelegramUser { + + if !existingPeerIds.contains(peer.id), case .user = peer { existingPeerIds.insert(peer.id) - entries.append(ChannelMembersSearchEntry(index: index, content: .peer(EnginePeer(peer)), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) + entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) index += 1 } } - + for foundPeer in foundRemotePeers.1 { let peer = foundPeer.peer - - if excludeBots, let user = peer as? TelegramUser, user.botInfo != nil { + + if excludeBots, case let .user(user) = peer, user.botInfo != nil { continue } - - if !existingPeerIds.contains(peer.id) && peer is TelegramUser { + + if !existingPeerIds.contains(peer.id), case .user = peer { existingPeerIds.insert(peer.id) - entries.append(ChannelMembersSearchEntry(index: index, content: .peer(EnginePeer(peer)), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) + entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) index += 1 } } diff --git a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift index d03290333d..0343da9fa6 100644 --- a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift @@ -1051,7 +1051,14 @@ public func channelPermissionsController(context: AccountContext, updatedPresent } } } - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { channel in dismissController?() presentControllerImpl?(channelBannedMemberController(context: context, peerId: peerId, memberId: peer.id, initialParticipant: participant?.participant, updated: { _ in diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index d79218d64a..afc282dcf6 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -1600,10 +1600,17 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: peerId)) |> deliverOnMainQueue).start(next: { invite in if let invite = invite { - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { peer in let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true @@ -1619,10 +1626,17 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta }, action: { _, f in f(.dismissWithoutContent) - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { peer in let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true diff --git a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift index e852712b5c..0bd450fc56 100644 --- a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift +++ b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift @@ -1416,7 +1416,7 @@ private func addContactToExisting(context: AccountContext, parentController: Vie let dataSignal: Signal<(EnginePeer?, DeviceContactStableId?), NoError> switch peer { case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + guard case let .user(contact) = contact, let phoneNumber = contact.phone else { return } dataSignal = (context.sharedContext.contactDataManager?.basicData() ?? .single([:])) diff --git a/submodules/PeersNearbyUI/Sources/PeersNearbyController.swift b/submodules/PeersNearbyUI/Sources/PeersNearbyController.swift index 28fc481656..32989c15be 100644 --- a/submodules/PeersNearbyUI/Sources/PeersNearbyController.swift +++ b/submodules/PeersNearbyUI/Sources/PeersNearbyController.swift @@ -597,11 +597,11 @@ public func peersNearbyController(context: AccountContext) -> ViewController { |> delay(1.0, queue: Queue.mainQueue()) ) - let signal = combineLatest(context.sharedContext.presentationData, dataPromise.get(), chatLocationPromise.get(), displayLoading, expandedPromise.get(), context.account.postbox.preferencesView(keys: [PreferencesKeys.peersNearby])) + let signal = combineLatest(context.sharedContext.presentationData, dataPromise.get(), chatLocationPromise.get(), displayLoading, expandedPromise.get(), context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.peersNearby))) |> deliverOnMainQueue |> map { presentationData, data, chatLocation, displayLoading, expanded, view -> (ItemListControllerState, (ItemListNodeState, Any)) in let previous = previousData.swap(data) - let state = view.values[PreferencesKeys.peersNearby]?.get(PeersNearbyState.self) ?? .default + let state = view?.get(PeersNearbyState.self) ?? .default var crossfade = false if (data?.users.isEmpty ?? true) != (previous?.users.isEmpty ?? true) { diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index 8c9cb2ab2d..ebd644eea5 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -475,8 +475,8 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { } return Signal { subscriber in let fetchDisposable = freeMediaFileInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: file)).start() - let dataDisposable = (context.account.postbox.mediaBox.resourceData(file.resource) - |> filter(\.complete) + let dataDisposable = (context.engine.resources.data(resource: EngineMediaResource(file.resource)) + |> filter(\.isComplete) |> take(1)).start(next: { data in subscriber.putNext(data.path) subscriber.putCompletion() diff --git a/submodules/SettingsUI/Sources/Data and Storage/StorageUsageExceptionsScreen.swift b/submodules/SettingsUI/Sources/Data and Storage/StorageUsageExceptionsScreen.swift index 7be3cda903..1569334394 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/StorageUsageExceptionsScreen.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/StorageUsageExceptionsScreen.swift @@ -170,10 +170,10 @@ private enum StorageUsageExceptionsEntry: ItemListNodeEntry { if peer.peer.id == arguments.context.account.peerId { title = presentationData.strings.DialogList_SavedMessages } else { - title = EnginePeer(peer.peer).displayTitle(strings: presentationData.strings, displayOrder: .firstLast) + title = peer.peer.displayTitle(strings: presentationData.strings, displayOrder: .firstLast) } - return ItemListDisclosureItem(presentationData: presentationData, icon: nil, context: arguments.context, iconPeer: EnginePeer(peer.peer), title: title, enabled: true, titleFont: .bold, label: optionText, labelStyle: .text, additionalDetailLabel: additionalDetailLabel, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: { + return ItemListDisclosureItem(presentationData: presentationData, icon: nil, context: arguments.context, iconPeer: peer.peer, title: title, enabled: true, titleFont: .bold, label: optionText, labelStyle: .text, additionalDetailLabel: additionalDetailLabel, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: { arguments.openPeerMenu(peer.peer.id, value) }, tag: StorageUsageExceptionsEntryTag.peer(peer.peer.id)) } @@ -285,7 +285,7 @@ public func storageUsageExceptionsScreen( continue } - result.append((peer: FoundPeer(peer: peer, subscribers: subscriberCount), value: value)) + result.append((peer: FoundPeer(peer: EnginePeer(peer), subscribers: subscriberCount), value: value)) } return result.sorted(by: { lhs, rhs in diff --git a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift index 4caf2e68c7..555deef539 100644 --- a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift +++ b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift @@ -791,10 +791,10 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode { self?.view.endEditing(true) } - let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications]) - + let preferences = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.globalNotifications)) + let previousEntriesHolder = Atomic<([NotificationExceptionEntry], PresentationTheme, PresentationStrings)?>(value: nil) - + self.listDisposable = (combineLatest(context.sharedContext.presentationData, statePromise.get(), preferences, context.engine.peers.notificationSoundList()) |> deliverOnMainQueue).start(next: { [weak self] presentationData, state, prefs, notificationSoundList in let entries = notificationsExceptionEntries(presentationData: presentationData, notificationSoundList: notificationSoundList, state: state) let previousEntriesAndPresentationData = previousEntriesHolder.swap((entries, presentationData.theme, presentationData.strings)) @@ -1045,10 +1045,10 @@ private final class NotificationExceptionsSearchContainerNode: SearchDisplayCont } - let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications]) - + let preferences = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.globalNotifications)) + let previousEntriesHolder = Atomic<([NotificationExceptionEntry], PresentationTheme, PresentationStrings)?>(value: nil) - + let stateQuery = stateAndPeers |> map { stateAndPeers -> String? in return stateAndPeers.1 @@ -1056,7 +1056,7 @@ private final class NotificationExceptionsSearchContainerNode: SearchDisplayCont |> distinctUntilChanged let searchSignal = stateQuery - |> mapToSignal { query -> Signal<(PresentationData, NotificationSoundList?, (NotificationExceptionState, String?), PreferencesView, [EngineRenderedPeer]), NoError> in + |> mapToSignal { query -> Signal<(PresentationData, NotificationSoundList?, (NotificationExceptionState, String?), PreferencesEntry?, [EngineRenderedPeer]), NoError> in var contactsSignal: Signal<[EngineRenderedPeer], NoError> = .single([]) if let query = query { contactsSignal = context.account.postbox.searchPeers(query: query) diff --git a/submodules/SettingsUI/Sources/Notifications/NotificationsAndSoundsController.swift b/submodules/SettingsUI/Sources/Notifications/NotificationsAndSoundsController.swift index 0109d7090e..9a634047c6 100644 --- a/submodules/SettingsUI/Sources/Notifications/NotificationsAndSoundsController.swift +++ b/submodules/SettingsUI/Sources/Notifications/NotificationsAndSoundsController.swift @@ -779,7 +779,7 @@ public func notificationsAndSoundsController(context: AccountContext, exceptions }) let sharedData = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings]) - let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications]) + let preferences = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.globalNotifications)) let exceptionsSignal = Signal.single(exceptionsList) |> then(context.engine.peers.notificationExceptionsList() |> map(Optional.init)) @@ -858,7 +858,7 @@ public func notificationsAndSoundsController(context: AccountContext, exceptions |> map { presentationData, sharedData, view, exceptions, authorizationStatus, warningSuppressed, hasMoreThanOneAccount -> (ItemListControllerState, (ItemListNodeState, Any)) in let viewSettings: GlobalNotificationSettingsSet - if let settings = view.values[PreferencesKeys.globalNotifications]?.get(GlobalNotificationSettings.self) { + if let settings = view?.get(GlobalNotificationSettings.self) { viewSettings = settings.effective } else { viewSettings = GlobalNotificationSettingsSet.defaultSettings diff --git a/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift b/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift index 984938fda7..2456dbe02e 100644 --- a/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift +++ b/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift @@ -1007,7 +1007,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor }) let sharedData = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings]) - let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications]) + let preferences = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.globalNotifications)) var automaticData: Signal<([EnginePeer], [EnginePeer.Id: EnginePeer.NotificationSettings]), NoError> = .single(([], [:])) if case .stories = category { @@ -1039,7 +1039,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor let signal = combineLatest(context.sharedContext.presentationData, context.engine.peers.notificationSoundList(), sharedData, preferences, statePromise.get(), automaticData) |> map { presentationData, notificationSoundList, sharedData, view, state, automaticData -> (ItemListControllerState, (ItemListNodeState, Any)) in let viewSettings: GlobalNotificationSettingsSet - if let settings = view.values[PreferencesKeys.globalNotifications]?.get(GlobalNotificationSettings.self) { + if let settings = view?.get(GlobalNotificationSettings.self) { viewSettings = settings.effective } else { viewSettings = GlobalNotificationSettingsSet.defaultSettings diff --git a/submodules/SettingsUI/Sources/Privacy and Security/DataPrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/DataPrivacySettingsController.swift index b1c88e665f..3a217663c8 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/DataPrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/DataPrivacySettingsController.swift @@ -546,11 +546,11 @@ public func dataPrivacyController(context: AccountContext, focusOnItemTag: DataP } |> distinctUntilChanged - let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get(), context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.secretChatLinkPreviewsKey()), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.contactSynchronizationSettings]), context.account.postbox.preferencesView(keys: [PreferencesKeys.contactsSettings]), context.engine.peers.recentPeers(), hasBotSettings) + let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get(), context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.secretChatLinkPreviewsKey()), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.contactSynchronizationSettings]), context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.contactsSettings)), context.engine.peers.recentPeers(), hasBotSettings) |> map { presentationData, state, noticeView, sharedData, preferences, recentPeers, hasBotSettings -> (ItemListControllerState, (ItemListNodeState, Any)) in let secretChatLinkPreviews = noticeView.value.flatMap({ ApplicationSpecificNotice.getSecretChatLinkPreviews($0) }) - - let settings: ContactsSettings = preferences.values[PreferencesKeys.contactsSettings]?.get(ContactsSettings.self) ?? ContactsSettings.defaultSettings + + let settings: ContactsSettings = preferences?.get(ContactsSettings.self) ?? ContactsSettings.defaultSettings let synchronizeDeviceContacts: Bool = settings.synchronizeContacts diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift index e024a7d320..a835c6f668 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift @@ -1021,12 +1021,12 @@ public func privacyAndSecurityController( let privacySignal = privacySettingsPromise.get() |> take(1) - let callsSignal = combineLatest(context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.voiceCallSettings]), context.account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration])) + let callsSignal = combineLatest(context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.voiceCallSettings]), context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.voipConfiguration))) |> take(1) |> map { sharedData, view -> (VoiceCallSettings, VoipConfiguration) in let voiceCallSettings: VoiceCallSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.voiceCallSettings]?.get(VoiceCallSettings.self) ?? .defaultSettings - let voipConfiguration = view.values[PreferencesKeys.voipConfiguration]?.get(VoipConfiguration.self) ?? .defaultValue - + let voipConfiguration = view?.get(VoipConfiguration.self) ?? .defaultValue + return (voiceCallSettings, voipConfiguration) } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsController.swift index 0730050b3b..a8cee09181 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsController.swift @@ -811,9 +811,9 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont let previousMode = Atomic(value: .sessions) - let enableQRLogin = context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + let enableQRLogin = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> map { view -> Bool in - guard let appConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) else { + guard let appConfiguration = view?.get(AppConfiguration.self) else { return false } guard let data = appConfiguration.data, let enableQR = data["qr_login_camera"] as? Bool, enableQR else { diff --git a/submodules/SettingsUI/Sources/ReactionNotificationSettingsController.swift b/submodules/SettingsUI/Sources/ReactionNotificationSettingsController.swift index 0463ea0ad6..116728780b 100644 --- a/submodules/SettingsUI/Sources/ReactionNotificationSettingsController.swift +++ b/submodules/SettingsUI/Sources/ReactionNotificationSettingsController.swift @@ -384,7 +384,7 @@ public func reactionNotificationSettingsController( } ) - let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications]) + let preferences = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.globalNotifications)) let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, @@ -394,7 +394,7 @@ public func reactionNotificationSettingsController( ) |> map { presentationData, notificationSoundList, preferencesView, state -> (ItemListControllerState, (ItemListNodeState, Any)) in let viewSettings: GlobalNotificationSettingsSet - if let settings = preferencesView.values[PreferencesKeys.globalNotifications]?.get(GlobalNotificationSettings.self) { + if let settings = preferencesView?.get(GlobalNotificationSettings.self) { viewSettings = settings.effective } else { viewSettings = GlobalNotificationSettingsSet.defaultSettings diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift index 92cf70cf2f..26f1542f22 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift @@ -2073,11 +2073,11 @@ private func privacySearchableItems(context: AccountContext, privacySettings: Ac } let callsSignal: Signal<(VoiceCallSettings, VoipConfiguration)?, NoError> if case .voiceCalls = kind { - callsSignal = combineLatest(context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.voiceCallSettings]), context.account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration])) + callsSignal = combineLatest(context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.voiceCallSettings]), context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.voipConfiguration))) |> take(1) |> map { sharedData, view -> (VoiceCallSettings, VoipConfiguration)? in let voiceCallSettings: VoiceCallSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.voiceCallSettings]?.get(VoiceCallSettings.self) ?? .defaultSettings - let voipConfiguration = view.values[PreferencesKeys.voipConfiguration]?.get(VoipConfiguration.self) ?? .defaultValue + let voipConfiguration = view?.get(VoipConfiguration.self) ?? .defaultValue return (voiceCallSettings, voipConfiguration) } } else { @@ -4317,11 +4317,11 @@ func settingsSearchableItems( return accountsAndPeers.1.count + 1 < maximumNumberOfAccounts } - let notificationSettings = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications]) + let notificationSettings = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.globalNotifications)) |> take(1) |> map { view -> GlobalNotificationSettingsSet in let viewSettings: GlobalNotificationSettingsSet - if let settings = view.values[PreferencesKeys.globalNotifications]?.get(GlobalNotificationSettings.self) { + if let settings = view?.get(GlobalNotificationSettings.self) { viewSettings = settings.effective } else { viewSettings = GlobalNotificationSettingsSet.defaultSettings diff --git a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift index 2b530933f6..d290580db0 100644 --- a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift +++ b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift @@ -888,11 +888,11 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta archivedPromise.set(.single(archivedPacks) |> then(context.engine.stickers.archivedStickerPacks() |> map(Optional.init))) quickReaction = combineLatest( context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)), - context.account.postbox.preferencesView(keys: [PreferencesKeys.reactionSettings]) + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.reactionSettings)) ) |> map { peer, preferencesView -> MessageReaction.Reaction? in let reactionSettings: ReactionSettings - if let entry = preferencesView.values[PreferencesKeys.reactionSettings], let value = entry.get(ReactionSettings.self) { + if let entry = preferencesView, let value = entry.get(ReactionSettings.self) { reactionSettings = value } else { reactionSettings = .default diff --git a/submodules/SettingsUI/Sources/UsernameSetupController.swift b/submodules/SettingsUI/Sources/UsernameSetupController.swift index c3d3b1fef0..24f5af80ab 100644 --- a/submodules/SettingsUI/Sources/UsernameSetupController.swift +++ b/submodules/SettingsUI/Sources/UsernameSetupController.swift @@ -468,10 +468,17 @@ public func usernameSetupController(context: AccountContext, mode: UsernameSetup })) } }, shareLink: { - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> take(1) |> deliverOnMainQueue).start(next: { peer in - if let user = peer as? TelegramUser, user.botInfo != nil { + if case let .user(user) = peer, user.botInfo != nil { context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://fragment.com/", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {}) } else { var currentAddressName: String = peer.addressName ?? "" diff --git a/submodules/ShareController/Sources/ShareSearchContainerNode.swift b/submodules/ShareController/Sources/ShareSearchContainerNode.swift index ee3b966d15..d25fe88bfc 100644 --- a/submodules/ShareController/Sources/ShareSearchContainerNode.swift +++ b/submodules/ShareController/Sources/ShareSearchContainerNode.swift @@ -286,7 +286,16 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { let foundItems = combineLatest(self.searchQuery.get(), self.themePromise.get()) |> mapToSignal { query, theme -> Signal<([ShareSearchPeerEntry]?, Bool), NoError> in if !query.isEmpty { - let accountPeer = context.stateManager.postbox.loadedPeerWithId(context.accountPeerId) |> take(1) + let accountPeer = context.engineData.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.accountPeerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map { $0._asPeer() } + |> take(1) let foundLocalPeers = context.stateManager.postbox.searchPeers(query: query.lowercased()) let foundRemotePeers: Signal<([FoundPeer], [FoundPeer], Bool), NoError> = .single(([], [], true)) |> then( @@ -326,12 +335,12 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { } for peer in foundPeers.foundRemotePeers.0 { - if let user = peer.peer as? TelegramUser, user.flags.contains(.requirePremium) { + if case let .user(user) = peer.peer, user.flags.contains(.requirePremium) { result.insert(user.id) } } for peer in foundPeers.foundRemotePeers.1 { - if let user = peer.peer as? TelegramUser, user.flags.contains(.requirePremium) { + if case let .user(user) = peer.peer, user.flags.contains(.requirePremium) { result.insert(user.id) } } @@ -390,18 +399,18 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { } else { for foundPeer in foundRemotePeers.0 { let peer = foundPeer.peer - if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) { + if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer._asPeer()) { existingPeerIds.insert(peer.id) - entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: EnginePeer(foundPeer.peer)), presence: nil, requiresPremiumForMessaging: peerRequiresPremiumForMessaging[peer.id] ?? false, requiresStars: nil, theme: theme, strings: strings, isGlobal: false)) + entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: foundPeer.peer), presence: nil, requiresPremiumForMessaging: peerRequiresPremiumForMessaging[peer.id] ?? false, requiresStars: nil, theme: theme, strings: strings, isGlobal: false)) index += 1 } } - + for foundPeer in foundRemotePeers.1 { let peer = foundPeer.peer - if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) { + if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer._asPeer()) { existingPeerIds.insert(peer.id) - entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: EnginePeer(peer)), presence: nil, requiresPremiumForMessaging: peerRequiresPremiumForMessaging[peer.id] ?? false, requiresStars: nil, theme: theme, strings: strings, isGlobal: true)) + entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: peer), presence: nil, requiresPremiumForMessaging: peerRequiresPremiumForMessaging[peer.id] ?? false, requiresStars: nil, theme: theme, strings: strings, isGlobal: true)) index += 1 } } diff --git a/submodules/StatisticsUI/Sources/GroupStatsController.swift b/submodules/StatisticsUI/Sources/GroupStatsController.swift index 7109e9d382..961b1ffe23 100644 --- a/submodules/StatisticsUI/Sources/GroupStatsController.swift +++ b/submodules/StatisticsUI/Sources/GroupStatsController.swift @@ -826,7 +826,15 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat let previousData = Atomic(value: nil) let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData - let signal = combineLatest(statePromise.get(), presentationData, dataPromise.get(), context.account.postbox.loadedPeerWithId(peerId), peersPromise.get(), longLoadingSignal) + let loadedChannelPeer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + let signal = combineLatest(statePromise.get(), presentationData, dataPromise.get(), loadedChannelPeer, peersPromise.get(), longLoadingSignal) |> deliverOnMainQueue |> map { state, presentationData, data, channelPeer, peers, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in let previous = previousData.swap(data) @@ -838,9 +846,9 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme) } } - + let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChannelInfo_Stats), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: groupStatsControllerEntries(accountPeerId: context.account.peerId, state: state, data: data, channelPeer: EnginePeer(channelPeer), peers: peers, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: groupStatsControllerEntries(accountPeerId: context.account.peerId, state: state, data: data, channelPeer: channelPeer, peers: peers, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false) return (controllerState, (listState, arguments)) } @@ -862,10 +870,17 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat } openPeerImpl = { [weak controller] peer in if let navigationController = controller?.navigationController as? NavigationController { - let _ = (context.account.postbox.loadedPeerWithId(peer.id) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> take(1) |> deliverOnMainQueue).start(next: { peer in - if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { navigationController.pushViewController(controller) } }) @@ -887,10 +902,17 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat } openPeerAdminActionsImpl = { [weak controller] participantPeerId in if let navigationController = controller?.navigationController as? NavigationController { - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> take(1) |> deliverOnMainQueue).start(next: { peer in - let controller = context.sharedContext.makeChatRecentActionsController(context: context, peer: peer, adminPeerId: participantPeerId, starsState: nil) + let controller = context.sharedContext.makeChatRecentActionsController(context: context, peer: peer._asPeer(), adminPeerId: participantPeerId, starsState: nil) navigationController.pushViewController(controller) }) } diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index 87cad945ee..2f8a557fda 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -196,7 +196,14 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { self.view.endEditing(true) self.context.joinGroupCall(peerId: peerId, invite: invite, requestJoinAsPeerId: { completion in - let currentAccountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) + let currentAccountPeer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> map { peer in return [FoundPeer(peer: peer, subscribers: nil)] } @@ -233,10 +240,10 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { var items: [ActionSheetItem] = [] var isGroup = false for peer in peers { - if peer.peer is TelegramGroup { + if case .legacyGroup = peer.peer { isGroup = true break - } else if let peer = peer.peer as? TelegramChannel, case .group = peer.info { + } else if case let .channel(channel) = peer.peer, case .group = channel.info { isGroup = true break } @@ -248,14 +255,14 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { if peer.peer.id.namespace == Namespaces.Peer.CloudUser { subtitle = presentationData.strings.VoiceChat_PersonalAccount } else if let subscribers = peer.subscribers { - if let peer = peer.peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer.peer, case .broadcast = channel.info { subtitle = strongSelf.presentationData.strings.Conversation_StatusSubscribers(subscribers) } else { subtitle = strongSelf.presentationData.strings.Conversation_StatusMembers(subscribers) } } - items.append(VoiceChatPeerActionSheetItem(context: context, peer: EnginePeer(peer.peer), title: EnginePeer(peer.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), subtitle: subtitle ?? "", action: { + items.append(VoiceChatPeerActionSheetItem(context: context, peer: peer.peer, title: peer.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), subtitle: subtitle ?? "", action: { dismissAction() completion(peer.peer.id) })) diff --git a/submodules/TelegramCallsUI/Sources/CallController.swift b/submodules/TelegramCallsUI/Sources/CallController.swift index c7dd71d3fc..098c84bf08 100644 --- a/submodules/TelegramCallsUI/Sources/CallController.swift +++ b/submodules/TelegramCallsUI/Sources/CallController.swift @@ -521,7 +521,6 @@ public final class CallController: ViewController { confirmation: { peer in switch peer { case let .peer(peer, _, _): - let peer = EnginePeer(peer) guard case let .user(user) = peer else { return .single(false) } @@ -539,7 +538,6 @@ public final class CallController: ViewController { isPeerEnabled: { peer in switch peer { case let .peer(peer, _, _): - let peer = EnginePeer(peer) guard case let .user(user) = peer else { return false } diff --git a/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift b/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift index 04529dbea6..97dbfb86b2 100644 --- a/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift @@ -319,15 +319,23 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { switch content { case let .call(sharedContext, account, call): self.presentationData = sharedContext.currentPresentationData.with { $0 } + let callPeer = TelegramEngine(account: account).data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: call.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } self.stateDisposable.set( (combineLatest( - account.postbox.loadedPeerWithId(call.peerId), + callPeer, call.state, call.isMuted ) |> deliverOnMainQueue).start(next: { [weak self] peer, state, isMuted in if let strongSelf = self { - strongSelf.currentPeer = peer + strongSelf.currentPeer = peer._asPeer() strongSelf.currentCallState = state strongSelf.currentIsMuted = isMuted diff --git a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift index d9fb545339..df2a9311c6 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift @@ -1185,12 +1185,20 @@ public final class MediaStreamComponentController: ViewControllerComponentContai return } - let _ = (combineLatest(queue: .mainQueue(), self.context.account.postbox.loadedPeerWithId(peerId), self.callImpl.state |> take(1)) + let sharedPeerSignal = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + let _ = (combineLatest(queue: .mainQueue(), sharedPeerSignal, self.callImpl.state |> take(1)) |> deliverOnMainQueue).start(next: { [weak self] peer, callState in if let strongSelf = self { var inviteLinks = inviteLinks - - if let peer = peer as? TelegramChannel, case .group = peer.info, !peer.flags.contains(.isGigagroup), !(peer.addressName ?? "").isEmpty, let defaultParticipantMuteState = callState.defaultParticipantMuteState { + + if case let .channel(channel) = peer, case .group = channel.info, !channel.flags.contains(.isGigagroup), !(channel.addressName ?? "").isEmpty, let defaultParticipantMuteState = callState.defaultParticipantMuteState { let isMuted = defaultParticipantMuteState == .muted if !isMuted { diff --git a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift index 2a4275f114..6d7cd2b900 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift @@ -320,7 +320,10 @@ public final class PresentationCallManagerImpl: PresentationCallManager { if let firstState = ringingStates.first { if self.currentCall == nil && self.currentGroupCall == nil { self.currentCallDisposable.set((combineLatest( - firstState.0.account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, PreferencesKeys.appConfiguration]) |> take(1), + firstState.0.engine.data.subscribe( + TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.voipConfiguration), + TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration) + ) |> take(1), accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.experimentalUISettings]) |> take(1) ) |> deliverOnMainQueue).start(next: { [weak self] preferences, sharedData in @@ -330,12 +333,12 @@ public final class PresentationCallManagerImpl: PresentationCallManager { if strongSelf.currentUpgradedToConferenceCallId == firstState.2.id { return } - - let configuration = preferences.values[PreferencesKeys.voipConfiguration]?.get(VoipConfiguration.self) ?? .defaultValue + + let configuration = preferences.0?.get(VoipConfiguration.self) ?? .defaultValue let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings]?.get(AutodownloadSettings.self) ?? .defaultSettings let experimentalSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings]?.get(ExperimentalUISettings.self) ?? .defaultSettings - let appConfiguration = preferences.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue - + let appConfiguration = preferences.1?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue + let call = PresentationCallImpl( context: firstState.0, audioSession: strongSelf.audioSession, @@ -366,7 +369,10 @@ public final class PresentationCallManagerImpl: PresentationCallManager { let _ = currentCall.hangUp().startStandalone() self.currentCallDisposable.set((combineLatest( - firstState.0.account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, PreferencesKeys.appConfiguration]) |> take(1), + firstState.0.engine.data.subscribe( + TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.voipConfiguration), + TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration) + ) |> take(1), accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.experimentalUISettings]) |> take(1) ) |> deliverOnMainQueue).start(next: { [weak self] preferences, sharedData in @@ -376,11 +382,11 @@ public final class PresentationCallManagerImpl: PresentationCallManager { if strongSelf.currentUpgradedToConferenceCallId == firstState.2.id { return } - - let configuration = preferences.values[PreferencesKeys.voipConfiguration]?.get(VoipConfiguration.self) ?? .defaultValue + + let configuration = preferences.0?.get(VoipConfiguration.self) ?? .defaultValue let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings]?.get(AutodownloadSettings.self) ?? .defaultSettings let experimentalSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings]?.get(ExperimentalUISettings.self) ?? .defaultSettings - let appConfiguration = preferences.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue + let appConfiguration = preferences.1?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue let call = PresentationCallImpl( context: firstState.0, @@ -628,7 +634,10 @@ public final class PresentationCallManagerImpl: PresentationCallManager { return peerView.peerIsContact } |> take(1), - context.account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, PreferencesKeys.appConfiguration]) |> take(1), + context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.voipConfiguration), + TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration) + ) |> take(1), accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.experimentalUISettings]) |> take(1), areVideoCallsAvailable ) @@ -638,10 +647,10 @@ public final class PresentationCallManagerImpl: PresentationCallManager { if let currentCall = strongSelf.currentCall { currentCall.rejectBusy() } - - let configuration = preferences.values[PreferencesKeys.voipConfiguration]?.get(VoipConfiguration.self) ?? .defaultValue + + let configuration = preferences.0?.get(VoipConfiguration.self) ?? .defaultValue let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings]?.get(AutodownloadSettings.self) ?? .defaultSettings - let appConfiguration = preferences.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue + let appConfiguration = preferences.1?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue let isVideoPossible: Bool = areVideoCallsAvailable diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 4cb912c60e..25916cfe2f 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -1182,25 +1182,32 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { }) if let peerId { - let _ = (self.account.postbox.loadedPeerWithId(peerId) + let _ = (self.accountContext.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let self else { return } var canManageCall = false - if let peer = peer as? TelegramGroup { - if case .creator = peer.role { + if case let .legacyGroup(group) = peer { + if case .creator = group.role { canManageCall = true - } else if case let .admin(rights, _) = peer.role, rights.rights.contains(.canManageCalls) { + } else if case let .admin(rights, _) = group.role, rights.rights.contains(.canManageCalls) { canManageCall = true } - } else if let peer = peer as? TelegramChannel { - if peer.flags.contains(.isCreator) { + } else if case let .channel(channel) = peer { + if channel.flags.contains(.isCreator) { canManageCall = true - } else if (peer.adminRights?.rights.contains(.canManageCalls) == true) { + } else if (channel.adminRights?.rights.contains(.canManageCalls) == true) { canManageCall = true } - self.peerUpdatesSubscription = self.accountContext.account.viewTracker.polledChannel(peerId: peer.id).start() + self.peerUpdatesSubscription = self.accountContext.account.viewTracker.polledChannel(peerId: channel.id).start() } var updatedValue = self.stateValue updatedValue.canManageCall = canManageCall diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index bee8112161..d97c4fc681 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -677,7 +677,14 @@ final class VideoChatScreenComponent: Component { return } - let _ = (groupCall.accountContext.account.postbox.loadedPeerWithId(peerId) + let _ = (groupCall.accountContext.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] chatPeer in guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return @@ -685,7 +692,7 @@ final class VideoChatScreenComponent: Component { guard let callState = self.callState, let peer = self.peer else { return } - + let initialTitle = callState.title let title: String @@ -698,7 +705,7 @@ final class VideoChatScreenComponent: Component { text = environment.strings.VoiceChat_EditTitleText } - let controller = voiceChatTitleEditController(context: groupCall.accountContext, forceTheme: environment.theme, title: title, text: text, placeholder: EnginePeer(chatPeer).displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), value: initialTitle, maxLength: 40, apply: { [weak self] title in + let controller = voiceChatTitleEditController(context: groupCall.accountContext, forceTheme: environment.theme, title: title, text: text, placeholder: chatPeer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), value: initialTitle, maxLength: 40, apply: { [weak self] title in guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } @@ -796,7 +803,14 @@ final class VideoChatScreenComponent: Component { } if let peerId = groupCall.peerId { - let _ = (groupCall.accountContext.account.postbox.loadedPeerWithId(peerId) + let _ = (groupCall.accountContext.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return @@ -1807,7 +1821,14 @@ final class VideoChatScreenComponent: Component { } }) - let currentAccountPeer = groupCall.accountContext.account.postbox.loadedPeerWithId(groupCall.accountContext.account.peerId) + let currentAccountPeer = groupCall.accountContext.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: groupCall.accountContext.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> map { peer in return [FoundPeer(peer: peer, subscribers: nil)] } diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift index dd47fdab48..5640a3513e 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift @@ -292,12 +292,19 @@ extension VideoChatScreenComponent.View { switch error { case .privacy: - let _ = (groupCall.accountContext.account.postbox.loadedPeerWithId(peer.id) + let _ = (groupCall.accountContext.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } - environment.controller()?.present(textAlertController(context: groupCall.accountContext, title: nil, text: environment.strings.Privacy_GroupsAndChannels_InviteToGroupError(EnginePeer(peer).compactDisplayTitle, EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_OK, action: {})]), in: .window(.root)) + environment.controller()?.present(textAlertController(context: groupCall.accountContext, title: nil, text: environment.strings.Privacy_GroupsAndChannels_InviteToGroupError(peer.compactDisplayTitle, peer.compactDisplayTitle).string, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_OK, action: {})]), in: .window(.root)) }) case .notMutualContact: environment.controller()?.present(textAlertController(context: context, title: nil, text: environment.strings.GroupInfo_AddUserLeftError, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_OK, action: {})]), in: .window(.root)) diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift index 8514be1ed1..41b9b2ff45 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift @@ -168,7 +168,7 @@ extension VideoChatScreenComponent.View { for peer in displayAsPeers { if peer.peer.id == callState.myPeerId { let avatarSize = CGSize(width: 28.0, height: 28.0) - items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_DisplayAs, textLayout: .secondLineWithValue(EnginePeer(peer.peer).displayTitle(strings: environment.strings, displayOrder: currentCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: currentCall.accountContext.account, peer: EnginePeer(peer.peer), size: avatarSize)), action: { [weak self] c, _ in + items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_DisplayAs, textLayout: .secondLineWithValue(peer.peer.displayTitle(strings: environment.strings, displayOrder: currentCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: currentCall.accountContext.account, peer: peer.peer, size: avatarSize)), action: { [weak self] c, _ in guard let self else { return } @@ -625,10 +625,10 @@ extension VideoChatScreenComponent.View { var isGroup = false if let displayAsPeers = self.displayAsPeers { for peer in displayAsPeers { - if peer.peer is TelegramGroup { + if case .legacyGroup = peer.peer { isGroup = true break - } else if let peer = peer.peer as? TelegramChannel, case .group = peer.info { + } else if case let .channel(channel) = peer.peer, case .group = channel.info { isGroup = true break } @@ -645,7 +645,7 @@ extension VideoChatScreenComponent.View { if peer.peer.id.namespace == Namespaces.Peer.CloudUser { subtitle = environment.strings.VoiceChat_PersonalAccount } else if let subscribers = peer.subscribers { - if let peer = peer.peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer.peer, case .broadcast = channel.info { subtitle = environment.strings.Conversation_StatusSubscribers(subscribers) } else { subtitle = environment.strings.Conversation_StatusMembers(subscribers) @@ -655,7 +655,7 @@ extension VideoChatScreenComponent.View { let isSelected = peer.peer.id == myPeerId let extendedAvatarSize = CGSize(width: 35.0, height: 35.0) let theme = environment.theme - let avatarSignal = peerAvatarCompleteImage(account: groupCall.accountContext.account, peer: EnginePeer(peer.peer), size: avatarSize) + let avatarSignal = peerAvatarCompleteImage(account: groupCall.accountContext.account, peer: peer.peer, size: avatarSize) |> map { image -> UIImage? in if isSelected, let image = image { return generateImage(extendedAvatarSize, rotatedContext: { size, context in @@ -676,7 +676,7 @@ extension VideoChatScreenComponent.View { } } - items.append(.action(ContextMenuActionItem(text: EnginePeer(peer.peer).displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: isSelected ? extendedAvatarSize : avatarSize, signal: avatarSignal), action: { [weak self] _, f in + items.append(.action(ContextMenuActionItem(text: peer.peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: isSelected ? extendedAvatarSize : avatarSize, signal: avatarSignal), action: { [weak self] _, f in f(.default) guard let self, case let .group(groupCall) = self.currentCall else { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift index 1e3a3d83db..02d15f743f 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift @@ -630,18 +630,25 @@ final class VoiceChatMainStageNode: ASDisplayNode { self.speakingAudioLevelView = nil } - self.speakingPeerDisposable.set((self.context.account.postbox.loadedPeerWithId(peerId) + self.speakingPeerDisposable.set((self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let strongSelf = self else { return } - + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - strongSelf.speakingAvatarNode.setPeer(context: strongSelf.context, theme: presentationData.theme, peer: EnginePeer(peer)) - + strongSelf.speakingAvatarNode.setPeer(context: strongSelf.context, theme: presentationData.theme, peer: peer) + let bodyAttributes = MarkdownAttributeSet(font: Font.regular(15.0), textColor: .white, additionalAttributes: [:]) let boldAttributes = MarkdownAttributeSet(font: Font.semibold(15.0), textColor: .white, additionalAttributes: [:]) - let attributedText = addAttributesToStringWithRanges(presentationData.strings.VoiceChat_ParticipantIsSpeaking(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) + let attributedText = addAttributesToStringWithRanges(presentationData.strings.VoiceChat_ParticipantIsSpeaking(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) strongSelf.speakingTitleNode.attributedText = attributedText strongSelf.speakingContainerNode.alpha = 0.0 diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift index e829e578d6..c82eabeb16 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift @@ -2957,7 +2957,7 @@ func _internal_groupCallDisplayAsAvailablePeers(accountPeerId: PeerId, network: } } - return peers.map { FoundPeer(peer: $0, subscribers: subscribers[$0.id]) } + return peers.map { FoundPeer(peer: EnginePeer($0), subscribers: subscribers[$0.id]) } } } } @@ -3011,7 +3011,7 @@ func _internal_cachedGroupCallDisplayAsAvailablePeers(account: Account, peerId: if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData { subscribers = cachedData.participantsSummary.memberCount } - peers.append(FoundPeer(peer: peer, subscribers: subscribers)) + peers.append(FoundPeer(peer: EnginePeer(peer), subscribers: subscribers)) } } return (peers, cached.timestamp) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift index 67aa4096db..a0603c10be 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift @@ -5,19 +5,15 @@ import TelegramApi import MtProtoKit public struct SendAsPeer: Equatable { - public let peer: Peer + public let peer: EnginePeer public let subscribers: Int32? public let isPremiumRequired: Bool - - public init(peer: Peer, subscribers: Int32?, isPremiumRequired: Bool) { + + public init(peer: EnginePeer, subscribers: Int32?, isPremiumRequired: Bool) { self.peer = peer self.subscribers = subscribers self.isPremiumRequired = isPremiumRequired } - - public static func ==(lhs: SendAsPeer, rhs: SendAsPeer) -> Bool { - return lhs.peer.isEqual(rhs.peer) && lhs.subscribers == rhs.subscribers && lhs.isPremiumRequired == rhs.isPremiumRequired - } } public final class CachedSendAsPeers: Codable { @@ -61,7 +57,7 @@ func _internal_cachedPeerSendAsAvailablePeers(account: Account, peerId: PeerId) if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData { subscribers = cachedData.participantsSummary.memberCount } - peers.append(SendAsPeer(peer: peer, subscribers: subscribers, isPremiumRequired: cached.premiumRequiredPeerIds.contains(peer.id))) + peers.append(SendAsPeer(peer: EnginePeer(peer), subscribers: subscribers, isPremiumRequired: cached.premiumRequiredPeerIds.contains(peer.id))) } } return (peers, cached.timestamp) @@ -167,7 +163,7 @@ func _internal_peerSendAsAvailablePeers(accountPeerId: PeerId, network: Network, peers.append(peer) } } - return peers.map { SendAsPeer(peer: $0, subscribers: subscribers[$0.id], isPremiumRequired: premiumRequiredPeerIds.contains($0.id)) } + return peers.map { SendAsPeer(peer: EnginePeer($0), subscribers: subscribers[$0.id], isPremiumRequired: premiumRequiredPeerIds.contains($0.id)) } } } } @@ -233,7 +229,7 @@ func _internal_cachedLiveStorySendAsAvailablePeers(account: Account, peerId: Pee if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData { subscribers = cachedData.participantsSummary.memberCount } - peers.append(SendAsPeer(peer: peer, subscribers: subscribers, isPremiumRequired: cached.premiumRequiredPeerIds.contains(peer.id))) + peers.append(SendAsPeer(peer: EnginePeer(peer), subscribers: subscribers, isPremiumRequired: cached.premiumRequiredPeerIds.contains(peer.id))) } } return (peers, cached.timestamp) @@ -327,7 +323,7 @@ func _internal_liveStorySendAsAvailablePeers(account: Account, peerId: PeerId) - peers.append(peer) } } - return peers.map { SendAsPeer(peer: $0, subscribers: subscribers[$0.id], isPremiumRequired: premiumRequiredPeerIds.contains($0.id)) } + return peers.map { SendAsPeer(peer: EnginePeer($0), subscribers: subscribers[$0.id], isPremiumRequired: premiumRequiredPeerIds.contains($0.id)) } } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift index fbbb62a2c8..e82a0a009c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift @@ -5,16 +5,16 @@ import TelegramApi import MtProtoKit public struct FoundPeer: Equatable { - public let peer: Peer + public let peer: EnginePeer public let subscribers: Int32? - - public init(peer: Peer, subscribers: Int32?) { + + public init(peer: EnginePeer, subscribers: Int32?) { self.peer = peer self.subscribers = subscribers } - + public static func ==(lhs: FoundPeer, rhs: FoundPeer) -> Bool { - return lhs.peer.isEqual(rhs.peer) && lhs.subscribers == rhs.subscribers + return lhs.peer == rhs.peer && lhs.subscribers == rhs.subscribers } } @@ -67,9 +67,9 @@ public func _internal_searchPeers(accountPeerId: PeerId, postbox: Postbox, netwo continue } if let user = peer as? TelegramUser { - renderedMyPeers.append(FoundPeer(peer: peer, subscribers: user.subscriberCount)) + renderedMyPeers.append(FoundPeer(peer: EnginePeer(peer), subscribers: user.subscriberCount)) } else { - renderedMyPeers.append(FoundPeer(peer: peer, subscribers: subscribers[peerId])) + renderedMyPeers.append(FoundPeer(peer: EnginePeer(peer), subscribers: subscribers[peerId])) } } } @@ -82,9 +82,9 @@ public func _internal_searchPeers(accountPeerId: PeerId, postbox: Postbox, netwo continue } if let user = peer as? TelegramUser { - renderedPeers.append(FoundPeer(peer: peer, subscribers: user.subscriberCount)) + renderedPeers.append(FoundPeer(peer: EnginePeer(peer), subscribers: user.subscriberCount)) } else { - renderedPeers.append(FoundPeer(peer: peer, subscribers: subscribers[peerId])) + renderedPeers.append(FoundPeer(peer: EnginePeer(peer), subscribers: subscribers[peerId])) } } } @@ -94,14 +94,14 @@ public func _internal_searchPeers(accountPeerId: PeerId, postbox: Postbox, netwo break case .channels: renderedMyPeers = renderedMyPeers.filter { item in - if let channel = item.peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = item.peer, case .broadcast = channel.info { return true } else { return false } } renderedPeers = renderedPeers.filter { item in - if let channel = item.peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = item.peer, case .broadcast = channel.info { return true } else { return false @@ -109,18 +109,18 @@ public func _internal_searchPeers(accountPeerId: PeerId, postbox: Postbox, netwo } case .groups: renderedMyPeers = renderedMyPeers.filter { item in - if let channel = item.peer as? TelegramChannel, case .group = channel.info { + if case let .channel(channel) = item.peer, case .group = channel.info { return true - } else if item.peer is TelegramGroup { + } else if case .legacyGroup = item.peer { return true } else { return false } } renderedPeers = renderedPeers.filter { item in - if let channel = item.peer as? TelegramChannel, case .group = channel.info { + if case let .channel(channel) = item.peer, case .group = channel.info { return true - } else if item.peer is TelegramGroup { + } else if case .legacyGroup = item.peer { return true } else { return false @@ -128,14 +128,14 @@ public func _internal_searchPeers(accountPeerId: PeerId, postbox: Postbox, netwo } case .privateChats: renderedMyPeers = renderedMyPeers.filter { item in - if item.peer is TelegramUser { + if case .user = item.peer { return true } else { return false } } renderedPeers = renderedPeers.filter { item in - if item.peer is TelegramUser { + if case .user = item.peer { return true } else { return false diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift index 656e37a112..8add7c2f44 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift @@ -441,15 +441,25 @@ public extension TelegramEngine { |> map { EngineMediaResource.FetchStatus($0) } } + public func status( + id: EngineMediaResource.Id, + resourceSize: Int64 + ) -> Signal { + return self.account.postbox.mediaBox.resourceStatus(MediaResourceId(id.stringRepresentation), resourceSize: resourceSize) + |> map { EngineMediaResource.FetchStatus($0) } + } + public func data( resource: EngineMediaResource, - pathExtension: String?, - waitUntilFetchStatus: Bool + pathExtension: String? = nil, + waitUntilFetchStatus: Bool = false, + attemptSynchronously: Bool = false ) -> Signal { return self.account.postbox.mediaBox.resourceData( resource._asResource(), pathExtension: pathExtension, - option: .complete(waitUntilFetchStatus: waitUntilFetchStatus) + option: .complete(waitUntilFetchStatus: waitUntilFetchStatus), + attemptSynchronously: attemptSynchronously ) |> map { EngineMediaResource.ResourceData($0) } } diff --git a/submodules/TelegramUI/Components/BatchVideoRendering/Sources/BatchVideoRenderingContext.swift b/submodules/TelegramUI/Components/BatchVideoRendering/Sources/BatchVideoRenderingContext.swift index cbb7053aa4..ac4810831b 100644 --- a/submodules/TelegramUI/Components/BatchVideoRendering/Sources/BatchVideoRenderingContext.swift +++ b/submodules/TelegramUI/Components/BatchVideoRendering/Sources/BatchVideoRenderingContext.swift @@ -205,12 +205,12 @@ public final class BatchVideoRenderingContext { ).startStrict() } if targetContext.dataDisposable == nil { - targetContext.dataDisposable = (self.context.account.postbox.mediaBox.resourceData(targetContext.file.media.resource) + targetContext.dataDisposable = (self.context.engine.resources.data(resource: EngineMediaResource(targetContext.file.media.resource)) |> deliverOnMainQueue).startStrict(next: { [weak self, weak targetContext] data in guard let self, let targetContext else { return } - if data.complete && targetContext.dataPath == nil { + if data.isComplete && targetContext.dataPath == nil { targetContext.dataPath = data.path self.update() } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift index 9b9387202a..45786ac58b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -2404,10 +2404,10 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let peach = 0x1F351 let coffin = 0x26B0 - let appConfiguration = item.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + let appConfiguration = item.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> take(1) |> map { view in - return view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue + return view?.get(AppConfiguration.self) ?? .defaultValue } let text = item.message.text diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift index cbff3c4238..994059e499 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift @@ -431,10 +431,10 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { guard let file = message.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile else { return .single(nil) } - return context.account.postbox.mediaBox.resourceData(id: file.resource.id) + return context.engine.resources.data(id: EngineMediaResource.Id(file.resource.id)) |> take(1) |> mapToSignal { data -> Signal in - if !data.complete { + if !data.isComplete { return .single(nil) } return .single(data.path) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index 19dc1042ea..ccc39814a1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -1766,7 +1766,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr if NativeVideoContent.isHLSVideo(file: file), let minimizedQuality = HLSVideoContent.minimizedHLSQuality(file: .standalone(media: file), codecConfiguration: HLSCodecConfiguration(context: context)) { let postbox = context.account.postbox - let playlistStatusSignal = postbox.mediaBox.resourceStatus(minimizedQuality.playlist.media.resource) + let playlistStatusSignal = context.engine.resources.status(resource: EngineMediaResource(minimizedQuality.playlist.media.resource)) |> map { status -> MediaResourceStatus in switch status { case .Fetching, .Paused: @@ -1796,7 +1796,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr return .single((.Local, nil)) } - return postbox.mediaBox.resourceStatus(preloadData.0.media.resource) + return context.engine.resources.status(resource: EngineMediaResource(preloadData.0.media.resource)) |> map { status -> Bool in if case .Fetching = status { return true @@ -1806,7 +1806,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } |> distinctUntilChanged |> mapToSignal { isFetching -> Signal<(MediaResourceStatus, MediaResourceStatus?), NoError> in - return postbox.mediaBox.resourceRangesStatus(preloadData.0.media.resource) + return context.engine.resources.resourceRangesStatus(resource: EngineMediaResource(preloadData.0.media.resource)) |> map { status -> (MediaResourceStatus, MediaResourceStatus?) in let preloadRanges = RangeSet(preloadData.1) let intersection = status.intersection(preloadRanges) diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index 8e91e61b3e..66f1e4ca74 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -357,8 +357,15 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { return .single(peer?._asPeer()) } } else { - resolveSignal = context.account.postbox.loadedPeerWithId(strongSelf.peer.id) - |> map(Optional.init) + resolveSignal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.peer.id)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map { Optional($0._asPeer()) } } strongSelf.resolvePeerByNameDisposable.set((resolveSignal |> deliverOnMainQueue).startStrict(next: { peer in diff --git a/submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift b/submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift index a0552e2033..d1b611b2b7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift @@ -70,8 +70,8 @@ private final class ChatSendAsPeerListContextItemNode: ASDisplayNode, ContextMen if peer.peer.id.namespace == Namespaces.Peer.CloudUser { subtitle = presentationData.strings.VoiceChat_PersonalAccount } else if let subscribers = peer.subscribers { - if let peer = peer.peer as? TelegramChannel { - if case .broadcast = peer.info { + if case let .channel(channel) = peer.peer { + if case .broadcast = channel.info { subtitle = presentationData.strings.Conversation_StatusSubscribers(subscribers) } else { subtitle = presentationData.strings.VoiceChat_DiscussionGroup @@ -86,7 +86,7 @@ private final class ChatSendAsPeerListContextItemNode: ASDisplayNode, ContextMen selectedItemIndex = i } let extendedAvatarSize = CGSize(width: 35.0, height: 35.0) - let avatarSignal = peerAvatarCompleteImage(account: item.context.account, peer: EnginePeer(peer.peer), size: avatarSize) + let avatarSignal = peerAvatarCompleteImage(account: item.context.account, peer: peer.peer, size: avatarSize) |> map { image -> UIImage? in if isSelected, let image = image { return generateImage(extendedAvatarSize, rotatedContext: { size, context in @@ -107,18 +107,18 @@ private final class ChatSendAsPeerListContextItemNode: ASDisplayNode, ContextMen } } - let action = ContextMenuActionItem(text: EnginePeer(peer.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: isSelected ? extendedAvatarSize : avatarSize, signal: avatarSignal), textIcon: { theme in + let action = ContextMenuActionItem(text: peer.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: isSelected ? extendedAvatarSize : avatarSize, signal: avatarSignal), textIcon: { theme in return !item.isPremium && peer.isPremiumRequired ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.badgeInactiveFillColor) : nil }, action: { _, f in f(.default) if !item.isPremium && peer.isPremiumRequired { - item.presentToast(EnginePeer(peer.peer)) + item.presentToast(peer.peer) return } if peer.peer.id != item.selectedPeerId { - item.action(EnginePeer(peer.peer)) + item.action(peer.peer) } }) let actionNode = ContextActionNode(presentationData: presentationData, action: action, getController: getController, actionSelected: actionSelected, requestLayout: {}, requestUpdateAction: { _, _ in diff --git a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift index fde8bae8a7..7253069a2e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift @@ -86,7 +86,7 @@ public final class ChatSendContactMessageContextPreview: UIView, ChatSendMessage for peer in self.contactPeers { switch peer { case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + guard case let .user(contact) = contact, let phoneNumber = contact.phone else { continue } let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift index 3baa7ab194..731b08b776 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift @@ -845,7 +845,7 @@ public final class ChatTextInputPanelComponent: Component { if let sendAsConfiguration = component.sendAsConfiguration { presentationInterfaceState = presentationInterfaceState.updatedSendAsPeers([SendAsPeer( - peer: sendAsConfiguration.currentPeer._asPeer(), + peer: sendAsConfiguration.currentPeer, subscribers: sendAsConfiguration.subscriberCount.flatMap(Int32.init(clamping:)), isPremiumRequired: sendAsConfiguration.isPremiumLocked )]).updatedShowSendAsPeers(sendAsConfiguration.isSelecting).updatedCurrentSendAsPeerId(sendAsConfiguration.currentPeer.id) diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift index 8edb6af71b..ee6f187026 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift @@ -1622,7 +1622,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg currentPeer = sendAsPeers.first?.peer } if let context = self.context, let peer = currentPeer { - self.sendAsAvatarNode.setPeer(context: context, theme: interfaceState.theme, peer: EnginePeer(peer), emptyColor: interfaceState.theme.list.mediaPlaceholderColor) + self.sendAsAvatarNode.setPeer(context: context, theme: interfaceState.theme, peer: peer, emptyColor: interfaceState.theme.list.mediaPlaceholderColor) } } else if let peer = interfaceState.renderedPeer?.peer as? TelegramUser, let _ = peer.botInfo, shouldDisplayMenuButton && interfaceState.editMessageState == nil { hasMenuButton = true diff --git a/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/Sources/ManagedDiceAnimationNode.swift b/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/Sources/ManagedDiceAnimationNode.swift index 9cd10cc11e..ddfe1640ca 100644 --- a/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/Sources/ManagedDiceAnimationNode.swift +++ b/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/Sources/ManagedDiceAnimationNode.swift @@ -140,9 +140,9 @@ public final class ManagedDiceAnimationNode: ManagedAnimationNode { self.context = context self.emoji = emoji - self.configuration.set(self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + self.configuration.set(self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> map { preferencesView -> InteractiveEmojiConfiguration? in - let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue + let appConfiguration: AppConfiguration = preferencesView?.get(AppConfiguration.self) ?? .defaultValue return InteractiveEmojiConfiguration.with(appConfiguration: appConfiguration) }) self.emojis.set(context.engine.stickers.loadedStickerPack(reference: .dice(emoji), forceActualized: false) diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index ff9b4d23e4..9477b10bf2 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -1,9 +1,7 @@ import Foundation import UIKit -import Postbox import SwiftSignalKit import AsyncDisplayKit -import Postbox import TelegramCore import Display import TelegramUIPreferences @@ -18,6 +16,7 @@ import TextFormat import WallpaperBackgroundNode import AnimationCache import MultiAnimationRenderer +import Postbox public struct ChatInterfaceHighlightedState: Equatable { public struct Quote: Equatable { diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift index 45f06a2c75..82475ee6f2 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift @@ -38,8 +38,8 @@ private func randomGenericReactionEffect(context: AccountContext) -> Signal filter(\.complete) + let dataDisposable = (context.engine.resources.data(resource: EngineMediaResource(file.resource)) + |> filter(\.isComplete) |> take(1)).start(next: { data in subscriber.putNext(data.path) subscriber.putCompletion() @@ -1207,10 +1207,10 @@ public final class EmojiStatusSelectionController: ViewController { for reaction in availableReactions.reactions { if case let .builtin(value) = reaction.value, value == emojiString { if let aroundAnimation = reaction.aroundAnimation?._parse() { - return context.account.postbox.mediaBox.resourceData(aroundAnimation.resource) + return context.engine.resources.data(resource: EngineMediaResource(aroundAnimation.resource)) |> take(1) |> map { data -> String? in - if data.complete { + if data.isComplete { return data.path } else { return nil @@ -1396,10 +1396,10 @@ public final class EmojiStatusSelectionController: ViewController { for reaction in availableReactions.reactions { if case let .builtin(value) = reaction.value, value == emojiString { if let aroundAnimation = reaction.aroundAnimation?._parse() { - return context.account.postbox.mediaBox.resourceData(aroundAnimation.resource) + return context.engine.resources.data(resource: EngineMediaResource(aroundAnimation.resource)) |> take(1) |> map { data -> String? in - if data.complete { + if data.isComplete { return data.path } else { return nil diff --git a/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerification.swift b/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerification.swift index 49c570eb66..e07f10ce92 100644 --- a/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerification.swift +++ b/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerification.swift @@ -83,8 +83,8 @@ public func ageVerificationAvailability(context: AccountContext) -> Signal { subscriber in let fetchedDisposable = fetchedData.start() - let resourceDataDisposable = context.account.postbox.mediaBox.resourceData(file.resource, attemptSynchronously: false).start(next: { next in - if next.complete { + let resourceDataDisposable = context.engine.resources.data(resource: EngineMediaResource(file.resource)).start(next: { next in + if next.isComplete { SSZipArchive.unzipFile(atPath: next.path, toDestination: NSTemporaryDirectory()) subscriber.putNext(.completed(compiledModelPath)) subscriber.putCompletion() diff --git a/submodules/TelegramUI/Components/GlobalControlPanelsContext/Sources/GlobalControlPanelsContext.swift b/submodules/TelegramUI/Components/GlobalControlPanelsContext/Sources/GlobalControlPanelsContext.swift index d42d3910c2..196523ecb5 100644 --- a/submodules/TelegramUI/Components/GlobalControlPanelsContext/Sources/GlobalControlPanelsContext.swift +++ b/submodules/TelegramUI/Components/GlobalControlPanelsContext/Sources/GlobalControlPanelsContext.swift @@ -316,9 +316,9 @@ public final class GlobalControlPanelsContext { if chatListNotices { let twoStepData: Signal = .single(nil) |> then(context.engine.auth.twoStepVerificationConfiguration() |> map(Optional.init)) - let accountFreezeConfiguration = (context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + let accountFreezeConfiguration = (context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> map { view -> AppConfiguration in - let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue + let appConfiguration: AppConfiguration = view?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue return appConfiguration } |> distinctUntilChanged diff --git a/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/Sources/ImageObjectSeparation.swift b/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/Sources/ImageObjectSeparation.swift index a1ad135246..b4b73cb1fd 100644 --- a/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/Sources/ImageObjectSeparation.swift +++ b/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/Sources/ImageObjectSeparation.swift @@ -75,8 +75,8 @@ public func cutoutAvailability(context: AccountContext) -> Signal { subscriber in let fetchedDisposable = fetchedData.start() - let resourceDataDisposable = context.account.postbox.mediaBox.resourceData(file.resource, attemptSynchronously: false).start(next: { next in - if next.complete { + let resourceDataDisposable = context.engine.resources.data(resource: EngineMediaResource(file.resource)).start(next: { next in + if next.isComplete { SSZipArchive.unzipFile(atPath: next.path, toDestination: NSTemporaryDirectory()) subscriber.putNext(.completed(compiledModelPath)) subscriber.putCompletion() diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift index a5c7b4d4b3..7ee5afa086 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift @@ -82,9 +82,9 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, cha signal = .single({ _ in return .stickers([]) }) } - let stickerConfiguration = context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + let stickerConfiguration = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> map { preferencesView -> StickersSearchConfiguration in - let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue + let appConfiguration: AppConfiguration = preferencesView?.get(AppConfiguration.self) ?? .defaultValue return StickersSearchConfiguration.with(appConfiguration: appConfiguration) } let stickerSettings = context.sharedContext.accountManager.transaction { transaction -> StickerSettings in diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index 46ff447e9f..eaf2febdba 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -957,7 +957,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, combineLatest(notificationExceptions, notificationsAuthorizationStatus.get(), notificationsWarningSuppressed.get()), combineLatest(context.account.viewTracker.featuredStickerPacks(), archivedStickerPacks), hasPassport, - context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]), + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)), context.engine.notices.getServerProvidedSuggestions(), context.engine.data.get( TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false), @@ -988,7 +988,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, }) var enableQRLogin = false - let appConfiguration = accountPreferences.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) + let appConfiguration = accountPreferences?.get(AppConfiguration.self) if let appConfiguration, let data = appConfiguration.data, let enableQR = data["qr_login_camera"] as? Bool, enableQR { enableQRLogin = true } @@ -2116,7 +2116,7 @@ func peerInfoScreenData( requestsStatePromise.get(), hasStories, threadData, - context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]), + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)), accountIsPremium, hasSavedMessages, hasSavedMessagesChats, @@ -2196,7 +2196,7 @@ func peerInfoScreenData( let peerNotificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings let threadNotificationSettings = threadData?.notificationSettings - let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue + let appConfiguration: AppConfiguration = preferencesView?.get(AppConfiguration.self) ?? .defaultValue return .single(PeerInfoScreenData( peer: peerView.peers[groupId], diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 62c731b5fe..7ac6061280 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -3702,7 +3702,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } func performBotCommand(command: PeerInfoBotCommand) { - let _ = (self.context.account.postbox.loadedPeerWithId(peerId) + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in guard let strongSelf = self else { return diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenCallActions.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenCallActions.swift index f27cded7ca..42760d5fd6 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenCallActions.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenCallActions.swift @@ -144,7 +144,14 @@ extension PeerInfoScreenNode { func openVoiceChatDisplayAsPeerSelection(completion: @escaping (PeerId) -> Void, gesture: ContextGesture? = nil, contextController: ContextControllerProtocol? = nil, result: ((ContextMenuActionResult) -> Void)? = nil, backAction: ((ContextControllerProtocol) -> Void)? = nil) { let dismissOnSelection = contextController == nil - let currentAccountPeer = self.context.account.postbox.loadedPeerWithId(context.account.peerId) + let currentAccountPeer = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> map { peer in return [FoundPeer(peer: peer, subscribers: nil)] } @@ -165,10 +172,10 @@ extension PeerInfoScreenNode { var isGroup = false for peer in peers { - if peer.peer is TelegramGroup { + if case .legacyGroup = peer.peer { isGroup = true break - } else if let peer = peer.peer as? TelegramChannel, case .group = peer.info { + } else if case let .channel(channel) = peer.peer, case .group = channel.info { isGroup = true break } @@ -183,7 +190,7 @@ extension PeerInfoScreenNode { if peer.peer.id.namespace == Namespaces.Peer.CloudUser { subtitle = strongSelf.presentationData.strings.VoiceChat_PersonalAccount } else if let subscribers = peer.subscribers { - if let peer = peer.peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer.peer, case .broadcast = channel.info { subtitle = strongSelf.presentationData.strings.Conversation_StatusSubscribers(subscribers) } else { subtitle = strongSelf.presentationData.strings.Conversation_StatusMembers(subscribers) @@ -191,8 +198,8 @@ extension PeerInfoScreenNode { } let avatarSize = CGSize(width: 28.0, height: 28.0) - let avatarSignal = peerAvatarCompleteImage(account: strongSelf.context.account, peer: EnginePeer(peer.peer), size: avatarSize) - items.append(.action(ContextMenuActionItem(text: EnginePeer(peer.peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: avatarSignal), action: { _, f in + let avatarSignal = peerAvatarCompleteImage(account: strongSelf.context.account, peer: peer.peer, size: avatarSize) + items.append(.action(ContextMenuActionItem(text: peer.peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: avatarSignal), action: { _, f in if dismissOnSelection { f(.dismissWithoutContent) } @@ -246,7 +253,14 @@ extension PeerInfoScreenNode { let context = self.context let peerId = self.peerId let defaultJoinAsPeerId = defaultJoinAsPeerId ?? self.context.account.peerId - let currentAccountPeer = self.context.account.postbox.loadedPeerWithId(self.context.account.peerId) + let currentAccountPeer = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> map { peer in return [FoundPeer(peer: peer, subscribers: nil)] } @@ -271,7 +285,7 @@ extension PeerInfoScreenNode { } if let peer = selectedPeer { let avatarSize = CGSize(width: 28.0, height: 28.0) - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_DisplayAs, textLayout: .secondLineWithValue(EnginePeer(peer.peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: strongSelf.context.account, peer: EnginePeer(peer.peer), size: avatarSize)), action: { c, f in + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_DisplayAs, textLayout: .secondLineWithValue(peer.peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: strongSelf.context.account, peer: peer.peer, size: avatarSize)), action: { c, f in guard let strongSelf = self else { return } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenURL.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenURL.swift index f9b34f3961..e9066b3ced 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenURL.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenURL.swift @@ -171,8 +171,15 @@ extension PeerInfoScreenNode { return .single(peer?._asPeer()) } } else { - resolveSignal = self.context.account.postbox.loadedPeerWithId(self.peerId) - |> map(Optional.init) + resolveSignal = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map { Optional($0._asPeer()) } } var cancelImpl: (() -> Void)? let presentationData = self.presentationData diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PresentAddMembers.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PresentAddMembers.swift index af50c523e1..805389e0ed 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PresentAddMembers.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PresentAddMembers.swift @@ -60,13 +60,20 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa contactsController.navigationPresentation = .modal confirmationImpl = { [weak contactsController] peerId in - return context.account.postbox.loadedPeerWithId(peerId) + return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue |> mapToSignal { peer in let result = ValuePromise() 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(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, actions: [ + 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) }), diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index bce7809348..1c64bc9774 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -899,8 +899,8 @@ final class PeerSelectionControllerNode: ASDisplayNode { var selectedPeerMap: [EnginePeer.Id: EnginePeer] = [:] for contactPeer in selectedContactPeers { if case let .peer(peer, _, _) = contactPeer { - selectedPeers.append(EnginePeer(peer)) - selectedPeerMap[peer.id] = EnginePeer(peer) + selectedPeers.append(peer) + selectedPeerMap[peer.id] = peer } } return (selectedPeers, selectedPeerMap) @@ -1589,7 +1589,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { contactListNode.openPeer = { [weak self] peer, _, _, _ in if case let .peer(peer, _, _) = peer { self?.contactListNode?.listNode.clearHighlightAnimated(true) - self?.requestOpenPeer?(EnginePeer(peer), nil) + self?.requestOpenPeer?(peer, nil) } } contactListNode.openDisabledPeer = { [weak self] peer, reason in diff --git a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/QuickReactionSetupController.swift b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/QuickReactionSetupController.swift index 697a6a5d6e..38cbafd692 100644 --- a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/QuickReactionSetupController.swift +++ b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/QuickReactionSetupController.swift @@ -245,10 +245,10 @@ public func quickReactionSetupController( } ) - let settings = context.account.postbox.preferencesView(keys: [PreferencesKeys.reactionSettings]) + let settings = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.reactionSettings)) |> map { preferencesView -> ReactionSettings in let reactionSettings: ReactionSettings - if let entry = preferencesView.values[PreferencesKeys.reactionSettings], let value = entry.get(ReactionSettings.self) { + if let entry = preferencesView, let value = entry.get(ReactionSettings.self) { reactionSettings = value } else { reactionSettings = .default diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift index 7bd2d9037d..2256c26de2 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift @@ -927,7 +927,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { signal = chatMessagePhoto(postbox: context.account.postbox, userLocation: .other, photoReference: .standalone(media: tmpImage)) fetchSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: .media(media: .standalone(media: tmpImage), resource: imageResource)) - statusSignal = context.account.postbox.mediaBox.resourceStatus(imageResource) + statusSignal = context.engine.resources.status(resource: EngineMediaResource(imageResource)) + |> map { $0._asStatus() } } else { displaySize = CGSize(width: 1.0, height: 1.0) contentSize = displaySize diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift index 012e74750b..7c25b0979a 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift @@ -1922,7 +1922,7 @@ public func preloadStoryMedia(context: AccountContext, info: StoryPreloadInfo) - return .complete() } - let statusSignal = context.account.postbox.mediaBox.resourceStatus(file.resource) + let statusSignal = context.engine.resources.status(resource: EngineMediaResource(file.resource)) |> filter { status in if case .Local = status { return true @@ -1969,7 +1969,7 @@ public func preloadStoryMedia(context: AccountContext, info: StoryPreloadInfo) - return .complete() } - let statusSignal = context.account.postbox.mediaBox.resourceStatus(file.resource) + let statusSignal = context.engine.resources.status(resource: EngineMediaResource(file.resource)) |> filter { status in if case .Local = status { return true @@ -2065,9 +2065,9 @@ public func waitUntilStoryMediaPreloaded(context: AccountContext, peerId: Engine case let .image(image): if let representation = largestImageRepresentation(image.representations) { statusSignals.append( - context.account.postbox.mediaBox.resourceData(representation.resource) + context.engine.resources.data(resource: EngineMediaResource(representation.resource)) |> filter { data in - return data.complete + return data.isComplete } |> take(1) |> ignoreValues @@ -2158,7 +2158,7 @@ public func waitUntilStoryMediaPreloaded(context: AccountContext, peerId: Engine return .complete() } - let statusSignal = context.account.postbox.mediaBox.resourceStatus(file.resource) + let statusSignal = context.engine.resources.status(resource: EngineMediaResource(file.resource)) |> filter { status in if case .Local = status { return true @@ -2207,7 +2207,7 @@ public func waitUntilStoryMediaPreloaded(context: AccountContext, peerId: Engine return .complete() } - let statusSignal = context.account.postbox.mediaBox.resourceStatus(file.resource) + let statusSignal = context.engine.resources.status(resource: EngineMediaResource(file.resource)) |> filter { status in if case .Local = status { return true diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemImageView.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemImageView.swift index 0973e94cab..516c8d872b 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemImageView.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemImageView.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AccountContext import TelegramCore -import Postbox import SwiftSignalKit import ComponentFlow import TinyThumbnail @@ -106,9 +105,9 @@ final class StoryItemImageView: UIView { if let peerReference = PeerReference(peer._asPeer()) { self.fetchDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(peer.id), userContentType: .story, reference: .media(media: .story(peer: peerReference, id: storyId, media: media._asMedia()), resource: representation.resource), ranges: nil).start() } - self.disposable = (context.account.postbox.mediaBox.resourceData(representation.resource, option: .complete(waitUntilFetchStatus: false)) + self.disposable = (context.engine.resources.data(resource: EngineMediaResource(representation.resource)) |> map { result -> UIImage? in - if result.complete { + if result.isComplete { if #available(iOS 15.0, *) { if let image = UIImage(contentsOfFile: result.path)?.preparingForDisplay() { return image @@ -165,9 +164,9 @@ final class StoryItemImageView: UIView { } let fullSize = context.account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedVideoFirstFrameRepresentation(), complete: true, fetch: true, attemptSynchronously: false) - var previewSize: Signal = .single(nil) + var previewSize: Signal = .single(nil) if let representation = file.previewRepresentations.first { - previewSize = context.account.postbox.mediaBox.resourceData(representation.resource, option: .complete(waitUntilFetchStatus: false)) + previewSize = context.engine.resources.data(resource: EngineMediaResource(representation.resource)) |> map(Optional.init) } @@ -190,7 +189,7 @@ final class StoryItemImageView: UIView { return nil } } - } else if let previewResult, previewResult.complete { + } else if let previewResult, previewResult.isComplete { if #available(iOS 15.0, *) { if let image = UIImage(contentsOfFile: previewResult.path)?.preparingForDisplay() { return image diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 0c0a5b5f27..7732f8a71a 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -3066,7 +3066,7 @@ public final class StoryItemSetContainerComponent: Component { sendAsConfiguration = sendAsPeer.flatMap { value in return MessageInputPanelComponent.SendAsConfiguration( - currentPeer: EnginePeer(value.peer), + currentPeer: value.peer, subscriberCount: value.subscribers.flatMap(Int.init), isPremiumLocked: value.isPremiumRequired, isSelecting: self.sendMessageContext.isSelectingSendAsPeer, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index e374ef17e8..2decbf8996 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -246,7 +246,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { var availablePeers: [SendAsPeer] = [] availablePeers.append(SendAsPeer( - peer: accountPeer._asPeer(), + peer: accountPeer, subscribers: nil, isPremiumRequired: false )) @@ -2038,7 +2038,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { var media: TelegramMediaContact? switch peer { case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + guard case let .user(contact) = contact, let phoneNumber = contact.phone else { continue } let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") @@ -2071,7 +2071,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { let dataSignal: Signal<(EnginePeer?, DeviceContactExtendedData?), NoError> switch peer { case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + guard case let .user(contact) = contact, let phoneNumber = contact.phone else { return } let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") @@ -3108,11 +3108,18 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { })) } case .info: - let _ = (context.account.postbox.loadedPeerWithId(peerId.id) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId.id)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> take(1) |> deliverOnMainQueue).start(next: { [weak navigationController] peer in if peer.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) == nil { - if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { navigationController?.pushViewController(infoController) } } @@ -3262,8 +3269,15 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } } else { - resolveSignal = component.context.account.postbox.loadedPeerWithId(peerId) - |> map(Optional.init) + resolveSignal = component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map { Optional($0._asPeer()) } } var cancelImpl: (() -> Void)? let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } @@ -3339,7 +3353,15 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { return } - let peerSignal: Signal = component.context.account.postbox.loadedPeerWithId(peer.id) |> map(Optional.init) + let peerSignal: Signal = component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map(Optional.init) self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak view] peer in guard let view, let component = view.component, let peer else { return @@ -3352,7 +3374,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { if component.metrics.widthClass == .regular { expandAvatar = false } - if let infoController = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer, mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: false, requestsContext: nil) { + if let infoController = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: false, requestsContext: nil) { component.controller()?.push(infoController) } })) @@ -4055,8 +4077,8 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { let initialData = await ChatSendStarsScreen.initialData( context: component.context, peerId: peerId, - myPeer: (sendAsPeer?.peer).flatMap(EnginePeer.init), - reactSubject: .liveStream(peerId: peerId, storyId: focusedItem.storyItem.id, minAmount: Int(minAmount), liveChatMessageParams: LiveChatMessageParams(appConfig: component.context.currentAppConfiguration.with({ $0 })), availableSendAsPeers: component.isEmbeddedInCamera ? [] : (self.sendAsData?.availablePeers.map({ EnginePeer($0.peer) }) ?? []), isDisplayOnly: component.isEmbeddedInCamera), + myPeer: sendAsPeer?.peer, + reactSubject: .liveStream(peerId: peerId, storyId: focusedItem.storyItem.id, minAmount: Int(minAmount), liveChatMessageParams: LiveChatMessageParams(appConfig: component.context.currentAppConfiguration.with({ $0 })), availableSendAsPeers: component.isEmbeddedInCamera ? [] : (self.sendAsData?.availablePeers.map({ $0.peer }) ?? []), isDisplayOnly: component.isEmbeddedInCamera), topPeers: topPeers, completion: { [weak self, weak view] amount, privacy, _, _ in guard let self, let view else { diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index 83704317b2..354ab8cf9a 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -329,9 +329,9 @@ public final class AccountContextImpl: AccountContext { self.animationRenderer = DCTMultiAnimationRendererImpl() (self.animationRenderer as? DCTMultiAnimationRendererImpl)?.useYuvA = sharedContext.immediateExperimentalUISettings.compressedEmojiCache - let updatedLimitsConfiguration = account.postbox.preferencesView(keys: [PreferencesKeys.limitsConfiguration]) + let updatedLimitsConfiguration = self.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.limitsConfiguration)) |> map { preferences -> LimitsConfiguration in - return preferences.values[PreferencesKeys.limitsConfiguration]?.get(LimitsConfiguration.self) ?? LimitsConfiguration.defaultValue + return preferences?.get(LimitsConfiguration.self) ?? LimitsConfiguration.defaultValue } self.currentLimitsConfiguration = Atomic(value: limitsConfiguration) @@ -353,7 +353,7 @@ public final class AccountContextImpl: AccountContext { let _ = currentContentSettings.swap(value) }) - let updatedAppConfiguration = getAppConfiguration(postbox: account.postbox) + let updatedAppConfiguration = getAppConfiguration(engine: self.engine) self.currentAppConfiguration = Atomic(value: appConfiguration) self._appConfiguration.set(.single(appConfiguration) |> then(updatedAppConfiguration)) @@ -923,10 +923,10 @@ private final class ChatLocationReplyContextHolderImpl: ChatLocationContextHolde } } -func getAppConfiguration(postbox: Postbox) -> Signal { - return postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) +func getAppConfiguration(engine: TelegramEngine) -> Signal { + return engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> map { view -> AppConfiguration in - let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue + let appConfiguration: AppConfiguration = view?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue return appConfiguration } |> distinctUntilChanged diff --git a/submodules/TelegramUI/Sources/ApplicationContext.swift b/submodules/TelegramUI/Sources/ApplicationContext.swift index 59da17e4f0..29ed818c97 100644 --- a/submodules/TelegramUI/Sources/ApplicationContext.swift +++ b/submodules/TelegramUI/Sources/ApplicationContext.swift @@ -780,9 +780,9 @@ final class AuthorizedApplicationContext { }) let importableContacts = self.context.sharedContext.contactDataManager?.importable() ?? .single([:]) - let optionalImportableContacts = self.context.account.postbox.preferencesView(keys: [PreferencesKeys.contactsSettings]) + let optionalImportableContacts = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.contactsSettings)) |> mapToSignal { preferences -> Signal<[DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData], NoError> in - let settings: ContactsSettings = preferences.values[PreferencesKeys.contactsSettings]?.get(ContactsSettings.self) ?? .defaultSettings + let settings: ContactsSettings = preferences?.get(ContactsSettings.self) ?? .defaultSettings if settings.synchronizeContacts { return importableContacts } else { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index bf6c235c20..89632a501b 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -760,7 +760,14 @@ extension ChatControllerImpl { }) } - let currentAccountPeer = self.context.account.postbox.loadedPeerWithId(self.context.account.peerId) + let currentAccountPeer = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> map { peer in return SendAsPeer(peer: peer, subscribers: nil, isPremiumRequired: false) } @@ -795,7 +802,7 @@ extension ChatControllerImpl { } } if !hasAnonymousPeer { - allPeers?.insert(SendAsPeer(peer: channel, subscribers: 0, isPremiumRequired: false), at: 0) + allPeers?.insert(SendAsPeer(peer: EnginePeer(channel), subscribers: 0, isPremiumRequired: false), at: 0) } } else if let channel = peerViewMainPeer(peerView) as? TelegramChannel, case let .broadcast(info) = channel.info, (info.flags.contains(.messagesShouldHaveSignatures) || info.flags.contains(.messagesShouldHaveProfiles)) { allPeers = peers @@ -813,7 +820,7 @@ extension ChatControllerImpl { allPeers?.insert(currentAccountPeer, at: 0) } if !hasAnonymousPeer { - allPeers?.insert(SendAsPeer(peer: channel, subscribers: 0, isPremiumRequired: false), at: 0) + allPeers?.insert(SendAsPeer(peer: EnginePeer(channel), subscribers: 0, isPremiumRequired: false), at: 0) } } else { allPeers = peers.filter { $0.peer.id != peerViewMainPeer(peerView)?.id } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift index d4e0f3fca8..3389539979 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift @@ -179,7 +179,15 @@ extension ChatControllerImpl { if let messageId = fromMessage?.id { peerSignal = loadedPeerFromMessage(account: self.context.account, peerId: peer.id, messageId: messageId) } else { - peerSignal = self.context.account.postbox.loadedPeerWithId(peer.id) |> map(Optional.init) + peerSignal = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map { Optional($0._asPeer()) } } self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).startStrict(next: { [weak self] peer in if let strongSelf = self, let peer = peer { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 692ce77bed..12d3cf1c2d 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -3049,7 +3049,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return $0 } }) - strongSelf.messageActionUrlAuthDisposable.set(((combineLatest(strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.context.account.peerId), strongSelf.context.engine.messages.requestMessageActionUrlAuth(subject: subject) |> afterDisposed { + let urlAuthAccountPeer = strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + strongSelf.messageActionUrlAuthDisposable.set(((combineLatest(urlAuthAccountPeer, strongSelf.context.engine.messages.requestMessageActionUrlAuth(subject: subject) |> afterDisposed { Queue.mainQueue().async { if let strongSelf = self { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { @@ -3077,7 +3085,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case .default: strongSelf.openUrl(defaultUrl, concealed: false, skipUrlAuth: true) case let .request(domain, bot, _, flags, _, _): - let controller = chatMessageActionUrlAuthController(context: strongSelf.context, defaultUrl: defaultUrl, domain: domain, bot: bot, requestWriteAccess: flags.contains(.requestWriteAccess), displayName: EnginePeer(peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), open: { [weak self] authorize, allowWriteAccess in + let controller = chatMessageActionUrlAuthController(context: strongSelf.context, defaultUrl: defaultUrl, domain: domain, bot: bot, requestWriteAccess: flags.contains(.requestWriteAccess), displayName: peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), open: { [weak self] authorize, allowWriteAccess in if let strongSelf = self { if authorize { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { @@ -3236,10 +3244,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: strongSelf.presentationData.strings.Conversation_ShareBotContactConfirmationTitle, text: strongSelf.presentationData.strings.Conversation_ShareBotContactConfirmation, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { if let strongSelf = self { - let _ = (strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.context.account.peerId) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).startStandalone(next: { peer in - if let peer = peer as? TelegramUser, let phone = peer.phone, !phone.isEmpty { - strongSelf.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaContact(firstName: peer.firstName ?? "", lastName: peer.lastName ?? "", phoneNumber: phone, peerId: peer.id, vCardData: nil)), threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]) + if case let .user(user) = peer, let phone = user.phone, !phone.isEmpty { + strongSelf.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaContact(firstName: user.firstName ?? "", lastName: user.lastName ?? "", phoneNumber: phone, peerId: user.id, vCardData: nil)), threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]) } }) } @@ -9187,8 +9202,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } } else if let peerId = self.chatLocation.peerId { - resolveSignal = self.context.account.postbox.loadedPeerWithId(peerId) - |> map(Optional.init) + resolveSignal = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map { Optional($0._asPeer()) } } else { resolveSignal = .single(nil) } @@ -9239,12 +9261,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } func shareAccountContact() { - let _ = (self.context.account.postbox.loadedPeerWithId(self.context.account.peerId) + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).startStandalone(next: { [weak self] accountPeer in guard let strongSelf = self else { return } - guard let user = accountPeer as? TelegramUser, let phoneNumber = user.phone else { + guard case let .user(user) = accountPeer, let phoneNumber = user.phone else { return } guard let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramUser else { @@ -9386,11 +9415,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G commit() case .info: dismissWebAppControllers() - strongSelf.navigationActionDisposable.set((strongSelf.context.account.postbox.loadedPeerWithId(peerId.id) + strongSelf.navigationActionDisposable.set((strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId.id)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> take(1) |> deliverOnMainQueue).startStrict(next: { [weak self] peer in if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil { - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { strongSelf.effectiveNavigationController?.pushViewController(infoController) } } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 3c04c94421..12beb7853f 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -493,7 +493,16 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if case let .messageOptions(_, messageIds, info) = subject { switch info { case let .forward(forward): - let messages = combineLatest(context.account.postbox.messagesAtIds(messageIds), context.account.postbox.loadedPeerWithId(context.account.peerId), forward.options) + let accountPeerSignal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map { $0._asPeer() } + let messages = combineLatest(context.account.postbox.messagesAtIds(messageIds), accountPeerSignal, forward.options) |> map { messages, accountPeer, options -> ([Message], Int32, Bool) in var messages = messages let forwardedMessageIds = Set(messages.map { $0.id }) @@ -582,7 +591,16 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } source = .custom(messages: messages, messageId: MessageId(peerId: PeerId(0), namespace: 0, id: 0), quote: nil, isSavedMusic: false, canReorder: false, loadMore: nil) case let .reply(reply): - let messages = combineLatest(context.account.postbox.messagesAtIds(messageIds), context.account.postbox.loadedPeerWithId(context.account.peerId)) + let replyAccountPeerSignal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map { $0._asPeer() } + let messages = combineLatest(context.account.postbox.messagesAtIds(messageIds), replyAccountPeerSignal) |> map { messages, accountPeer -> ([Message], Int32, Bool) in var messages = messages messages.sort(by: { lhsMessage, rhsMessage in @@ -611,10 +629,19 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { stories = .single([:]) } + let linkAccountPeerSignal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map { $0._asPeer() } if let replyMessageId = options.replyMessageId { return combineLatest( context.account.postbox.messagesAtIds([replyMessageId]), - context.account.postbox.loadedPeerWithId(context.account.peerId), + linkAccountPeerSignal, stories ) |> map { messages, peer, stories -> (ChatControllerSubject.LinkOptions, Peer, Message?, [StoryId: CodableEntry]) in @@ -622,7 +649,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } } else { return combineLatest( - context.account.postbox.loadedPeerWithId(context.account.peerId), + linkAccountPeerSignal, stories ) |> map { peer, stories -> (ChatControllerSubject.LinkOptions, Peer, Message?, [StoryId: CodableEntry]) in @@ -833,9 +860,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.setupHistoryNode() - self.interactiveEmojisDisposable = (self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + self.interactiveEmojisDisposable = (self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> map { preferencesView -> InteractiveEmojiConfiguration in - let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue + let appConfiguration: AppConfiguration = preferencesView?.get(AppConfiguration.self) ?? .defaultValue return InteractiveEmojiConfiguration.with(appConfiguration: appConfiguration) } |> deliverOnMainQueue).startStrict(next: { [weak self] emojis in diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index cca4be7df8..7ff26fd263 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -554,7 +554,7 @@ extension ChatControllerImpl { var media: TelegramMediaContact? switch peer { case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + guard case let .user(contact) = contact, let phoneNumber = contact.phone else { continue } let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") @@ -607,7 +607,7 @@ extension ChatControllerImpl { let dataSignal: Signal<(Peer?, DeviceContactExtendedData?), NoError> switch peer { case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + guard case let .user(contact) = contact, let phoneNumber = contact.phone else { return } let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") @@ -1743,7 +1743,7 @@ extension ChatControllerImpl { var media: TelegramMediaContact? switch peer { case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + guard case let .user(contact) = contact, let phoneNumber = contact.phone else { continue } let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") @@ -1785,7 +1785,7 @@ extension ChatControllerImpl { let dataSignal: Signal<(Peer?, DeviceContactExtendedData?), NoError> switch peer { case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + guard case let .user(contact) = contact, let phoneNumber = contact.phone else { return } let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 4417a7f1bb..b262549202 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -1,11 +1,9 @@ import Foundation import UIKit -import Postbox import SwiftSignalKit import Display import AsyncDisplayKit import TelegramCore -import Postbox import TelegramPresentationData import TelegramUIPreferences import MediaResources @@ -39,6 +37,7 @@ import UrlHandling import TextFormat import ChatNewThreadInfoItem import PhoneNumberFormat +import Postbox struct ChatTopVisibleMessageRange: Equatable { var lowerBound: MessageIndex @@ -1688,16 +1687,16 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId) ), peerReactionSettings, - self.context.account.postbox.preferencesView(keys: [PreferencesKeys.reactionSettings]) + self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.reactionSettings)) ) |> map { peer, peerReactionSettings, preferencesView -> (MessageReaction.Reaction?, Bool) in var areStarsEnabled: Bool = false if let peerReactionSettings, let value = peerReactionSettings.knownValue?.starsAllowed { areStarsEnabled = value } - + let reactionSettings: ReactionSettings - if let entry = preferencesView.values[PreferencesKeys.reactionSettings], let value = entry.get(ReactionSettings.self) { + if let entry = preferencesView, let value = entry.get(ReactionSettings.self) { reactionSettings = value } else { reactionSettings = .default @@ -2529,10 +2528,10 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH } private func beginPresentationDataManagement(updated: Signal) { - let appConfiguration = self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + let appConfiguration = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> take(1) |> map { view in - return view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue + return view?.get(AppConfiguration.self) ?? .defaultValue } var didSetPresentationData = false diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 8248671fb0..71b8ac5d2a 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -44,7 +44,7 @@ private struct MessageContextMenuData { let canPin: Bool let canEdit: Bool let canSelect: Bool - let resourceStatus: MediaResourceStatus? + let resourceStatus: EngineMediaResource.FetchStatus? let messageActions: ChatAvailableMessageActions } @@ -813,9 +813,9 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState |> map(Optional.init) } - var loadResourceStatusSignal: Signal = .single(nil) + var loadResourceStatusSignal: Signal = .single(nil) if let loadCopyMediaResource = loadCopyMediaResource { - loadResourceStatusSignal = context.account.postbox.mediaBox.resourceStatus(loadCopyMediaResource) + loadResourceStatusSignal = context.engine.resources.status(resource: EngineMediaResource(loadCopyMediaResource)) |> take(1) |> map(Optional.init) } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift index 06271060c2..2c880d7c8e 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift @@ -69,9 +69,9 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee signal = .single({ _ in return .stickers([]) }) } - let stickerConfiguration = context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + let stickerConfiguration = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> map { preferencesView -> StickersSearchConfiguration in - let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue + let appConfiguration: AppConfiguration = preferencesView?.get(AppConfiguration.self) ?? .defaultValue return StickersSearchConfiguration.with(appConfiguration: appConfiguration) } let stickerSettings = context.sharedContext.accountManager.transaction { transaction -> StickerSettings in diff --git a/submodules/TelegramUI/Sources/ChatLinkPreview.swift b/submodules/TelegramUI/Sources/ChatLinkPreview.swift index 7dd4ac4884..643451dd21 100644 --- a/submodules/TelegramUI/Sources/ChatLinkPreview.swift +++ b/submodules/TelegramUI/Sources/ChatLinkPreview.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramCore import SwiftSignalKit import TelegramNotices diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsController.swift b/submodules/TelegramUI/Sources/ChatSearchResultsController.swift index c686bf1d6d..9e21477601 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsController.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import SwiftSignalKit import TelegramCore import TelegramPresentationData diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift index 3f2a25e270..19a00689a8 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift @@ -383,7 +383,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection displayCountAlert = true updatedState = updatedState.withToggledPeerId(.peer(peer.id)) } else { - addedToken = EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: accountPeerId, peer: peer, strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(EnginePeer(peer))) + addedToken = EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: accountPeerId, peer: peer._asPeer(), strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(peer)) } } updatedCount = updatedState.selectedPeerIndices.count @@ -400,7 +400,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection state.selectedPeerIds.remove(peer.id) removedTokenId = peer.id } else { - addedToken = EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: accountPeerId, peer: peer, strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(EnginePeer(peer))) + addedToken = EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: accountPeerId, peer: peer._asPeer(), strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(peer)) state.selectedPeerIds.insert(peer.id) } updatedCount = state.selectedPeerIds.count @@ -448,7 +448,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection if !self.params.initialSelectedPeers.isEmpty { for peer in self.params.initialSelectedPeers { - self.contactsNode.openPeer?(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil)) + self.contactsNode.openPeer?(.peer(peer: peer, isGlobal: false, participantCount: nil)) } /*if case let .contacts(contactsNode) = self.contactsNode.contentNode { contactsNode.updateSelectionState { state in @@ -478,7 +478,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection a(.default) if let self { - self.params.sendMessage?(EnginePeer(peer)) + self.params.sendMessage?(peer) } }))) @@ -488,7 +488,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection a(.default) if let self { - self.params.openProfile?(EnginePeer(peer)) + self.params.openProfile?(peer) } }))) diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift index ba2ffa3754..5ff26d31e7 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift @@ -314,7 +314,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { } case let .chats(chatsNode): chatsNode.peerSelected = { [weak self] peer, _, _, _, _ in - self?.openPeer?(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil)) + self?.openPeer?(.peer(peer: peer, isGlobal: false, participantCount: nil)) } chatsNode.additionalCategorySelected = { [weak self] id in guard let strongSelf = self else { @@ -489,7 +489,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { } else if count <= 1 { let callTitle: String if case let .contacts(contactListNode) = self.contentNode, let peer = contactListNode.selectedPeers.first, case let .peer(peer, _, _) = peer { - callTitle = self.presentationData.strings.NewCall_ActionCallSingle(EnginePeer(peer).compactDisplayTitle).string + callTitle = self.presentationData.strings.NewCall_ActionCallSingle(peer.compactDisplayTitle).string } else { callTitle = self.presentationData.strings.NewCall_ActionCallMultiple } diff --git a/submodules/TelegramUI/Sources/ContactSelectionController.swift b/submodules/TelegramUI/Sources/ContactSelectionController.swift index 047253a186..0e304d184e 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionController.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionController.swift @@ -514,7 +514,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController a(.default) if let self { - self.sendMessage?(EnginePeer(peer)) + self.sendMessage?(peer) } }))) @@ -524,7 +524,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController a(.default) if let self { - self.openProfile?(EnginePeer(peer)) + self.openProfile?(peer) } }))) diff --git a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift index de38b13eca..d272c478ae 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift @@ -157,7 +157,7 @@ final class ContactSelectionControllerNode: ASDisplayNode { onlyWriteable: false, isGroupInvitation: false, isPeerEnabled: { peer in - return isPeerEnabled(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil)) + return isPeerEnabled(.peer(peer: peer, isGlobal: false, participantCount: nil)) }, displayCallIcons: displayCallIcons, contextAction: multipleSelection ? { peer, node, gesture, _, _ in @@ -227,7 +227,7 @@ final class ContactSelectionControllerNode: ASDisplayNode { strongSelf.contactListNode.updateSelectionState { state in let peerId = ContactListPeerId.peer(peer.id) let state = state ?? ContactListNodeGroupSelectionState() - return state.withToggledPeerId(peerId).withSelectedPeerMap([peerId: ContactListPeer.peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil)]) + return state.withToggledPeerId(peerId).withSelectedPeerMap([peerId: ContactListPeer.peer(peer: peer, isGlobal: false, participantCount: nil)]) } } } diff --git a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift index ed79bfdbf4..a71bd7b345 100644 --- a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift @@ -97,7 +97,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode private var currentImageResource: TelegramMediaResource? private var currentVideoFile: TelegramMediaFile? private var currentAnimatedStickerFile: TelegramMediaFile? - private var resourceStatus: MediaResourceStatus? + private var resourceStatus: EngineMediaResource.FetchStatus? private(set) var item: HorizontalListContextResultsChatInputPanelItem? private var statusDisposable = MetaDisposable() private let statusNode: RadialStatusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5)) @@ -202,7 +202,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode let sideInset: CGFloat = 4.0 var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - var updatedStatusSignal: Signal? + var updatedStatusSignal: Signal? var imageResource: TelegramMediaResource? var stickerFile: TelegramMediaFile? @@ -223,9 +223,9 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode } if let file = videoFile { - updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(file.resource) + updatedStatusSignal = item.context.engine.resources.status(resource: EngineMediaResource(file.resource)) } else if let imageResource = imageResource { - updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(imageResource) + updatedStatusSignal = item.context.engine.resources.status(resource: EngineMediaResource(imageResource)) } case let .internalReference(internalReference): if let image = internalReference.image { @@ -254,12 +254,12 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode if file.isVideo && file.isAnimated { videoFile = file imageResource = nil - updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(file.resource) + updatedStatusSignal = item.context.engine.resources.status(resource: EngineMediaResource(file.resource)) } else if let imageResource = imageResource { - updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(imageResource) + updatedStatusSignal = item.context.engine.resources.status(resource: EngineMediaResource(imageResource)) } } else if let imageResource = imageResource { - updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(imageResource) + updatedStatusSignal = item.context.engine.resources.status(resource: EngineMediaResource(imageResource)) } } diff --git a/submodules/TelegramUI/Sources/MediaManager.swift b/submodules/TelegramUI/Sources/MediaManager.swift index f838410fdf..d4afb7608d 100644 --- a/submodules/TelegramUI/Sources/MediaManager.swift +++ b/submodules/TelegramUI/Sources/MediaManager.swift @@ -3,7 +3,6 @@ import SwiftSignalKit import AVFoundation import MobileCoreServices import Display -import Postbox import TelegramCore import MediaPlayer import TelegramAudio diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index 0550abb689..b3dbff2477 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -137,13 +137,13 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { params.openUrl(url) return true case let .pass(file): - let _ = (params.context.account.postbox.mediaBox.resourceData(file.resource, option: .complete(waitUntilFetchStatus: true)) + let _ = (params.context.engine.resources.data(resource: EngineMediaResource(file.resource), waitUntilFetchStatus: true) |> take(1) |> deliverOnMainQueue).startStandalone(next: { data in guard let navigationController = params.navigationController else { return } - if data.complete, let content = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { + if data.isComplete, let content = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { if let pass = try? PKPass(data: content), let controller = PKAddPassesViewController(pass: pass) { if let window = navigationController.view.window { controller.popoverPresentationController?.sourceView = window diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 36620abb6e..a1d4e946dc 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -753,9 +753,9 @@ func openResolvedUrlImpl( let loadedData = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) return .single(loadedData) } else { - return context.account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: false) + return context.engine.resources.data(resource: EngineMediaResource(resource)) |> map { next -> Data? in - if next.size > 0, let data = try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []) { + if next.availableSize > 0, let data = try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []) { context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) return data } else { diff --git a/submodules/TelegramUI/Sources/PrefetchManager.swift b/submodules/TelegramUI/Sources/PrefetchManager.swift index 1ab0ba75e7..9e45dce177 100644 --- a/submodules/TelegramUI/Sources/PrefetchManager.swift +++ b/submodules/TelegramUI/Sources/PrefetchManager.swift @@ -52,10 +52,10 @@ private final class PrefetchManagerInnerImpl { } |> distinctUntilChanged - let appConfiguration = account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + let appConfiguration = engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> take(1) |> map { view in - return view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue + return view?.get(AppConfiguration.self) ?? .defaultValue } let orderedPreloadMedia = combineLatest(account.viewTracker.orderedPreloadMedia, TelegramEngine(account: account).stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false), appConfiguration) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 9e0c754208..1b1b46bc76 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -3294,12 +3294,12 @@ public final class SharedAccountContextImpl: SharedAccountContext { )).startStandalone(next: { [weak controller] result, options in if let (peers, _, _, _, _, _) = result, let contactPeer = peers.first, case let .peer(peer, _, _) = contactPeer, let starsContext = context.starsContext { if case .starGiftTransfer = source { - presentTransferAlertImpl?(EnginePeer(peer)) + presentTransferAlertImpl?(peer) } else { let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.DisallowedGifts(id: peer.id)) |> deliverOnMainQueue).start(next: { disallowedGifts in if let disallowedGifts, disallowedGifts == TelegramDisallowedGifts.All && peer.id != context.account.peerId { - let alertController = textAlertController(context: context, title: nil, text: presentationData.strings.Gift_Send_GiftsDisallowed(EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) + let alertController = textAlertController(context: context, title: nil, text: presentationData.strings.Gift_Send_GiftsDisallowed(peer.compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) controller?.present(alertController, in: .window(.root)) return } diff --git a/submodules/TelegramUI/Sources/TextLinkHandling.swift b/submodules/TelegramUI/Sources/TextLinkHandling.swift index 703227e606..5129416aed 100644 --- a/submodules/TelegramUI/Sources/TextLinkHandling.swift +++ b/submodules/TelegramUI/Sources/TextLinkHandling.swift @@ -158,10 +158,17 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n openPeerMentionImpl(mention) case let .hashtag(_, hashtag): if let peerId = peerId { - let peerSignal = context.account.postbox.loadedPeerWithId(peerId) + let peerSignal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } let _ = (peerSignal |> deliverOnMainQueue).start(next: { peer in - let searchController = HashtagSearchController(context: context, peer: EnginePeer(peer), query: hashtag) + let searchController = HashtagSearchController(context: context, peer: peer, query: hashtag) (controller.navigationController as? NavigationController)?.pushViewController(searchController) }) } diff --git a/submodules/WebSearchUI/Sources/LegacyWebSearchEditor.swift b/submodules/WebSearchUI/Sources/LegacyWebSearchEditor.swift index 7649f89917..63d03b4f27 100644 --- a/submodules/WebSearchUI/Sources/LegacyWebSearchEditor.swift +++ b/submodules/WebSearchUI/Sources/LegacyWebSearchEditor.swift @@ -17,9 +17,9 @@ func presentLegacyWebSearchEditor(context: AccountContext, theme: PresentationTh var screenImage: Signal = .single(nil) if let resource = item.thumbnailResource { - screenImage = context.account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: true) + screenImage = context.engine.resources.data(resource: EngineMediaResource(resource), attemptSynchronously: true) |> map { maybeData -> UIImage? in - if maybeData.complete { + if maybeData.isComplete { if let loadedData = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []), let image = UIImage(data: loadedData) { return image } diff --git a/submodules/WebSearchUI/Sources/WebSearchController.swift b/submodules/WebSearchUI/Sources/WebSearchController.swift index 9e960e9a63..86db6a2bad 100644 --- a/submodules/WebSearchUI/Sources/WebSearchController.swift +++ b/submodules/WebSearchUI/Sources/WebSearchController.swift @@ -161,9 +161,9 @@ public final class WebSearchController: ViewController { } } - let gifProvider = self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) - |> map { view -> String? in - guard let appConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) else { + let gifProvider = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) + |> map { preferencesEntry -> String? in + guard let appConfiguration = preferencesEntry?.get(AppConfiguration.self) else { return nil } let configuration = WebSearchConfiguration(appConfiguration: appConfiguration) diff --git a/submodules/WebSearchUI/Sources/WebSearchVideoGalleryItem.swift b/submodules/WebSearchUI/Sources/WebSearchVideoGalleryItem.swift index 676cce6782..96a38ad37d 100644 --- a/submodules/WebSearchUI/Sources/WebSearchVideoGalleryItem.swift +++ b/submodules/WebSearchUI/Sources/WebSearchVideoGalleryItem.swift @@ -176,10 +176,7 @@ final class WebSearchVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.requiresDownload = true var mediaFileStatus: Signal = .single(nil) if let mediaResource = mediaResource { - mediaFileStatus = item.context.account.postbox.mediaBox.resourceStatus(mediaResource._asResource()) - |> map { status in - return EngineMediaResource.FetchStatus(status) - } + mediaFileStatus = item.context.engine.resources.status(resource: mediaResource) |> map(Optional.init) }