Postbox -> TelegramEngine waves 37-43 + wave 44 design/plan (squashed)
Squashes 20 commits — the implementation and outcome commits of
waves 37 through 43 plus wave 44's spec and implementation-plan
docs — into a single commit. Per-wave lessons remain recorded in
docs/superpowers/postbox-refactor-log.md. The unrelated "Add swift
svg" commit is preserved separately outside this squash.
Wave 37 — peerTokenTitle: peer Peer → EnginePeer (1 file)
Wave 38 — canSendMessagesToPeer: peer Peer → EnginePeer (12 files)
Wave 39 — AccountContext.makePeerInfoController: peer Peer → EnginePeer (52 files)
Wave 40 — makeChatQrCodeScreen + makeChatRecentActionsController bundle (8 files)
Wave 41 — RenderedChannelParticipant.peer: Peer → EnginePeer (28 files)
Wave 42 — PeerInfoScreenData.peer: Peer? → EnginePeer? (17 files)
Wave 43 — PeerInfoScreen 6 helpers: peer Peer? → EnginePeer? (12 files)
Wave 44 — RenderedChannelParticipant.peers design doc + implementation plan
(impl and outcome land in subsequent commits, not part of squash)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e51fef57d8
commit
493f3103b3
121 changed files with 5298 additions and 350 deletions
|
|
@ -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-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.
|
||||
Waves landed so far (as of 2026-04-24): 43 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
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,411 @@
|
|||
# Wave 40 — `makeChatQrCodeScreen` + `makeChatRecentActionsController` 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:** Bundle migrate two sibling `AccountContext` methods deferred from wave 39 — `makeChatQrCodeScreen` (4 consumer sites) and `makeChatRecentActionsController` (3 consumer sites) — from raw `peer: Peer` to `peer: EnginePeer`, applying the body-shadow pattern.
|
||||
|
||||
**Architecture:** Body-shadow pattern (wave-38/39 style). Protocol + impl signatures change to `peer: EnginePeer`; each impl body gets a `let peer = peer._asPeer()` shadow so the downstream constructors (`ChatQrCodeScreenImpl`, `ChatRecentActionsController`) remain raw-`Peer` consumers (out of scope).
|
||||
|
||||
**Tech Stack:** Swift, Bazel, iOS; TelegramCore / AccountContext / TelegramUI / PeerInfoUI / StatisticsUI / SettingsUI / ContactListUI / PeerInfoScreen submodules.
|
||||
|
||||
**Reference:** Wave-39 "Out of scope" section in `docs/superpowers/specs/2026-04-24-makePeerInfoController-engine-peer-migration-design.md`.
|
||||
|
||||
---
|
||||
|
||||
## Pre-flight classification
|
||||
|
||||
**`makeChatQrCodeScreen` (4 consumer sites):**
|
||||
|
||||
| # | Site | Shape | Edit |
|
||||
|---|---|---|---|
|
||||
| 1 | `SettingsSearchableItems.swift:974` | **Shape-A-variant** | Rewrite upstream `guard let peer = peer?._asPeer() else { return }` (line 971) → `guard let peer = peer else { return }`. Call stays `peer: peer`. |
|
||||
| 2 | `SettingsSearchableItems.swift:992` | **Shape-A-variant** | Same pattern as #1 (upstream guard at line 989). |
|
||||
| 3 | `ContactsController.swift:478` | **Shape-A** | Drop `._asPeer()` from `peer: peer._asPeer()` → `peer: peer`. Source: `Signal<EnginePeer, NoError>`. |
|
||||
| 4 | `PeerInfoScreen.swift:4623` | **Shape-C** | Wrap: `peer: peer` → `peer: EnginePeer(peer)`. Source: `data.peer: Peer?`. |
|
||||
|
||||
**`makeChatRecentActionsController` (3 consumer sites):**
|
||||
|
||||
| # | Site | Shape | Edit |
|
||||
|---|---|---|---|
|
||||
| 5 | `ChannelAdminsController.swift:734` | **Shape-A** | Drop `._asPeer()`. Source: `engine.data.get(Peer.Peer(id:))` — `peer` is `EnginePeer` in the `guard let peer` on line 729. |
|
||||
| 6 | `GroupStatsController.swift:915` | **Shape-A** | Drop `._asPeer()`. Source: `Signal<EnginePeer, NoError>` (mapToSignal at 906). |
|
||||
| 7 | `PeerInfoScreenOpenChat.swift:115` | **Shape-C** | Wrap: `peer: peer` → `peer: EnginePeer(peer)`. Source: `self.data?.peer: Peer?`. |
|
||||
|
||||
**Net bridge delta:** −5 `_asPeer()` drops (sites 1, 2, 3, 5, 6) + 2 `EnginePeer(...)` wraps (sites 4, 7) = **−3 net**. Sites 4 and 7 become ratchet markers for a future `PeerInfoScreenData.peer Peer → EnginePeer` wave.
|
||||
|
||||
---
|
||||
|
||||
## File touch summary
|
||||
|
||||
8 files:
|
||||
|
||||
1. `submodules/AccountContext/Sources/AccountContext.swift` — protocol decls (2 lines).
|
||||
2. `submodules/TelegramUI/Sources/SharedAccountContext.swift` — impl signatures + body shadows (2 sites).
|
||||
3. `submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift` — 2 Shape-A-variant upstream guard rewrites.
|
||||
4. `submodules/ContactListUI/Sources/ContactsController.swift` — 1 Shape-A drop.
|
||||
5. `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift` — 1 Shape-C wrap.
|
||||
6. `submodules/PeerInfoUI/Sources/ChannelAdminsController.swift` — 1 Shape-A drop.
|
||||
7. `submodules/StatisticsUI/Sources/GroupStatsController.swift` — 1 Shape-A drop.
|
||||
8. `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenChat.swift` — 1 Shape-C wrap.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Update `AccountContext` protocol signatures
|
||||
|
||||
**Files:**
|
||||
- Modify: `submodules/AccountContext/Sources/AccountContext.swift:1401` and `:1461`
|
||||
|
||||
- [ ] **Step 1: Update `makeChatRecentActionsController` decl**
|
||||
|
||||
```swift
|
||||
// old_string
|
||||
func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?, starsState: StarsRevenueStats?) -> ViewController
|
||||
|
||||
// new_string
|
||||
func makeChatRecentActionsController(context: AccountContext, peer: EnginePeer, adminPeerId: PeerId?, starsState: StarsRevenueStats?) -> ViewController
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update `makeChatQrCodeScreen` decl**
|
||||
|
||||
```swift
|
||||
// old_string
|
||||
func makeChatQrCodeScreen(context: AccountContext, peer: Peer, threadId: Int64?, temporary: Bool) -> ViewController
|
||||
|
||||
// new_string
|
||||
func makeChatQrCodeScreen(context: AccountContext, peer: EnginePeer, threadId: Int64?, temporary: Bool) -> ViewController
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Update `SharedAccountContext` impls with body-shadow
|
||||
|
||||
**Files:**
|
||||
- Modify: `submodules/TelegramUI/Sources/SharedAccountContext.swift:2302` (makeChatRecentActionsController)
|
||||
- Modify: `submodules/TelegramUI/Sources/SharedAccountContext.swift:2730` (makeChatQrCodeScreen)
|
||||
|
||||
- [ ] **Step 1: Update `makeChatRecentActionsController` impl**
|
||||
|
||||
```swift
|
||||
// old_string
|
||||
public func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?, starsState: StarsRevenueStats?) -> ViewController {
|
||||
return ChatRecentActionsController(context: context, peer: peer, adminPeerId: adminPeerId, starsState: starsState)
|
||||
}
|
||||
|
||||
// new_string
|
||||
public func makeChatRecentActionsController(context: AccountContext, peer: EnginePeer, adminPeerId: PeerId?, starsState: StarsRevenueStats?) -> ViewController {
|
||||
let peer = peer._asPeer()
|
||||
return ChatRecentActionsController(context: context, peer: peer, adminPeerId: adminPeerId, starsState: starsState)
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update `makeChatQrCodeScreen` impl**
|
||||
|
||||
```swift
|
||||
// old_string
|
||||
public func makeChatQrCodeScreen(context: AccountContext, peer: Peer, threadId: Int64?, temporary: Bool) -> ViewController {
|
||||
return ChatQrCodeScreenImpl(context: context, subject: .peer(peer: peer, threadId: threadId, temporary: temporary))
|
||||
}
|
||||
|
||||
// new_string
|
||||
public func makeChatQrCodeScreen(context: AccountContext, peer: EnginePeer, threadId: Int64?, temporary: Bool) -> ViewController {
|
||||
let peer = peer._asPeer()
|
||||
return ChatQrCodeScreenImpl(context: context, subject: .peer(peer: peer, threadId: threadId, temporary: temporary))
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: `SettingsSearchableItems.swift` — two Shape-A-variant guard rewrites
|
||||
|
||||
**Files:**
|
||||
- Modify: `submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift:971` and `:989`
|
||||
|
||||
Both sites share the same structure: an upstream `guard let peer = peer?._asPeer() else { return }` unwraps `EnginePeer?` to `Peer`. Rewrite the guard to keep the local as `EnginePeer`; the call site below stays unchanged.
|
||||
|
||||
- [ ] **Step 1: Rewrite guard at line 971 (qr-code item)**
|
||||
|
||||
```swift
|
||||
// old_string
|
||||
present: { context, _, present in
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer = peer?._asPeer() else {
|
||||
return
|
||||
}
|
||||
let controller = context.sharedContext.makeChatQrCodeScreen(context: context, peer: peer, threadId: nil, temporary: false)
|
||||
present(.push, controller)
|
||||
})
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
//TODO:fix
|
||||
items.append(
|
||||
SettingsSearchableItem(
|
||||
id: "qr-code/share",
|
||||
|
||||
// new_string
|
||||
present: { context, _, present in
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer = peer else {
|
||||
return
|
||||
}
|
||||
let controller = context.sharedContext.makeChatQrCodeScreen(context: context, peer: peer, threadId: nil, temporary: false)
|
||||
present(.push, controller)
|
||||
})
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
//TODO:fix
|
||||
items.append(
|
||||
SettingsSearchableItem(
|
||||
id: "qr-code/share",
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Rewrite guard at line 989 (qr-code/share item)**
|
||||
|
||||
```swift
|
||||
// old_string
|
||||
id: "qr-code/share",
|
||||
isVisible: false,
|
||||
present: { context, _, present in
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer = peer?._asPeer() else {
|
||||
return
|
||||
}
|
||||
let controller = context.sharedContext.makeChatQrCodeScreen(context: context, peer: peer, threadId: nil, temporary: false)
|
||||
present(.push, controller)
|
||||
})
|
||||
}
|
||||
|
||||
// new_string
|
||||
id: "qr-code/share",
|
||||
isVisible: false,
|
||||
present: { context, _, present in
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer = peer else {
|
||||
return
|
||||
}
|
||||
let controller = context.sharedContext.makeChatQrCodeScreen(context: context, peer: peer, threadId: nil, temporary: false)
|
||||
present(.push, controller)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: `ContactsController.swift` — Shape-A drop
|
||||
|
||||
**Files:**
|
||||
- Modify: `submodules/ContactListUI/Sources/ContactsController.swift:478`
|
||||
|
||||
- [ ] **Step 1: Drop `._asPeer()` at the call site**
|
||||
|
||||
```swift
|
||||
// old_string
|
||||
controller.present(strongSelf.context.sharedContext.makeChatQrCodeScreen(context: strongSelf.context, peer: peer._asPeer(), threadId: nil, temporary: false), in: .window(.root))
|
||||
|
||||
// new_string
|
||||
controller.present(strongSelf.context.sharedContext.makeChatQrCodeScreen(context: strongSelf.context, peer: peer, threadId: nil, temporary: false), in: .window(.root))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: `PeerInfoScreen.swift` — Shape-C wrap
|
||||
|
||||
**Files:**
|
||||
- Modify: `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift:4623`
|
||||
|
||||
Local `peer` comes from `data.peer` (type `Peer`). Wrap at the call site.
|
||||
|
||||
- [ ] **Step 1: Wrap with `EnginePeer(...)`**
|
||||
|
||||
```swift
|
||||
// old_string
|
||||
let qrController = self.context.sharedContext.makeChatQrCodeScreen(context: self.context, peer: peer, threadId: threadId, temporary: temporary)
|
||||
|
||||
// new_string
|
||||
let qrController = self.context.sharedContext.makeChatQrCodeScreen(context: self.context, peer: EnginePeer(peer), threadId: threadId, temporary: temporary)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: `ChannelAdminsController.swift` — Shape-A drop
|
||||
|
||||
**Files:**
|
||||
- Modify: `submodules/PeerInfoUI/Sources/ChannelAdminsController.swift:734`
|
||||
|
||||
Local `peer` is `EnginePeer` (from `guard let peer` on :729 unwrapping `EnginePeer?` from `engine.data.get(Peer.Peer(id:))`).
|
||||
|
||||
- [ ] **Step 1: Drop `._asPeer()`**
|
||||
|
||||
```swift
|
||||
// old_string
|
||||
pushControllerImpl?(context.sharedContext.makeChatRecentActionsController(context: context, peer: peer._asPeer(), adminPeerId: nil, starsState: nil))
|
||||
|
||||
// new_string
|
||||
pushControllerImpl?(context.sharedContext.makeChatRecentActionsController(context: context, peer: peer, adminPeerId: nil, starsState: nil))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: `GroupStatsController.swift` — Shape-A drop
|
||||
|
||||
**Files:**
|
||||
- Modify: `submodules/StatisticsUI/Sources/GroupStatsController.swift:915`
|
||||
|
||||
Local `peer` is `EnginePeer` (from `Signal<EnginePeer, NoError>` via the `mapToSignal` at :906).
|
||||
|
||||
- [ ] **Step 1: Drop `._asPeer()`**
|
||||
|
||||
```swift
|
||||
// old_string
|
||||
let controller = context.sharedContext.makeChatRecentActionsController(context: context, peer: peer._asPeer(), adminPeerId: participantPeerId, starsState: nil)
|
||||
|
||||
// new_string
|
||||
let controller = context.sharedContext.makeChatRecentActionsController(context: context, peer: peer, adminPeerId: participantPeerId, starsState: nil)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8: `PeerInfoScreenOpenChat.swift` — Shape-C wrap
|
||||
|
||||
**Files:**
|
||||
- Modify: `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenChat.swift:115`
|
||||
|
||||
Local `peer` comes from `self.data?.peer` (type `Peer`). Wrap at the call site.
|
||||
|
||||
- [ ] **Step 1: Wrap with `EnginePeer(...)`**
|
||||
|
||||
```swift
|
||||
// old_string
|
||||
let controller = self.context.sharedContext.makeChatRecentActionsController(context: self.context, peer: peer, adminPeerId: nil, starsState: self.data?.starsRevenueStatsState)
|
||||
|
||||
// new_string
|
||||
let controller = self.context.sharedContext.makeChatRecentActionsController(context: self.context, peer: EnginePeer(peer), adminPeerId: nil, starsState: self.data?.starsRevenueStatsState)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 9: Build + iterate
|
||||
|
||||
- [ ] **Step 1: Run full build**
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Expected: first-pass-clean (wave 39's 50-file wave landed first-pass-clean; this 8-file wave should too).
|
||||
|
||||
- [ ] **Step 2: If errors, iterate**
|
||||
|
||||
Each error should point at a call site the plan missed. Fix, re-run. Do not widen the scope — if a call site not in the classification table above appears as an error, investigate whether the memory/inventory was stale.
|
||||
|
||||
---
|
||||
|
||||
### Task 10: Verify no residue
|
||||
|
||||
- [ ] **Step 1: Grep for raw-`Peer` sites**
|
||||
|
||||
```bash
|
||||
grep -rn "makeChatQrCodeScreen\|makeChatRecentActionsController" --include="*.swift" submodules/
|
||||
```
|
||||
|
||||
Expected output: 2 protocol-decl lines (AccountContext.swift), 2 impl-decl lines (SharedAccountContext.swift), and exactly 7 consumer sites — all with `peer: peer`, `peer: EnginePeer(peer)`, or similar (no `peer: x._asPeer()` remaining for these two methods).
|
||||
|
||||
---
|
||||
|
||||
### Task 11: Commit + update refactor log
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/superpowers/postbox-refactor-log.md` — append wave-40 outcome section.
|
||||
|
||||
- [ ] **Step 1: Stage exactly these 8 files (enumerate, do not use `git add -u`)**
|
||||
|
||||
```bash
|
||||
git add \
|
||||
submodules/AccountContext/Sources/AccountContext.swift \
|
||||
submodules/TelegramUI/Sources/SharedAccountContext.swift \
|
||||
submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift \
|
||||
submodules/ContactListUI/Sources/ContactsController.swift \
|
||||
submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift \
|
||||
submodules/PeerInfoUI/Sources/ChannelAdminsController.swift \
|
||||
submodules/StatisticsUI/Sources/GroupStatsController.swift \
|
||||
submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenChat.swift \
|
||||
docs/superpowers/plans/2026-04-24-makeChatQrCodeScreen-recentActions-engine-peer-migration.md
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify staging with `git status --short`**
|
||||
|
||||
Verify only the 9 files above are staged. If other files appear (e.g. `ChatMessageTransitionNode.swift` WIP, `sourcekit-bazel-bsp` submodule marker) — reset them out of the index with `git restore --staged <file>` and re-check.
|
||||
|
||||
- [ ] **Step 3: Commit (wave 40)**
|
||||
|
||||
```bash
|
||||
git commit -m "$(cat <<'EOF'
|
||||
Postbox -> TelegramEngine wave 40
|
||||
|
||||
makeChatQrCodeScreen + makeChatRecentActionsController peer Peer->EnginePeer.
|
||||
|
||||
- AccountContext protocol: 2 decls updated
|
||||
- SharedAccountContext impls: 2 signatures + 2 body-shadow `let peer = peer._asPeer()`
|
||||
- 5 Shape-A `._asPeer()` drops (SettingsSearchableItems x2 guard-variant, ContactsController, ChannelAdminsController, GroupStatsController)
|
||||
- 2 Shape-C `EnginePeer(peer)` wraps (PeerInfoScreen, PeerInfoScreenOpenChat)
|
||||
- Net: -3 bridges
|
||||
|
||||
Sibling follow-up to wave 39 (makePeerInfoController). Pre-flight classification
|
||||
completed in wave-39 design doc's "Out of scope" section.
|
||||
|
||||
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Log wave 40 outcome**
|
||||
|
||||
Append a new section to `docs/superpowers/postbox-refactor-log.md`:
|
||||
|
||||
```markdown
|
||||
## Wave 40 outcome
|
||||
|
||||
Commit: `<hash>`. Bundle of `AccountContext.makeChatQrCodeScreen` + `makeChatRecentActionsController` peer `Peer → EnginePeer`. 8 files / ~12 lines changed. Pre-flight classification from wave-39 design doc held: 5 Shape-A drops + 2 Shape-C wraps + 2 impl body-shadows + 2 protocol decls. Net −3 bridges. Build outcome: <first-pass-clean | N iterations>.
|
||||
|
||||
Sibling follow-up to wave 39 — completes the "Option 1 cluster" (makePeerInfoController family from wave-38 memory). Ratchet markers installed at PeerInfoScreen:4623 and PeerInfoScreenOpenChat:115 for a future `PeerInfoScreenData.peer Peer → EnginePeer` wave.
|
||||
```
|
||||
|
||||
Then commit the log update:
|
||||
|
||||
```bash
|
||||
git add docs/superpowers/postbox-refactor-log.md
|
||||
git commit -m "$(cat <<'EOF'
|
||||
docs: log wave 40 outcome
|
||||
|
||||
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Update memory**
|
||||
|
||||
Update `/Users/isaac/.claude/projects/-Users-isaac-build-telegram-telegram-ios/memory/project_postbox_refactor_next_wave.md`:
|
||||
- Move wave-40 (this bundle) from "candidates" to "Latest commits".
|
||||
- Bump wave-41 recommendation to RenderedChannelParticipant.peer (Option 3) or RenderedPeer (Option 2).
|
||||
- Add wave-40 lesson if any (e.g. "bundled sibling migration with shared pre-flight is cheap" or similar).
|
||||
|
||||
---
|
||||
|
||||
## Self-review checklist (writing-plans skill)
|
||||
|
||||
- **Spec coverage:** Every site from the memory/wave-39-doc pre-flight is a task. Sites 1+2 → Task 3; Site 3 → Task 4; Site 4 → Task 5; Site 5 → Task 6; Site 6 → Task 7; Site 7 → Task 8. Impl bodies → Task 2. Protocol → Task 1. Build → Task 9. Verify → Task 10. Commit+log → Task 11. ✓
|
||||
- **Placeholders:** None. Every Edit step has exact `old_string` / `new_string`. Commit message and log-update text are spelled out. ✓
|
||||
- **Type consistency:** Both methods take `peer: EnginePeer` everywhere — protocol decl, impl decl, and call sites' parameter passes. ✓
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,658 @@
|
|||
# Wave 43 plan: PeerInfoScreen helpers `peer: Peer?` → `peer: EnginePeer?`
|
||||
|
||||
> **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 six PeerInfoScreen module helpers (`canEditPeerInfo`, `availableActionsForMemberOfPeer`, `peerInfoHeaderActionButtons`, `peerInfoHeaderButtons`, `peerInfoCanEdit`, `peerInfoIsChatMuted`) from `peer: Peer?` to `peer: EnginePeer?`, rewriting internal `as?`/`is` against concrete `TelegramX` subclasses to `case let .x` / `case .x` enum patterns on `EnginePeer`, and updating all 21 call sites to drop wave-42-installed `._asPeer()` / `?._asPeer()` bridges or add `.flatMap(EnginePeer.init)` / `EnginePeer(...)` wraps as appropriate.
|
||||
|
||||
**Architecture:** In-place signature migration following wave-42 precedent — no new typealiases, no engine wrapper structs, no TelegramCore changes. All edits within `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/` (10 files). Single atomic commit.
|
||||
|
||||
**Tech Stack:** Swift, Bazel build, `EnginePeer` enum cases (`.user(TelegramUser)`, `.legacyGroup(TelegramGroup)`, `.channel(TelegramChannel)`, `.secretChat(TelegramSecretChat)`).
|
||||
|
||||
**Preceding waves:** 42 (`PeerInfoScreenData.peer: Peer? → EnginePeer?`) on 2026-04-24. This wave drops ~7 `?._asPeer()` / `._asPeer()` bridges installed then.
|
||||
|
||||
**Expected net wrap change:** ~7 DROPs vs ~12 ADDs → net roughly 0. The headline win is helper-signature migration, not wrap count. Follow-up waves migrating `PeerInfoHeaderEditingContentNode.update`, `PeerInfoEditingAvatarNode.update`, `PeerInfoEditingAvatarOverlayNode.update`, `PeerInfoHeaderNode.update`, `PeerInfoScreenMemberItem.enclosingPeer`, `PeerInfoMembersPane` enclosingPeer param will drop the ADDs introduced here.
|
||||
|
||||
**Build expectation:** 2 iterations likely (per wave-41 lesson — foundational-type migrations rarely first-pass-clean). Hedge for iteration-3 if unexpected property accesses surface.
|
||||
|
||||
---
|
||||
|
||||
## Pre-flight facts (verified from repo 2026-04-24)
|
||||
|
||||
### EnginePeer cases (from `submodules/TelegramCore/Sources/TelegramEngine/Peers/Peer.swift:177`)
|
||||
|
||||
```
|
||||
case user(TelegramUser)
|
||||
case legacyGroup(TelegramGroup)
|
||||
case channel(TelegramChannel)
|
||||
case secretChat(TelegramSecretChat)
|
||||
```
|
||||
|
||||
Forwarded property subset on `EnginePeer` includes `id`, `addressName`, `usernames`, `indexName`, `debugDisplayTitle`, `displayLetters`, `profileImageRepresentations`, `smallProfileImage`, `largeProfileImage`, `isDeleted`, `isScam`, `isFake`, `isVerified`, `isPremium`, `isSubscription`, `isService`, `nameColor`, `verificationIconFileId`, `profileColor`, `effectiveProfileColor`, `emojiStatus`, `backgroundEmojiId`, `profileBackgroundEmojiId`, and (via `LocalizedPeerData/Sources/PeerTitle.swift`) `compactDisplayTitle`, `displayTitle(strings:displayOrder:)`. **Not** forwarded: Peer-specific members like `isCopyProtectionEnabled`, `hasPermission(_:)`, `hasBannedPermission(_:)`, `isDeleted` on user (WAIT: yes forwarded). **Internal helper bodies do not access any non-forwarded Peer members** — all their concrete-type work happens via `as? TelegramX`, which is enum-rewrite territory. Confirmed by reading helper bodies (PeerInfoData.swift:2255–2670).
|
||||
|
||||
### Helper call sites inventory (21 sites across 10 files)
|
||||
|
||||
Running command:
|
||||
|
||||
```bash
|
||||
grep -rn "\bcanEditPeerInfo\b\|\bavailableActionsForMemberOfPeer\b\|\bpeerInfoHeaderActionButtons\b\|\bpeerInfoHeaderButtons\b\|\bpeerInfoCanEdit\b\|\bpeerInfoIsChatMuted\b" submodules/ --include="*.swift" | grep -v PeerInfoData.swift
|
||||
```
|
||||
|
||||
All 21 call sites:
|
||||
|
||||
| # | File | Line | Current arg | Peer var origin | Action |
|
||||
|---|------|------|-------------|-----------------|--------|
|
||||
| 1 | `PeerInfoHeaderNode.swift` | 548 | `peer: peer` | method param `peer: Peer?` | ADD-WRAP: `peer: peer.flatMap(EnginePeer.init)` |
|
||||
| 2 | `PeerInfoHeaderNode.swift` | 549 | `peer: peer` | same | ADD-WRAP |
|
||||
| 3 | `PeerInfoHeaderNode.swift` | 2361 | `peer: peer` | same | ADD-WRAP |
|
||||
| 4 | `PeerInfoEditingAvatarNode.swift` | 66 | `peer: peer` | method param `peer: Peer?` (unwrapped via `guard let peer = peer`) | ADD-WRAP: `peer: EnginePeer(peer)` (peer is non-optional `Peer` here) |
|
||||
| 5 | `PeerInfoEditingAvatarOverlayNode.swift` | 85 | `peer: peer` | method param `peer: Peer?` (unwrapped via `guard let peer = peer`) | ADD-WRAP: `peer: EnginePeer(peer)` |
|
||||
| 6 | `PeerInfoHeaderEditingContentNode.swift` | 59 | `peer: peer` | method param `peer: Peer?` | ADD-WRAP: `peer: peer.flatMap(EnginePeer.init)` |
|
||||
| 7 | `PeerInfoHeaderEditingContentNode.swift` | 88 | `peer: peer` | same | ADD-WRAP |
|
||||
| 8 | `PeerInfoHeaderEditingContentNode.swift` | 93 | `peer: peer` | same | ADD-WRAP |
|
||||
| 9 | `PeerInfoHeaderEditingContentNode.swift` | 159 | `peer: peer` | same | ADD-WRAP |
|
||||
| 10 | `PeerInfoHeaderEditingContentNode.swift` | 162 | `peer: peer` | same | ADD-WRAP |
|
||||
| 11 | `PeerInfoScreenAvatarSetup.swift` | 435 | `peer: peer._asPeer()` | `peer = data.peer` (EnginePeer unwrapped) | DROP: `peer: peer` |
|
||||
| 12 | `PeerInfoScreenPerformButtonAction.swift` | 62 | `peer: self.data?.peer?._asPeer()` | `self.data?.peer` is `EnginePeer?` | DROP: `peer: self.data?.peer` |
|
||||
| 13 | `PeerInfoScreenPerformButtonAction.swift` | 397 | `peer: peer._asPeer()` | `peer = data.peer` unwrapped at line 381 | DROP: `peer: peer` |
|
||||
| 14 | `PeerInfoScreenPerformButtonAction.swift` | 398 | `peer: peer._asPeer()` | same | DROP: `peer: peer` |
|
||||
| 15 | `PeerInfoScreenOpenMember.swift` | 19 | `peer: enclosingPeer._asPeer()` | `enclosingPeer = self.data?.peer` unwrapped at line 14 | DROP: `peer: enclosingPeer` |
|
||||
| 16 | `PeerInfoScreen.swift` | 1905 | `peer: group` | `group: TelegramGroup` from `if case let .legacyGroup(group) = data.peer` | CONVERT: `peer: data.peer` |
|
||||
| 17 | `PeerInfoScreen.swift` | 1961 | `peer: channel` | `channel: TelegramChannel` from `if case let .channel(channel) = data.peer` | CONVERT: `peer: data.peer` |
|
||||
| 18 | `PeerInfoScreen.swift` | 5857 | `peer: self.data?.peer?._asPeer()` | `self.data?.peer` is `EnginePeer?` | DROP: `peer: self.data?.peer` |
|
||||
| 19 | `PeerInfoProfileItems.swift` | 853 | `peer: peer._asPeer()` | `peer = data.peer` unwrapped at line 821 | DROP: `peer: peer` |
|
||||
| 20 | `PeerInfoScreenMemberItem.swift` | 178 | `peer: item.enclosingPeer` | `item.enclosingPeer: Peer` (stored raw) | ADD-WRAP: `peer: EnginePeer(item.enclosingPeer)` |
|
||||
| 21 | `PeerInfoMembersPane.swift` | 139 | `peer: enclosingPeer` | `enclosingPeer: Peer` (local raw) | ADD-WRAP: `peer: EnginePeer(enclosingPeer)` |
|
||||
|
||||
**Summary:** 7 DROPs (sites 11–15, 18, 19), 10 ADD-WRAPs (1–10, 20, 21 = 12 total ADDs), 2 CONVERTs (16, 17 — from concrete-type arg to whole-EnginePeer; no wrap delta but simpler/safer).
|
||||
|
||||
### `is TelegramX` scan on helper bodies
|
||||
|
||||
Only `peerInfoIsChatMuted` has them (PeerInfoData.swift:2641, 2643). Rewrite pattern: `if peer is TelegramUser` → `if case .user = peer`, `else if peer is TelegramGroup` → `else if case .legacyGroup = peer`.
|
||||
|
||||
No `is TelegramX` checks exist at call sites for these specific helpers (wave-42 would have caught them since call-site `data.peer` is already `EnginePeer?`).
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
All edits within `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/`:
|
||||
|
||||
**Modify (helper definitions):**
|
||||
- `PeerInfoData.swift:2265–2670` — 6 helper signatures + bodies
|
||||
|
||||
**Modify (call sites):**
|
||||
- `PeerInfoScreen.swift` (3 sites: 1905, 1961, 5857)
|
||||
- `PeerInfoHeaderNode.swift` (3 sites: 548, 549, 2361)
|
||||
- `PeerInfoEditingAvatarNode.swift` (1 site: 66)
|
||||
- `PeerInfoEditingAvatarOverlayNode.swift` (1 site: 85)
|
||||
- `PeerInfoHeaderEditingContentNode.swift` (5 sites: 59, 88, 93, 159, 162)
|
||||
- `PeerInfoScreenAvatarSetup.swift` (1 site: 435)
|
||||
- `PeerInfoScreenPerformButtonAction.swift` (3 sites: 62, 397, 398)
|
||||
- `PeerInfoScreenOpenMember.swift` (1 site: 19)
|
||||
- `PeerInfoProfileItems.swift` (1 site: 853)
|
||||
- `ListItems/PeerInfoScreenMemberItem.swift` (1 site: 178)
|
||||
- `Panes/PeerInfoMembersPane.swift` (1 site: 139)
|
||||
|
||||
Total: 10 files modified (PeerInfoData.swift counts once).
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Migrate the six helper signatures and bodies in `PeerInfoData.swift`
|
||||
|
||||
**Files:**
|
||||
- Modify: `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift:2265–2670`
|
||||
|
||||
- [ ] **Step 1: Migrate `canEditPeerInfo` (line 2265).** Signature: `peer: Peer?` → `peer: EnginePeer?`. Body rewrites:
|
||||
|
||||
```swift
|
||||
// before (line 2269-2287)
|
||||
if let user = peer as? TelegramUser, let botInfo = user.botInfo {
|
||||
return botInfo.flags.contains(.canEdit)
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
if let threadData = threadData {
|
||||
if chatLocation.threadId == 1 {
|
||||
return false
|
||||
}
|
||||
if channel.hasPermission(.manageTopics) {
|
||||
return true
|
||||
}
|
||||
if threadData.author == context.account.peerId {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if channel.hasPermission(.changeInfo) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
switch group.role {
|
||||
case .admin, .creator:
|
||||
return true
|
||||
case .member:
|
||||
break
|
||||
}
|
||||
if !group.hasBannedPermission(.banChangeInfo) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// after
|
||||
if case let .user(user) = peer, let botInfo = user.botInfo {
|
||||
return botInfo.flags.contains(.canEdit)
|
||||
} else if case let .channel(channel) = peer {
|
||||
if let threadData = threadData {
|
||||
if chatLocation.threadId == 1 {
|
||||
return false
|
||||
}
|
||||
if channel.hasPermission(.manageTopics) {
|
||||
return true
|
||||
}
|
||||
if threadData.author == context.account.peerId {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if channel.hasPermission(.changeInfo) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else if case let .legacyGroup(group) = peer {
|
||||
switch group.role {
|
||||
case .admin, .creator:
|
||||
return true
|
||||
case .member:
|
||||
break
|
||||
}
|
||||
if !group.hasBannedPermission(.banChangeInfo) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `if context.account.peerId == peer?.id` line (2266) stays identical (`.id` is forwarded by `EnginePeer`).
|
||||
|
||||
- [ ] **Step 2: Migrate `availableActionsForMemberOfPeer` (line 2314).** Signature: `peer: Peer?` → `peer: EnginePeer?`. Body rewrites — four `peer as? TelegramChannel/TelegramGroup` sites become `case let .channel/.legacyGroup` patterns:
|
||||
|
||||
```swift
|
||||
// Line 2320: if let channel = peer as? TelegramChannel
|
||||
// → : if case let .channel(channel) = peer
|
||||
|
||||
// Line 2324: } else if let group = peer as? TelegramGroup {
|
||||
// → : } else if case let .legacyGroup(group) = peer {
|
||||
|
||||
// Line 2330: if let channel = peer as? TelegramChannel
|
||||
// → : if case let .channel(channel) = peer
|
||||
|
||||
// Line 2374: } else if let group = peer as? TelegramGroup {
|
||||
// → : } else if case let .legacyGroup(group) = peer {
|
||||
```
|
||||
|
||||
The `if peer == nil` check (line 2317) stays identical (Optional == nil works on EnginePeer? too).
|
||||
|
||||
- [ ] **Step 3: Migrate `peerInfoHeaderActionButtons` (line 2434).** Signature: `peer: Peer?` → `peer: EnginePeer?`. Single body rewrite at line 2436:
|
||||
|
||||
```swift
|
||||
// before
|
||||
if !isContact && !isSecretChat, let user = peer as? TelegramUser, user.botInfo == nil {
|
||||
|
||||
// after
|
||||
if !isContact && !isSecretChat, case let .user(user) = peer, user.botInfo == nil {
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Migrate `peerInfoHeaderButtons` (line 2447).** Signature: `peer: Peer?` → `peer: EnginePeer?`. Three body rewrites:
|
||||
|
||||
```swift
|
||||
// Line 2449: if let user = peer as? TelegramUser {
|
||||
// → : if case let .user(user) = peer {
|
||||
|
||||
// Line 2483: } else if let channel = peer as? TelegramChannel {
|
||||
// → : } else if case let .channel(channel) = peer {
|
||||
|
||||
// Line 2558: } else if let group = peer as? TelegramGroup {
|
||||
// → : } else if case let .legacyGroup(group) = peer {
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Migrate `peerInfoCanEdit` (line 2585).** Signature: `peer: Peer?` → `peer: EnginePeer?`. Three body rewrites. Note: original shadows `peer` inside each branch (`let peer = peer as? TelegramX`). Rewrite preserves the shadowing via `case let`:
|
||||
|
||||
```swift
|
||||
// Line 2586: if let user = peer as? TelegramUser {
|
||||
// → : if case let .user(user) = peer {
|
||||
|
||||
// Line 2597: } else if let peer = peer as? TelegramChannel {
|
||||
// → : } else if case let .channel(peer) = peer {
|
||||
// (intentional shadow of outer `peer` with inner `peer: TelegramChannel` — preserved)
|
||||
|
||||
// Line 2618: } else if let peer = peer as? TelegramGroup {
|
||||
// → : } else if case let .legacyGroup(peer) = peer {
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Migrate `peerInfoIsChatMuted` (line 2633).** Outer signature: `peer: Peer?` → `peer: EnginePeer?`. Inner function signature (line 2634) also migrates: `func isPeerMuted(peer: Peer?, ...)` → `func isPeerMuted(peer: EnginePeer?, ...)`. Body rewrites inside the inner function (line 2641–2651):
|
||||
|
||||
```swift
|
||||
// before (line 2641)
|
||||
if peer is TelegramUser {
|
||||
peerIsMuted = !globalNotificationSettings.privateChats.enabled
|
||||
} else if peer is TelegramGroup {
|
||||
peerIsMuted = !globalNotificationSettings.groupChats.enabled
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
switch channel.info {
|
||||
case .group:
|
||||
peerIsMuted = !globalNotificationSettings.groupChats.enabled
|
||||
case .broadcast:
|
||||
peerIsMuted = !globalNotificationSettings.channels.enabled
|
||||
}
|
||||
}
|
||||
|
||||
// after
|
||||
if case .user = peer {
|
||||
peerIsMuted = !globalNotificationSettings.privateChats.enabled
|
||||
} else if case .legacyGroup = peer {
|
||||
peerIsMuted = !globalNotificationSettings.groupChats.enabled
|
||||
} else if case let .channel(channel) = peer {
|
||||
switch channel.info {
|
||||
case .group:
|
||||
peerIsMuted = !globalNotificationSettings.groupChats.enabled
|
||||
case .broadcast:
|
||||
peerIsMuted = !globalNotificationSettings.channels.enabled
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The outer `if let peer = peer` (line 2640) stays unchanged (Optional binding works on EnginePeer?).
|
||||
|
||||
The inner `peerInfoIsChatMuted` body (line 2659–2669) calls `isPeerMuted(peer: peer, ...)` with the outer `peer` (now EnginePeer?) — works without change because inner signature now matches.
|
||||
|
||||
- [ ] **Step 7: Re-read PeerInfoData.swift lines 2265–2670 and visually verify no `as? TelegramX` or `is TelegramX` patterns remain.**
|
||||
|
||||
Run: `grep -n "as? TelegramUser\|as? TelegramChannel\|as? TelegramGroup\|is TelegramUser\|is TelegramChannel\|is TelegramGroup" submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift | awk -F: '$2 >= 2265 && $2 <= 2670'`
|
||||
Expected: empty output.
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Update call sites — DROPs (7 sites)
|
||||
|
||||
**Files:**
|
||||
- Modify: `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift:435`
|
||||
- Modify: `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift:62,397,398`
|
||||
- Modify: `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMember.swift:19`
|
||||
- Modify: `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift:5857`
|
||||
- Modify: `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoProfileItems.swift:853`
|
||||
|
||||
- [ ] **Step 1: DROP at `PeerInfoScreenAvatarSetup.swift:435`.**
|
||||
|
||||
```swift
|
||||
// before
|
||||
guard let data = self.controllerNode.data, let peer = data.peer, mode != .generic || canEditPeerInfo(context: self.context, peer: peer._asPeer(), chatLocation: self.chatLocation, threadData: data.threadData) else {
|
||||
|
||||
// after
|
||||
guard let data = self.controllerNode.data, let peer = data.peer, mode != .generic || canEditPeerInfo(context: self.context, peer: peer, chatLocation: self.chatLocation, threadData: data.threadData) else {
|
||||
```
|
||||
|
||||
- [ ] **Step 2: DROP at `PeerInfoScreenPerformButtonAction.swift:62`.**
|
||||
|
||||
```swift
|
||||
// before
|
||||
let chatIsMuted = peerInfoIsChatMuted(peer: self.data?.peer?._asPeer(), peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings)
|
||||
|
||||
// after
|
||||
let chatIsMuted = peerInfoIsChatMuted(peer: self.data?.peer, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings)
|
||||
```
|
||||
|
||||
- [ ] **Step 3: DROP at `PeerInfoScreenPerformButtonAction.swift:397 and :398` (peerInfoHeaderButtons, two lines, same pattern `peer: peer._asPeer()` → `peer: peer`).**
|
||||
|
||||
Use Edit with `replace_all=true` on the substring `peer: peer._asPeer(), cachedData: data.cachedData` — this exact form appears exactly twice in the file (lines 397, 398), both targets.
|
||||
|
||||
```swift
|
||||
// before (at both 397 and 398)
|
||||
peerInfoHeaderButtons(peer: peer._asPeer(), cachedData: data.cachedData, isOpenedFromChat: ...
|
||||
|
||||
// after
|
||||
peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: ...
|
||||
```
|
||||
|
||||
Verification after edit:
|
||||
|
||||
```bash
|
||||
grep -n "_asPeer()" submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift
|
||||
```
|
||||
Expected: empty (all three DROPs done).
|
||||
|
||||
- [ ] **Step 4: DROP at `PeerInfoScreenOpenMember.swift:19`.**
|
||||
|
||||
```swift
|
||||
// before
|
||||
let actions = availableActionsForMemberOfPeer(accountPeerId: self.context.account.peerId, peer: enclosingPeer._asPeer(), member: member)
|
||||
|
||||
// after
|
||||
let actions = availableActionsForMemberOfPeer(accountPeerId: self.context.account.peerId, peer: enclosingPeer, member: member)
|
||||
```
|
||||
|
||||
- [ ] **Step 5: DROP at `PeerInfoScreen.swift:5857`.**
|
||||
|
||||
```swift
|
||||
// before
|
||||
} else if peerInfoCanEdit(peer: self.data?.peer?._asPeer(), chatLocation: self.chatLocation, threadData: self.data?.threadData, cachedData: self.data?.cachedData, isContact: self.data?.isContact) {
|
||||
|
||||
// after
|
||||
} else if peerInfoCanEdit(peer: self.data?.peer, chatLocation: self.chatLocation, threadData: self.data?.threadData, cachedData: self.data?.cachedData, isContact: self.data?.isContact) {
|
||||
```
|
||||
|
||||
- [ ] **Step 6: DROP at `PeerInfoProfileItems.swift:853`.**
|
||||
|
||||
Only the `availableActionsForMemberOfPeer` call — the sibling `enclosingPeer: peer._asPeer()` at line 852 is NOT a helper-migration target (it's `PeerInfoScreenMemberItem.enclosingPeer: Peer`, unchanged in this wave).
|
||||
|
||||
```swift
|
||||
// before (line 853)
|
||||
let actions = availableActionsForMemberOfPeer(accountPeerId: context.account.peerId, peer: peer._asPeer(), member: member)
|
||||
|
||||
// after
|
||||
let actions = availableActionsForMemberOfPeer(accountPeerId: context.account.peerId, peer: peer, member: member)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Update call sites — CONVERTs (2 sites in PeerInfoScreen.swift)
|
||||
|
||||
**Files:**
|
||||
- Modify: `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift:1905,1961`
|
||||
|
||||
At these sites the helper arg is currently a concrete `TelegramGroup` / `TelegramChannel` extracted via case pattern. After migration the helper takes `EnginePeer?`, so pass `data.peer` directly — the helper re-does the pattern match internally, semantics preserved.
|
||||
|
||||
- [ ] **Step 1: CONVERT at `PeerInfoScreen.swift:1905`.**
|
||||
|
||||
```swift
|
||||
// before
|
||||
} else if case let .legacyGroup(group) = data.peer, canEditPeerInfo(context: strongSelf.context, peer: group, chatLocation: chatLocation, threadData: data.threadData) {
|
||||
|
||||
// after
|
||||
} else if case let .legacyGroup(group) = data.peer, canEditPeerInfo(context: strongSelf.context, peer: data.peer, chatLocation: chatLocation, threadData: data.threadData) {
|
||||
```
|
||||
|
||||
`group` stays bound because the body below still uses it. Only the helper arg changes.
|
||||
|
||||
- [ ] **Step 2: CONVERT at `PeerInfoScreen.swift:1961`.**
|
||||
|
||||
```swift
|
||||
// before
|
||||
} else if case let .channel(channel) = data.peer, canEditPeerInfo(context: strongSelf.context, peer: channel, chatLocation: strongSelf.chatLocation, threadData: data.threadData) {
|
||||
|
||||
// after
|
||||
} else if case let .channel(channel) = data.peer, canEditPeerInfo(context: strongSelf.context, peer: data.peer, chatLocation: strongSelf.chatLocation, threadData: data.threadData) {
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Update call sites — ADD-WRAPs in internal-update methods (10 sites in 4 files)
|
||||
|
||||
These files' internal `.update(peer: Peer?, ...)` methods are NOT migrated in this wave (scope: helpers only). Each helper call inside bridges `peer` (raw `Peer?`) to `EnginePeer?` via `.flatMap(EnginePeer.init)`, or — where `peer` has already been unwrapped to non-optional `Peer` — via `EnginePeer(peer)`.
|
||||
|
||||
**Files:**
|
||||
- Modify: `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift:548,549,2361`
|
||||
- Modify: `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarNode.swift:66`
|
||||
- Modify: `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarOverlayNode.swift:85`
|
||||
- Modify: `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderEditingContentNode.swift:59,88,93,159,162`
|
||||
|
||||
- [ ] **Step 1: ADD-WRAPs at `PeerInfoHeaderNode.swift:548,549,2361`.** At lines 548, 549, the local `peer` is the raw `Peer?` method parameter (line 496). At line 2361 likewise.
|
||||
|
||||
```swift
|
||||
// before (line 548)
|
||||
let actionButtonKeys: [PeerInfoHeaderButtonKey] = (self.isSettings || self.isMyProfile) ? [] : peerInfoHeaderActionButtons(peer: peer, isSecretChat: isSecretChat, isContact: isContact)
|
||||
|
||||
// after
|
||||
let actionButtonKeys: [PeerInfoHeaderButtonKey] = (self.isSettings || self.isMyProfile) ? [] : peerInfoHeaderActionButtons(peer: peer.flatMap(EnginePeer.init), isSecretChat: isSecretChat, isContact: isContact)
|
||||
```
|
||||
|
||||
```swift
|
||||
// before (line 549)
|
||||
let buttonKeys: [PeerInfoHeaderButtonKey] = (self.isSettings || self.isMyProfile) ? [] : peerInfoHeaderButtons(peer: peer, cachedData: cachedData, ...)
|
||||
|
||||
// after
|
||||
let buttonKeys: [PeerInfoHeaderButtonKey] = (self.isSettings || self.isMyProfile) ? [] : peerInfoHeaderButtons(peer: peer.flatMap(EnginePeer.init), cachedData: cachedData, ...)
|
||||
```
|
||||
|
||||
```swift
|
||||
// before (line 2361)
|
||||
let chatIsMuted = peerInfoIsChatMuted(peer: peer, peerNotificationSettings: peerNotificationSettings, threadNotificationSettings: threadNotificationSettings, globalNotificationSettings: globalNotificationSettings)
|
||||
|
||||
// after
|
||||
let chatIsMuted = peerInfoIsChatMuted(peer: peer.flatMap(EnginePeer.init), peerNotificationSettings: peerNotificationSettings, threadNotificationSettings: threadNotificationSettings, globalNotificationSettings: globalNotificationSettings)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: ADD-WRAP at `PeerInfoEditingAvatarNode.swift:66`.** Here `peer` is non-optional `Peer` (unwrapped at line 62: `guard let peer = peer else { return }`). Use `EnginePeer(peer)`.
|
||||
|
||||
```swift
|
||||
// before
|
||||
let canEdit = canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData)
|
||||
|
||||
// after
|
||||
let canEdit = canEditPeerInfo(context: self.context, peer: EnginePeer(peer), chatLocation: chatLocation, threadData: threadData)
|
||||
```
|
||||
|
||||
- [ ] **Step 3: ADD-WRAP at `PeerInfoEditingAvatarOverlayNode.swift:85`.** Same shape — `peer` is non-optional `Peer` (unwrapped at line 64).
|
||||
|
||||
```swift
|
||||
// before
|
||||
if canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData)
|
||||
|
||||
// after
|
||||
if canEditPeerInfo(context: self.context, peer: EnginePeer(peer), chatLocation: chatLocation, threadData: threadData)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: ADD-WRAPs at `PeerInfoHeaderEditingContentNode.swift:59,88,93,159,162`.** Here `peer` is the method's `peer: Peer?` parameter (line 52). Five identical bridge forms.
|
||||
|
||||
For each of lines 59, 88, 93, 159, 162, replace `peer: peer` (inside `canEditPeerInfo(... peer: peer, ...)`) with `peer: peer.flatMap(EnginePeer.init)`.
|
||||
|
||||
The simplest approach: issue five separate Edit calls, each scoped to a unique surrounding substring. Example:
|
||||
|
||||
```swift
|
||||
// before (line 59)
|
||||
if canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData) {
|
||||
|
||||
// after
|
||||
if canEditPeerInfo(context: self.context, peer: peer.flatMap(EnginePeer.init), chatLocation: chatLocation, threadData: threadData) {
|
||||
```
|
||||
|
||||
Note line 59's trailing double-space before `{` in the original — preserve it.
|
||||
|
||||
Lines 88, 93, 159 share an identical surrounding substring `if canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData) {` (no trailing double-space, no `|| isEditableBot`). To avoid collision with line 59, use `replace_all=true` on THIS exact string (matches 88, 93 — wait, 159 uses `isEnabled = canEditPeerInfo(...)`, different prefix). Safer plan: one Edit per line, each with enough surrounding context to be unique. Verify uniqueness after each edit with grep.
|
||||
|
||||
Line 88's surrounding context: inside `if let _ = peer as? TelegramGroup {` branch — preceded by `fieldKeys.append(.title)`.
|
||||
|
||||
Line 93's surrounding context: inside `if let _ = peer as? TelegramChannel {` branch — preceded by `fieldKeys.append(.title)`. Same inner phrase as 88 — so `fieldKeys.append(.title)\n if canEditPeerInfo...` appears twice. Use line-specific context (preceding `else if let _ = peer as?` token).
|
||||
|
||||
Line 159: `isEnabled = canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData)` (no trailing text).
|
||||
|
||||
Line 162: `isEnabled = canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData) || isEditableBot`. Unique — contains ` || isEditableBot`.
|
||||
|
||||
Recommended: five sequential Edits with explicit line disambiguation via surrounding context. Do not bulk-replace-all — the identical `peer: peer, chatLocation: chatLocation, threadData: threadData)` substring appears at all five sites but their line-specific surroundings differ.
|
||||
|
||||
Verification after all five edits:
|
||||
|
||||
```bash
|
||||
grep -c "peer: peer," submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderEditingContentNode.swift
|
||||
```
|
||||
Expected: 0 (no unmigrated call sites remain; other `peer:` occurrences in the file are either type annotations or at the method signature, which uses `peer: Peer?` not `peer: peer`).
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Update call sites — ADD-WRAPs at raw-`Peer` member-item sites (2 sites)
|
||||
|
||||
**Files:**
|
||||
- Modify: `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenMemberItem.swift:178`
|
||||
- Modify: `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift:139`
|
||||
|
||||
At these sites `enclosingPeer` is non-optional `Peer` (raw, stored on the item / local). Wrap with `EnginePeer(...)`.
|
||||
|
||||
- [ ] **Step 1: ADD-WRAP at `PeerInfoScreenMemberItem.swift:178`.**
|
||||
|
||||
```swift
|
||||
// before
|
||||
let actions = availableActionsForMemberOfPeer(accountPeerId: item.context.accountPeerId, peer: item.enclosingPeer, member: item.member)
|
||||
|
||||
// after
|
||||
let actions = availableActionsForMemberOfPeer(accountPeerId: item.context.accountPeerId, peer: EnginePeer(item.enclosingPeer), member: item.member)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: ADD-WRAP at `PeerInfoMembersPane.swift:139`.**
|
||||
|
||||
```swift
|
||||
// before
|
||||
let actions = availableActionsForMemberOfPeer(accountPeerId: context.account.peerId, peer: enclosingPeer, member: member)
|
||||
|
||||
// after
|
||||
let actions = availableActionsForMemberOfPeer(accountPeerId: context.account.peerId, peer: EnginePeer(enclosingPeer), member: member)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Build and iterate
|
||||
|
||||
- [ ] **Step 1: Full project build with `--continueOnError` to surface all errors at once.**
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Expected: likely 2-iteration convergence. Budget up to iteration 3.
|
||||
|
||||
Likely categories of residual errors:
|
||||
|
||||
1. **Missed call sites** — grep-miss from planning. Remediate by adding `.flatMap(EnginePeer.init)` or `EnginePeer(...)` as appropriate.
|
||||
2. **Missed `as? TelegramX` / `is TelegramX` inside helper bodies** — Swift compiler error "cannot convert value of type 'EnginePeer?' to expected argument type 'Peer?'" or warning "'is' test is always false". Fix with `case` pattern.
|
||||
3. **Optional-lifting edge cases** — `if case let .user(user) = peer` may fail if Swift interprets `peer` as non-optional. If so, rewrite as `if let peer, case let .user(user) = peer`.
|
||||
4. **Unused binding warnings** — e.g. `if case let .user(user) = peer` where `user` isn't used inside that branch. Swift's `-warnings-as-errors` (658/665 submodule BUILDs) promotes these. Rewrite as `if case .user = peer`.
|
||||
5. **Unused variable `peer` or `group`/`channel` at CONVERT sites 16, 17** — lines 1905/1961 bind `group`/`channel` in the `case let` pattern; if the body body doesn't use it, Swift emits "value 'group' was never used" which `-warnings-as-errors` promotes to error. Since the body below DOES use them (updatePeerTitle(peerId: group.id, ...)` etc.), this should not trigger — but verify.
|
||||
|
||||
- [ ] **Step 2: For each error category above, apply the correct fix in-place and rebuild. Iterate until green.**
|
||||
|
||||
- [ ] **Step 3: After build is green, run the post-migration grep audit:**
|
||||
|
||||
```bash
|
||||
# Should be empty — no _asPeer() bridges at helper call sites
|
||||
grep -rn "canEditPeerInfo(.*_asPeer\|peerInfoIsChatMuted(.*_asPeer\|peerInfoHeaderButtons(.*_asPeer\|peerInfoHeaderActionButtons(.*_asPeer\|peerInfoCanEdit(.*_asPeer\|availableActionsForMemberOfPeer(.*_asPeer" submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/
|
||||
|
||||
# Should be empty — no concrete-type casts against peer param in helper bodies
|
||||
grep -nE "as\?\s+TelegramUser|as\?\s+TelegramChannel|as\?\s+TelegramGroup|\bis\s+TelegramUser\b|\bis\s+TelegramChannel\b|\bis\s+TelegramGroup\b" submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift | awk -F: '$2 >= 2265 && $2 <= 2670'
|
||||
```
|
||||
|
||||
Expected: both empty.
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Commit
|
||||
|
||||
- [ ] **Step 1: Verify working tree only contains wave-43 edits + pre-existing WIP.**
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
```
|
||||
|
||||
Expected (pre-existing WIP, NOT to be staged):
|
||||
|
||||
```
|
||||
m build-system/bazel-rules/sourcekit-bazel-bsp
|
||||
M submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift
|
||||
?? build-system/tulsi/
|
||||
?? submodules/TgVoip/
|
||||
?? third-party/libx264/
|
||||
```
|
||||
|
||||
Plus wave-43 edits (all under `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/`):
|
||||
|
||||
```
|
||||
M submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift
|
||||
M submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift
|
||||
M submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift
|
||||
M submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarNode.swift
|
||||
M submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarOverlayNode.swift
|
||||
M submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderEditingContentNode.swift
|
||||
M submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift
|
||||
M submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift
|
||||
M submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMember.swift
|
||||
M submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoProfileItems.swift
|
||||
M submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenMemberItem.swift
|
||||
M submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Explicitly stage only the wave-43 files (not the WIP).**
|
||||
|
||||
```bash
|
||||
git add \
|
||||
submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift \
|
||||
submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift \
|
||||
submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift \
|
||||
submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarNode.swift \
|
||||
submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarOverlayNode.swift \
|
||||
submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderEditingContentNode.swift \
|
||||
submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift \
|
||||
submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift \
|
||||
submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMember.swift \
|
||||
submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoProfileItems.swift \
|
||||
submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenMemberItem.swift \
|
||||
submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift \
|
||||
docs/superpowers/plans/2026-04-24-peerinfoscreen-helpers-engine-peer-migration.md
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit.**
|
||||
|
||||
Use a HEREDOC for the message:
|
||||
|
||||
```bash
|
||||
git commit -m "$(cat <<'EOF'
|
||||
Postbox -> TelegramEngine wave 43
|
||||
|
||||
Migrate six PeerInfoScreen helpers (canEditPeerInfo,
|
||||
availableActionsForMemberOfPeer, peerInfoHeaderActionButtons,
|
||||
peerInfoHeaderButtons, peerInfoCanEdit, peerInfoIsChatMuted) from
|
||||
`peer: Peer?` to `peer: EnginePeer?`. Internal `as? TelegramX` /
|
||||
`is TelegramX` patterns rewritten to `case let .x` / `case .x` on
|
||||
EnginePeer enum. All 21 call sites updated in the same commit: 7
|
||||
`._asPeer()` bridges installed by wave 42 dropped; 12
|
||||
`.flatMap(EnginePeer.init)` / `EnginePeer(...)` wraps added at sites
|
||||
whose enclosing methods still take raw Peer?; 2 concrete-type args
|
||||
converted to pass the whole EnginePeer value.
|
||||
|
||||
All edits within submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/.
|
||||
No new engine typealiases. No TelegramCore changes.
|
||||
|
||||
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Verify commit.**
|
||||
|
||||
```bash
|
||||
git log --oneline -1
|
||||
git show --stat HEAD
|
||||
```
|
||||
|
||||
Expected: one commit, ~10 files changed, clean diff.
|
||||
|
||||
---
|
||||
|
||||
## Self-review checklist (run before handoff)
|
||||
|
||||
**Spec coverage:**
|
||||
- All 6 helper signatures migrated (Task 1 steps 1–6). ✓
|
||||
- All 21 call sites touched (Tasks 2–5). ✓
|
||||
- Build iteration explicit (Task 6). ✓
|
||||
- Commit explicit (Task 7). ✓
|
||||
|
||||
**Type consistency:**
|
||||
- Helper signatures all `peer: EnginePeer?` (consistent). ✓
|
||||
- Call-site transforms: DROP/ADD/CONVERT actions match the inventory table. ✓
|
||||
- `EnginePeer.init` constructor used both as `.flatMap(EnginePeer.init)` (Peer? → EnginePeer?) and `EnginePeer(...)` (Peer → EnginePeer) — both are valid (construction overloaded on EnginePeer extension at `TelegramCore/TelegramEngine/Peers/Peer.swift:564`). ✓
|
||||
|
||||
**Placeholder scan:**
|
||||
- No "TBD" / "handle appropriately" / "similar to Task N" language — every step has its concrete code. ✓
|
||||
|
||||
**Risks flagged:**
|
||||
- Wave-41 lesson: foundational-type migrations rarely first-pass-clean. Budget 2 iterations. ✓
|
||||
- Wave-41 lesson: `-warnings-as-errors` promotes always-false `is` checks and unused bindings to build errors. Task 6 step 1 calls these out explicitly. ✓
|
||||
- Wave-42 lesson: `EnginePeer` doesn't forward every Peer property. Helper bodies were verified to access only `.id`, which IS forwarded; other property accesses were on concrete types (`TelegramChannel.hasPermission(...)` etc.) which remain on concrete types post-migration. No forwarding-gap remediation expected in helpers. ✓
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
# Wave 42 plan: `PeerInfoScreenData.peer: Peer? → EnginePeer?`
|
||||
|
||||
Date: 2026-04-24
|
||||
Preceding waves: 41 (`RenderedChannelParticipant.peer`), 40 (`makeChatQrCodeScreen`/`makeChatRecentActionsController`), 39 (`makePeerInfoController`)
|
||||
Scope (confirmed with user): only `PeerInfoScreenData.peer`. Sibling fields (`chatPeer`, `savedMessagesPeer`, `linkedDiscussionPeer`, `linkedMonoforumPeer`) are follow-up-wave candidates.
|
||||
|
||||
## Change target
|
||||
|
||||
File: `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift`
|
||||
|
||||
- L386: `let peer: Peer?` → `let peer: EnginePeer?`
|
||||
- L442: `peer: Peer?,` → `peer: EnginePeer?,`
|
||||
- Store unchanged (`self.peer = peer`)
|
||||
|
||||
## Construction sites (5, all in PeerInfoData.swift)
|
||||
|
||||
| Line | Current `peer:` arg | Rewrite |
|
||||
|------|---------------------|---------|
|
||||
| 1027 | `peer: peer` (local, `Peer?` from `peerView.peers[peerId]`) | `peer: peer.flatMap(EnginePeer.init)` |
|
||||
| 1100 | `peer: nil` | unchanged |
|
||||
| 1620 | `peer: peer` (local, `Peer?` from `peerView.peers[userPeerId]`) | `peer: peer.flatMap(EnginePeer.init)` |
|
||||
| 1867 | `peer: peerView.peers[peerId]` | `peer: peerView.peers[peerId].flatMap(EnginePeer.init)` |
|
||||
| 2205 | `peer: peerView.peers[groupId]` | `peer: peerView.peers[groupId].flatMap(EnginePeer.init)` |
|
||||
|
||||
## Consumer migration patterns (across 18 files, ~114 `data.peer` accesses)
|
||||
|
||||
### Pattern A — as-cast → enum pattern match (~20 sites)
|
||||
|
||||
```swift
|
||||
// before
|
||||
if let user = data.peer as? TelegramUser, user.botInfo == nil { ... }
|
||||
|
||||
// after
|
||||
if case let .user(user) = data.peer, user.botInfo == nil { ... }
|
||||
```
|
||||
|
||||
Scope both sides consistently. A cast inside a larger `guard let ..., let user = ... as? TelegramUser else { return }` becomes `guard ..., case let .user(user) = data.peer else { return }`.
|
||||
|
||||
### Pattern B — `is TelegramXxx` check → enum case pattern (~5 sites)
|
||||
|
||||
The wave-41 lesson: `-warnings-as-errors` catches always-false `is` checks.
|
||||
|
||||
```swift
|
||||
// before
|
||||
if let peer = self.data?.peer, peer is TelegramChannel { ... }
|
||||
if peer is TelegramGroup { ... }
|
||||
|
||||
// after
|
||||
if case .channel = self.data?.peer { ... }
|
||||
if case .legacyGroup = peer { ... }
|
||||
```
|
||||
|
||||
`TelegramGroup` maps to `.legacyGroup`. `TelegramChannel` maps to `.channel`. `TelegramUser` maps to `.user`. `TelegramSecretChat` maps to `.secretChat`.
|
||||
|
||||
Known sites in PeerInfoScreen.swift (inventory): L3981, L4133, L4192, L4194 (and L7421 for `chatPeer`-bound — chatPeer stays raw, so L7421 is out of scope). Use repo grep on `PeerInfoScreen/Sources` with token `is Telegram(Channel|User|Group|SecretChat)` to catch other sites.
|
||||
|
||||
### Pattern C — existing `EnginePeer(peer)` wraps where `peer` was bound from `data.peer` — DROP (15+ sites)
|
||||
|
||||
```swift
|
||||
// before
|
||||
if let peer = self.data?.peer {
|
||||
self.joinChannel(peer: EnginePeer(peer)) // wave-40 wrap
|
||||
}
|
||||
|
||||
// after
|
||||
if let peer = self.data?.peer {
|
||||
self.joinChannel(peer: peer) // peer is now EnginePeer already
|
||||
}
|
||||
```
|
||||
|
||||
Care needed: only drop the wrap where the bound `peer` variable comes from `data.peer`. Wraps on `chatPeer`, `currentPeer`, `user` (bound via `as? TelegramUser`), `groupPeer`, or PeerView lookups stay. The lexical scope makes this judgeable.
|
||||
|
||||
Known drop sites (PeerInfoScreen.swift): 1331, 1339, 1346, 1561, 2353, 2405, 3409, 3459, 3624, 3747, 4306, 4573 (inner — review scope), 4623. PeerInfoHeaderNode.swift: 571, 1218, 2054 (if bound from data.peer). PeerInfoScreenOpenChat.swift: 25, 40, 51, 57, 80, 89, 115. Verify each by backtracking the `if let peer = ...` binding.
|
||||
|
||||
### Pattern D — helper call sites still taking `Peer?` (ADD-WRAP, ~10 sites)
|
||||
|
||||
`canEditPeerInfo`, `peerInfoIsChatMuted`, `peerInfoHeaderButtons`, `peerInfoHeaderActionButtons`, `peerInfoCanEdit`, `availableActionsForMemberOfPeer` all keep `peer: Peer?` in this wave. Call sites must bridge:
|
||||
|
||||
```swift
|
||||
// before
|
||||
peerInfoIsChatMuted(peer: self.data?.peer, ...)
|
||||
|
||||
// after
|
||||
peerInfoIsChatMuted(peer: self.data?.peer?._asPeer(), ...)
|
||||
```
|
||||
|
||||
Site count (from grep): PeerInfoHeaderNode.swift:548/549/2361, PeerInfoScreenAvatarSetup.swift:435, PeerInfoScreenPerformButtonAction.swift:62/397/398, PeerInfoEditingAvatarNode.swift:66, PeerInfoScreen.swift:1905/1961/5857, PeerInfoHeaderEditingContentNode.swift:59/88/93/159/162, PeerInfoEditingAvatarOverlayNode.swift:85. But the local `peer` at some of these is already narrowed via `as? TelegramUser` (now `case let .user(user)`); in that case the helper gets `user` (still `Peer`-conforming), no bridge needed. Bridge only where the raw `data.peer` flows into the helper.
|
||||
|
||||
These ADD-WRAP markers become ratchet-drops for a follow-up wave that migrates the helper signatures.
|
||||
|
||||
### Pattern E — `EnginePeer?` passed as `EnginePeer?` directly (DROP wraps on callback args)
|
||||
|
||||
Where `data.peer` feeds `makePeerInfoController(peer: EnginePeer)` / `chatInterfaceInteraction.openPeer(_ peer: EnginePeer, ...)` / `.peer(EnginePeer)` ChatLocation / `AvatarGalleryController(peer: EnginePeer)` / `makeChatQrCodeScreen(peer: EnginePeer)` / `makeChatRecentActionsController(peer: EnginePeer)` — drop the `EnginePeer(...)` wrap; pass directly.
|
||||
|
||||
### Pattern F — `EnginePeer(peer).displayTitle(...)` / `.compactDisplayTitle` usage (DROP wrap)
|
||||
|
||||
```swift
|
||||
// before
|
||||
EnginePeer(peer).displayTitle(strings: ..., displayOrder: ...)
|
||||
|
||||
// after (peer is now EnginePeer already)
|
||||
peer.displayTitle(strings: ..., displayOrder: ...)
|
||||
```
|
||||
|
||||
### Pattern G — `.isPremium` on `peer?` inside construction site (L1060, L1626, L1902, L2242)
|
||||
|
||||
`peerView.peers[peerId]?.isPremium` — `Peer` protocol exposes `isPremium`. But the construction site receives raw `Peer?` and then we wrap via `flatMap(EnginePeer.init)`. The `peer?.isPremium` in the same construction scope still refers to the *local* raw peer variable (type unchanged), not `self.peer`. **No change needed at construction sites for `.isPremium` accesses on the local raw `peer`.** Only change `.isPremium` accesses on `data.peer` (which is now `EnginePeer?`) — `EnginePeer.isPremium` exists.
|
||||
|
||||
## File-by-file plan
|
||||
|
||||
1. **PeerInfoData.swift** — declaration + init + 5 constructions. Also review L1529 (`peerView.peers[peerView.peerId] is TelegramUser`) — OUT OF SCOPE (not `data.peer`); don't touch. Helper functions L2265/2314/2434/2447/2585/2633 stay `peer: Peer?` — DO NOT TOUCH.
|
||||
|
||||
2. **PeerInfoScreen.swift** — largest consumer, ~70+ sites. Walk every `data.peer` / `data?.peer` / `self.data?.peer` / `self.data.peer`. Apply A/B/C/E/F patterns. For `if let peer = data.peer` bindings, subsequent uses of `peer` now have type `EnginePeer` — drop wraps on those uses.
|
||||
|
||||
3. **PeerInfoScreenOpenChat.swift, PeerInfoScreenOpenBio.swift, PeerInfoScreenOpenMember.swift, PeerInfoScreenOpenPeerInfoContextMenu.swift, PeerInfoScreenOpenUsername.swift, PeerInfoScreenCallActions.swift, PeerInfoScreenMessageActions.swift, PeerInfoScreenPerformButtonAction.swift, PeerInfoScreenAvatarSetup.swift, PeerInfoScreenSettingsActions.swift, PeerInfoScreenDisplayGiftsContextMenu.swift, PeerInfoScreenDisplayMediaGalleryContextMenu.swift** — various `data?.peer as? TelegramXxx` (A), helper bridges (D), wrap drops (C/E).
|
||||
|
||||
4. **PeerInfoPaneContainerNode.swift** — L1252 `as? TelegramChannel` (A).
|
||||
|
||||
5. **PeerInfoProfileItems.swift, PeerInfoSettingsItems.swift, ListItems/PeerInfoScreenPersonalChannelItem.swift** — `data.peer as? TelegramUser` style consumers (A).
|
||||
|
||||
6. **PeerInfoHeaderNode.swift, PeerInfoEditingAvatarNode.swift, PeerInfoEditingAvatarOverlayNode.swift, PeerInfoHeaderEditingContentNode.swift** — these files receive `peer` as a parameter (not directly `data.peer`). Only touch if a parameter type declared as `Peer?` is the field from `data.peer` being passed in; otherwise leave.
|
||||
|
||||
## Replace_all guidance (wave-41 lesson)
|
||||
|
||||
Several wraps repeat identically. Where a file has multiple identical `EnginePeer(peer)` expressions in scopes where `peer` is now `EnginePeer`, use `replace_all=true` on the unique full expression. BUT verify each such file has no same-pattern wrap where `peer` is still raw (chatPeer-bound, currentPeer-bound, etc.) — such wraps must survive.
|
||||
|
||||
Safer alternative: edit each site individually.
|
||||
|
||||
## Out of scope (enumerated)
|
||||
|
||||
- `PeerInfoScreenData.chatPeer`, `.savedMessagesPeer`, `.linkedDiscussionPeer`, `.linkedMonoforumPeer` — stay `Peer?`.
|
||||
- Internal helpers `canEditPeerInfo` / `peerInfoIsChatMuted` / etc. — stay `peer: Peer?`.
|
||||
- `peerView.peers[...]` access inside PeerInfoData.swift — stays raw `Peer?`.
|
||||
- Any `is TelegramXxx` check on a non-`data.peer`-derived variable.
|
||||
|
||||
## Build methodology
|
||||
|
||||
1. Apply declaration + init + construction edits.
|
||||
2. Apply consumer edits file by file.
|
||||
3. `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`
|
||||
4. Iterate on errors. Budget: 2–4 iterations (wave-41 lesson: foundational-type property-access migrations are not first-pass-clean).
|
||||
|
||||
## Expected ratchet math
|
||||
|
||||
- Drops: 15+ wave-40 wraps, ~20 as-cast patterns collapsed, ~5 is-checks rewritten, several `EnginePeer(peer).displayTitle` wraps dropped.
|
||||
- Adds: ~10 `?._asPeer()` helper bridges, 4 `flatMap(EnginePeer.init)` at construction.
|
||||
- Net: ~15–25 bridges dropped.
|
||||
|
||||
## WIP interference check
|
||||
|
||||
Pre-existing WIP in tree (per memory):
|
||||
- `submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift` — modified, unrelated animation WIP. DO NOT TOUCH.
|
||||
- `submodules/TgVoip/`, `third-party/libx264/`, `build-system/tulsi/` — untracked, unrelated.
|
||||
- `build-system/bazel-rules/sourcekit-bazel-bsp` — submodule marker, unrelated.
|
||||
|
||||
Wave-42 files are all in `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/` — no overlap with WIP. Commit with explicit file list (wave-39/41 lesson).
|
||||
|
||||
## Post-commit followups
|
||||
|
||||
- Update `docs/superpowers/postbox-refactor-log.md` with "Wave 42 outcome".
|
||||
- Update `memory/project_postbox_refactor_next_wave.md` with wave 43 candidates:
|
||||
- Wave 42.x sibling: `PeerInfoScreenData.chatPeer` / `.savedMessagesPeer` / `.linkedDiscussionPeer` / `.linkedMonoforumPeer` as a bundle (same file, narrow blast radius).
|
||||
- Wave 42.y: PeerInfo-internal helper signatures (drops the ~10 ADD-WRAP markers).
|
||||
- Option 2 from wave-42 shortlist: `RenderedChannelParticipant.peers` dict.
|
||||
|
|
@ -0,0 +1,395 @@
|
|||
# Postbox → TelegramEngine wave 37: `peerTokenTitle` peer parameter Peer → EnginePeer
|
||||
|
||||
> **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 private free function `peerTokenTitle(accountPeerId: PeerId, peer: Peer, strings:, nameDisplayOrder:)` in `submodules/TelegramUI/Sources/ContactMultiselectionController.swift` so `peer` is `EnginePeer`, dropping 5 `._asPeer()` bridges at call sites in the same file.
|
||||
|
||||
**Architecture:** Single-file, atomic, private-function refactor. No public API change, no BUILD-file touch, no cross-module effects. Function body simplifies `EnginePeer(peer).displayTitle(...)` → `peer.displayTitle(...)`.
|
||||
|
||||
**Tech Stack:** Swift, Bazel via `Make.py` wrapper, Telegram-iOS project conventions (see CLAUDE.md).
|
||||
|
||||
**Reference:** Spec `docs/superpowers/specs/2026-04-24-peertokentitle-engine-peer-migration-design.md`.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
Only one file is touched:
|
||||
|
||||
- **Modify:** `submodules/TelegramUI/Sources/ContactMultiselectionController.swift`
|
||||
- L21 — signature change (`peer: Peer` → `peer: EnginePeer`)
|
||||
- L27 — body simplification (drop redundant `EnginePeer(...)` wrap)
|
||||
- L171, L201, L386, L403, L748 — call-site bridge drops (`peer: peer._asPeer()` → `peer: peer`)
|
||||
|
||||
No files created. No files deleted. No BUILD files touched.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Pre-flight inventory verification
|
||||
|
||||
**Files:** None (grep-only).
|
||||
|
||||
- [ ] **Step 1: Confirm the function is private and single-file**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
grep -rn "peerTokenTitle" submodules/ Telegram/ third-party/ --include="*.swift"
|
||||
```
|
||||
|
||||
Expected: exactly 6 matches, all in `submodules/TelegramUI/Sources/ContactMultiselectionController.swift` — 1 definition at L21 and 5 call sites at L171, L201, L386, L403, L748.
|
||||
|
||||
If any match appears outside this file, **stop and re-evaluate scope**: the function may not actually be private or another file has copy-pasted the name.
|
||||
|
||||
- [ ] **Step 2: Confirm all 5 call sites currently use `._asPeer()`**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
grep -n "peerTokenTitle(.*_asPeer())" submodules/TelegramUI/Sources/ContactMultiselectionController.swift
|
||||
```
|
||||
|
||||
Expected: 5 matches, line numbers 171, 201, 386, 403, 748.
|
||||
|
||||
If the count is not 5, **stop and re-inventory** — a prior change may have shifted line numbers or altered a call site.
|
||||
|
||||
- [ ] **Step 3: Confirm no other `peerTokenTitle` overload exists**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
grep -n "func peerTokenTitle" submodules/TelegramUI/Sources/ContactMultiselectionController.swift
|
||||
```
|
||||
|
||||
Expected: exactly 1 match at line 21 (`private func peerTokenTitle(...)`).
|
||||
|
||||
- [ ] **Step 4: Confirm `EnginePeer.displayTitle(strings:displayOrder:)` exists**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
grep -rn "func displayTitle(strings:" submodules/TelegramCore/Sources/TelegramEngine/ submodules/TelegramCore/Sources/SyncCore/
|
||||
```
|
||||
|
||||
Expected: a match on `EnginePeer` extension exposing `displayTitle(strings: PresentationStrings, displayOrder: PresentationPersonNameOrder)`. (This is the method already called as `EnginePeer(peer).displayTitle(...)` at L27, so its existence is certain — this step just makes the dependency explicit.)
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Edit the function signature and body
|
||||
|
||||
**Files:**
|
||||
- Modify: `submodules/TelegramUI/Sources/ContactMultiselectionController.swift:21-29`
|
||||
|
||||
- [ ] **Step 1: Read the current function definition**
|
||||
|
||||
Read the file, lines 21–29. Current state:
|
||||
|
||||
```swift
|
||||
private func peerTokenTitle(accountPeerId: PeerId, peer: Peer, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) -> String {
|
||||
if peer.id == accountPeerId {
|
||||
return strings.DialogList_SavedMessages
|
||||
} else if peer.id.isReplies {
|
||||
return strings.DialogList_Replies
|
||||
} else {
|
||||
return EnginePeer(peer).displayTitle(strings: strings, displayOrder: nameDisplayOrder)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Apply the signature change**
|
||||
|
||||
Use Edit with:
|
||||
|
||||
- `old_string`:
|
||||
```
|
||||
private func peerTokenTitle(accountPeerId: PeerId, peer: Peer, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) -> String {
|
||||
if peer.id == accountPeerId {
|
||||
return strings.DialogList_SavedMessages
|
||||
} else if peer.id.isReplies {
|
||||
return strings.DialogList_Replies
|
||||
} else {
|
||||
return EnginePeer(peer).displayTitle(strings: strings, displayOrder: nameDisplayOrder)
|
||||
}
|
||||
}
|
||||
```
|
||||
- `new_string`:
|
||||
```
|
||||
private func peerTokenTitle(accountPeerId: PeerId, peer: EnginePeer, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) -> String {
|
||||
if peer.id == accountPeerId {
|
||||
return strings.DialogList_SavedMessages
|
||||
} else if peer.id.isReplies {
|
||||
return strings.DialogList_Replies
|
||||
} else {
|
||||
return peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: `accountPeerId: PeerId` stays as-is — `PeerId` is already the typealias for `EnginePeer.Id`. `peer.id.isReplies` works unchanged because `EnginePeer.Id` exposes `isReplies`.
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Drop `._asPeer()` bridges at all 5 call sites
|
||||
|
||||
**Files:**
|
||||
- Modify: `submodules/TelegramUI/Sources/ContactMultiselectionController.swift` (L171, L201, L386, L403, L748)
|
||||
|
||||
All 5 call sites have an identical argument fragment:
|
||||
|
||||
```
|
||||
peer: peer._asPeer(),
|
||||
```
|
||||
|
||||
…which must become:
|
||||
|
||||
```
|
||||
peer: peer,
|
||||
```
|
||||
|
||||
The surrounding context differs per site (two distinct `strings/nameDisplayOrder` chains, see below), so we handle the substitution in two batches.
|
||||
|
||||
- [ ] **Step 1: Replace sites L171, L201, L748 (use `strongSelf.presentationData.strings` / `strongSelf.presentationData.nameDisplayOrder` or `self.presentationData.strings` / `self.presentationData.nameDisplayOrder`)**
|
||||
|
||||
Three call sites share identical code but with different leading `accountPeerId` expressions. Apply them individually.
|
||||
|
||||
**L171 and L201 are identical** — both read:
|
||||
|
||||
```swift
|
||||
return EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: params.context.account.peerId, peer: peer._asPeer(), strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(peer))
|
||||
```
|
||||
|
||||
Use Edit with `replace_all=true`:
|
||||
|
||||
- `old_string`:
|
||||
```
|
||||
return EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: params.context.account.peerId, peer: peer._asPeer(), strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(peer))
|
||||
```
|
||||
- `new_string`:
|
||||
```
|
||||
return EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: params.context.account.peerId, peer: peer, strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(peer))
|
||||
```
|
||||
|
||||
**L748** reads:
|
||||
|
||||
```swift
|
||||
tokens.append(EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: self.context.account.peerId, peer: peer._asPeer(), strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(peer)))
|
||||
```
|
||||
|
||||
Use Edit (no `replace_all` — this line is unique):
|
||||
|
||||
- `old_string`:
|
||||
```
|
||||
tokens.append(EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: self.context.account.peerId, peer: peer._asPeer(), strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(peer)))
|
||||
```
|
||||
- `new_string`:
|
||||
```
|
||||
tokens.append(EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: self.context.account.peerId, peer: peer, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(peer)))
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Replace sites L386 and L403 (use `accountPeerId` local)**
|
||||
|
||||
**L386 and L403 are identical** — both read:
|
||||
|
||||
```swift
|
||||
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))
|
||||
```
|
||||
|
||||
Use Edit with `replace_all=true`:
|
||||
|
||||
- `old_string`:
|
||||
```
|
||||
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))
|
||||
```
|
||||
- `new_string`:
|
||||
```
|
||||
addedToken = EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: accountPeerId, peer: peer, strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(peer))
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Grep to confirm zero remaining bridge sites**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
grep -n "peerTokenTitle(.*_asPeer())" submodules/TelegramUI/Sources/ContactMultiselectionController.swift
|
||||
```
|
||||
|
||||
Expected: **0 matches**.
|
||||
|
||||
If any match remains, the previous edits missed a line variant — re-read the file around each missed line and apply a targeted Edit for that variant.
|
||||
|
||||
- [ ] **Step 4: Confirm the 5 expected `peer: peer,` call sites now appear**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
grep -n "peerTokenTitle(.*peer: peer," submodules/TelegramUI/Sources/ContactMultiselectionController.swift
|
||||
```
|
||||
|
||||
Expected: 5 matches, line numbers approximately 171, 201, 386, 403, 748 (exact numbers unchanged — the edits don't shift line counts).
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Build verification
|
||||
|
||||
**Files:** None edited in this task.
|
||||
|
||||
- [ ] **Step 1: Run the full project 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
|
||||
```
|
||||
|
||||
Expected: build succeeds with exit code 0 and no compilation errors.
|
||||
|
||||
**If the build fails:**
|
||||
|
||||
1. Inspect the error output. Three failure modes are anticipated (all should be rare given the scope):
|
||||
- **Missing `displayTitle` on `EnginePeer`:** unlikely, since L27 was calling it pre-migration. If it happens, verify the `EnginePeer` import chain — but do not add new imports; this file already imports `TelegramCore`.
|
||||
- **A 6th call site exists** that the pre-flight grep missed (e.g., one using a different string pattern like `peer:peer` with no space, or a multi-line call). Locate it with `grep -n "peerTokenTitle" submodules/TelegramUI/Sources/ContactMultiselectionController.swift` and apply the bridge drop manually.
|
||||
- **Unrelated type-inference cascade**, e.g., some `peer` local was previously inferred as `Peer` via the callback chain and now can't be. Read the error line and assess: if it's inside the function body or call site, adjust; if it's elsewhere in the file, it was pre-existing and unrelated — still, don't touch it mid-wave. Abandon per wave-rule 5 if scope creep is required.
|
||||
2. Re-run the build after the fix.
|
||||
|
||||
- [ ] **Step 2: Confirm the post-migration grep is clean**
|
||||
|
||||
Run (after successful build):
|
||||
|
||||
```bash
|
||||
grep -n "peerTokenTitle(.*_asPeer())" submodules/TelegramUI/Sources/ContactMultiselectionController.swift
|
||||
```
|
||||
|
||||
Expected: **0 matches**.
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Commit
|
||||
|
||||
**Files:**
|
||||
- `submodules/TelegramUI/Sources/ContactMultiselectionController.swift`
|
||||
|
||||
- [ ] **Step 1: Stage the one file**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git add submodules/TelegramUI/Sources/ContactMultiselectionController.swift
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the staged diff**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git diff --cached --stat
|
||||
```
|
||||
|
||||
Expected: `1 file changed, 6 insertions(+), 6 deletions(-)` (or thereabouts — 1 line's worth of signature change, 1 body-line change, 5 identical call-site changes; each is a 1-line replacement, net zero line-count delta).
|
||||
|
||||
Also run:
|
||||
|
||||
```bash
|
||||
git diff --cached
|
||||
```
|
||||
|
||||
Inspect manually to confirm: (a) the function signature changed `peer: Peer` → `peer: EnginePeer`; (b) the body `EnginePeer(peer).displayTitle(...)` → `peer.displayTitle(...)`; (c) 5 call sites lost `._asPeer()`. No other edits.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git commit -m "$(cat <<'EOF'
|
||||
Postbox -> TelegramEngine wave 37
|
||||
|
||||
peerTokenTitle: peer parameter Peer -> EnginePeer.
|
||||
|
||||
Drops 5 _asPeer() bridges in ContactMultiselectionController.swift
|
||||
(L171, L201, L386, L403, L748) - bridges installed by prior waves.
|
||||
|
||||
Private free function, single-file change.
|
||||
|
||||
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Confirm commit**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git log --oneline -3
|
||||
```
|
||||
|
||||
Expected: the new wave-37 commit at the top.
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Update memory / log
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/isaac/.claude/projects/-Users-isaac-build-telegram-telegram-ios/memory/project_postbox_refactor_next_wave.md`
|
||||
- Modify: `docs/superpowers/postbox-refactor-log.md`
|
||||
|
||||
- [ ] **Step 1: Read the current memory file for the refactor**
|
||||
|
||||
Read `/Users/isaac/.claude/projects/-Users-isaac-build-telegram-telegram-ios/memory/project_postbox_refactor_next_wave.md`.
|
||||
|
||||
- [ ] **Step 2: Update frontmatter + add wave-37 entry**
|
||||
|
||||
Update the `description:` frontmatter field to reference wave 37 outcome (number of bridges dropped, build-iteration count, first-pass-clean-or-not). Add a bullet to "Latest commits" section with the new SHA and a one-line summary. Remove the "peerTokenTitle parameter migration" bullet from the "Wave 37 candidates" section (it's now landed). Update "Recommended wave 37" section to "Recommended wave 38" with a fresh recommendation from the remaining candidates.
|
||||
|
||||
- [ ] **Step 3: Read the refactor log**
|
||||
|
||||
Read `docs/superpowers/postbox-refactor-log.md`, locate the "Wave 36 outcome" section.
|
||||
|
||||
- [ ] **Step 4: Append wave-37 outcome**
|
||||
|
||||
Under the "Wave N outcomes" section, append a "Wave 37 outcome" subsection with:
|
||||
|
||||
- Commit SHA (from `git log --oneline -1`)
|
||||
- File touched (1: ContactMultiselectionController.swift)
|
||||
- Lines changed (6 deletions, 6 insertions)
|
||||
- Bridges dropped (5)
|
||||
- Build iterations to converge (should be 1)
|
||||
- Any lessons observed (likely none — this wave is mechanical)
|
||||
|
||||
- [ ] **Step 5: Commit memory + log update**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git add docs/superpowers/postbox-refactor-log.md
|
||||
git commit -m "$(cat <<'EOF'
|
||||
docs: log wave 37 outcome
|
||||
|
||||
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
(Memory file under `~/.claude/` is not in the repo — save it separately via the Write tool; do not try to `git add` it.)
|
||||
|
||||
---
|
||||
|
||||
## Self-review results
|
||||
|
||||
**Spec coverage:** Every scope item in the spec maps to a task:
|
||||
- Spec L21 signature change → Task 2 Step 2
|
||||
- Spec L27 body simplification → Task 2 Step 2
|
||||
- Spec L171/201/386/403/748 bridge drops → Task 3 Steps 1–2
|
||||
- Spec verification (grep + build + post-grep) → Task 1 + Task 4
|
||||
- Spec commit message → Task 5 Step 3
|
||||
|
||||
Out-of-scope items (L459, `import Postbox`, `accountPeerId: PeerId`) remain explicitly untouched — no task edits them.
|
||||
|
||||
**Placeholder scan:** No TBD, TODO, placeholder phrases, or "handle edge cases"-style hand-waves. Every step has a concrete command or code block.
|
||||
|
||||
**Type consistency:** `peer: EnginePeer`, `EnginePeer.Id` (= `PeerId` typealias), and `EnginePeer.displayTitle(strings:displayOrder:)` are all consistent across tasks.
|
||||
666
docs/superpowers/plans/2026-04-24-rcp-peers-engine-migration.md
Normal file
666
docs/superpowers/plans/2026-04-24-rcp-peers-engine-migration.md
Normal file
|
|
@ -0,0 +1,666 @@
|
|||
# Wave 44 — RenderedChannelParticipant.peers Engine-Peer Migration 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 `RenderedChannelParticipant.peers: [PeerId: Peer]` to `[EnginePeer.Id: EnginePeer]`. Closes the wave-41 ratchet — the public struct no longer leaks raw Postbox `Peer` in any field.
|
||||
|
||||
**Architecture:** Single atomic commit. Declaration in TelegramCore changes; 8 TelegramCore producer functions wrap raw `Peer` values at their local-dict insertion points (inside transactions that already read from Postbox); 11 consumer-surface bridges drop (6 `EnginePeer(peer)` read-wraps + 5 `.mapValues({ $0._asPeer() })` constructor-unwrap transforms); 1 consumer-surface unwrap is added where an extracted `EnginePeer` value flows into a `SimpleDictionary<PeerId, Peer>`.
|
||||
|
||||
**Tech Stack:** Swift, Bazel (via `python3 build-system/Make/Make.py`), Postbox, TelegramCore, TelegramEngine. No unit tests — full-build verification only.
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-04-24-rcp-peers-engine-migration-design.md`
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
All edits happen in existing files — no new files created. Touched files:
|
||||
|
||||
**TelegramCore (declaration + producers, 9 files):**
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift` (declaration)
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestStartBot.swift`
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelOwnershipTransfer.swift`
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift`
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Peers/AddPeerMember.swift`
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerAdmins.swift`
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift`
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Peers/Ranks.swift`
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelMembers.swift`
|
||||
|
||||
**Consumers (drops + 1 add, 5 files):**
|
||||
- `submodules/PeerInfoUI/Sources/ChannelAdminsController.swift`
|
||||
- `submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift`
|
||||
- `submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift`
|
||||
- `submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift`
|
||||
- `submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift`
|
||||
|
||||
**Total:** 14 files, ~30 edits, one atomic commit.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Pre-flight re-verification
|
||||
|
||||
**Purpose:** Confirm the grep surface matches the spec before editing anything. If any site count diverges, stop and update the spec.
|
||||
|
||||
**Files:** None modified.
|
||||
|
||||
- [ ] **Step 1.1: Verify 7 `participant.peers[...]` consumer read sites**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep -rnE "participant\.peers\[|rcp\.peers\[|renderedParticipant\.peers\[" --include="*.swift" submodules/ 2>/dev/null
|
||||
```
|
||||
|
||||
Expected output — exactly 6 bracketed-indexing sites (the 7th site, iteration without bracket-indexing, is checked in Step 1.2):
|
||||
- `submodules/PeerInfoUI/Sources/ChannelAdminsController.swift:293`
|
||||
- `submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift:835`
|
||||
- `submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift:869`
|
||||
- `submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift:1087`
|
||||
- `submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift:1121`
|
||||
- `submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift:164`
|
||||
|
||||
If any line numbers differ by more than ±3 lines, re-read surrounding context to confirm identity. If a NEW site appears that isn't in the spec, STOP and update the spec before proceeding.
|
||||
|
||||
- [ ] **Step 1.2: Verify the iteration site is still at the expected line**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep -nE "for \(.*,.* peer\) in participant\.peers" submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift
|
||||
```
|
||||
|
||||
Expected: `672: for (_, peer) in participant.peers {`
|
||||
|
||||
- [ ] **Step 1.3: Verify all 8 TelegramCore producers still build `var peers: [PeerId: Peer] = [:]` locally**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep -rnE "^[[:space:]]+var peers: \[PeerId: Peer\] = \[:\]" submodules/TelegramCore/Sources/TelegramEngine/ 2>/dev/null
|
||||
```
|
||||
|
||||
Expected 8 matches, one per producer file:
|
||||
- `Messages/RequestStartBot.swift:61`
|
||||
- `Peers/ChannelOwnershipTransfer.swift:170`
|
||||
- `Peers/JoinChannel.swift:59`
|
||||
- `Peers/AddPeerMember.swift:242`
|
||||
- `Peers/PeerAdmins.swift:251`
|
||||
- `Peers/ChannelBlacklist.swift:128`
|
||||
- `Peers/Ranks.swift:60`
|
||||
- `Peers/ChannelMembers.swift:102`
|
||||
|
||||
If a producer is missing from this grep, check whether it now receives `peers` as a parameter rather than building locally — if so, STOP and update the spec (chain-migration needed).
|
||||
|
||||
- [ ] **Step 1.4: Verify no `as?` / `is TelegramX` casts exist on extracted dict values**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep -rnE "peer = participant\.peers" --include="*.swift" -A 4 submodules/ 2>/dev/null | grep -E "as\?|is Telegram"
|
||||
```
|
||||
|
||||
Expected output: empty. If this returns non-empty, STOP and update the spec.
|
||||
|
||||
- [ ] **Step 1.5: Verify no one is assigning into `participant.peers` (writes would break the migration)**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep -rnE "participant\.peers\[[^]]+\][[:space:]]*=" --include="*.swift" submodules/ 2>/dev/null
|
||||
```
|
||||
|
||||
Expected output: empty (`.peers` is a `let`; no writes possible anyway, but double-check).
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Migrate declaration in ChannelParticipants.swift
|
||||
|
||||
**Purpose:** Change the struct field type and init default.
|
||||
|
||||
**Files:**
|
||||
- Modify: `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift:11, 14`
|
||||
|
||||
- [ ] **Step 2.1: Change field declaration**
|
||||
|
||||
In `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift`, line 11:
|
||||
|
||||
```swift
|
||||
// before
|
||||
public let peers: [PeerId: Peer]
|
||||
|
||||
// after
|
||||
public let peers: [EnginePeer.Id: EnginePeer]
|
||||
```
|
||||
|
||||
- [ ] **Step 2.2: Change init default**
|
||||
|
||||
Same file, line 14:
|
||||
|
||||
```swift
|
||||
// before
|
||||
public init(participant: ChannelParticipant, peer: EnginePeer, peers: [PeerId: Peer] = [:], presences: [PeerId: PeerPresence] = [:]) {
|
||||
|
||||
// after
|
||||
public init(participant: ChannelParticipant, peer: EnginePeer, peers: [EnginePeer.Id: EnginePeer] = [:], presences: [PeerId: PeerPresence] = [:]) {
|
||||
```
|
||||
|
||||
Do NOT commit yet — this leaves the repo in a broken state until producers and consumers are updated.
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Migrate TelegramCore producers (8 files)
|
||||
|
||||
**Purpose:** Each of the 8 TelegramCore producers builds a local `peers: [PeerId: Peer] = [:]` dict from raw Postbox peers inside a transaction. Migrate each local dict to `[EnginePeer.Id: EnginePeer] = [:]` and wrap every insertion value with `EnginePeer(...)`.
|
||||
|
||||
**Pattern (applies to every sub-step):**
|
||||
```swift
|
||||
// before
|
||||
var peers: [PeerId: Peer] = [:]
|
||||
peers[X.id] = X
|
||||
|
||||
// after
|
||||
var peers: [EnginePeer.Id: EnginePeer] = [:]
|
||||
peers[X.id] = EnginePeer(X)
|
||||
```
|
||||
|
||||
The surrounding `presences: [PeerId: PeerPresence]` dict and the `RCP(..., peer: EnginePeer(X), ...)` wrap on the primary `peer` field both stay unchanged.
|
||||
|
||||
- [ ] **Step 3.1: Migrate `RequestStartBot.swift`**
|
||||
|
||||
File: `submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestStartBot.swift`
|
||||
|
||||
Line 61: `var peers: [PeerId: Peer] = [:]` → `var peers: [EnginePeer.Id: EnginePeer] = [:]`
|
||||
Line 64: `peers[peer.id] = peer` → `peers[peer.id] = EnginePeer(peer)`
|
||||
|
||||
- [ ] **Step 3.2: Migrate `ChannelOwnershipTransfer.swift`**
|
||||
|
||||
File: `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelOwnershipTransfer.swift`
|
||||
|
||||
Line 170: `var peers: [PeerId: Peer] = [:]` → `var peers: [EnginePeer.Id: EnginePeer] = [:]`
|
||||
Line 172: `peers[accountUser.id] = accountUser` → `peers[accountUser.id] = EnginePeer(accountUser)`
|
||||
Line 176: `peers[user.id] = user` → `peers[user.id] = EnginePeer(user)`
|
||||
|
||||
Line 180 is a double-RCP-construction; `peers:` reuses the same local — no change at line 180.
|
||||
|
||||
- [ ] **Step 3.3: Migrate `JoinChannel.swift`**
|
||||
|
||||
File: `submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift`
|
||||
|
||||
Line 59: `var peers: [PeerId: Peer] = [:]` → `var peers: [EnginePeer.Id: EnginePeer] = [:]`
|
||||
Line 64: `peers[account.peerId] = peer` → `peers[account.peerId] = EnginePeer(peer)`
|
||||
Line 77: `peers[peer.id] = peer` → `peers[peer.id] = EnginePeer(peer)`
|
||||
|
||||
- [ ] **Step 3.4: Migrate `AddPeerMember.swift`**
|
||||
|
||||
File: `submodules/TelegramCore/Sources/TelegramEngine/Peers/AddPeerMember.swift`
|
||||
|
||||
Line 242: `var peers: [PeerId: Peer] = [:]` → `var peers: [EnginePeer.Id: EnginePeer] = [:]`
|
||||
Line 244: `peers[memberPeer.id] = memberPeer` → `peers[memberPeer.id] = EnginePeer(memberPeer)`
|
||||
Line 251: `peers[peer.id] = peer` → `peers[peer.id] = EnginePeer(peer)`
|
||||
|
||||
- [ ] **Step 3.5: Migrate `PeerAdmins.swift`**
|
||||
|
||||
File: `submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerAdmins.swift`
|
||||
|
||||
Line 251: `var peers: [PeerId: Peer] = [:]` → `var peers: [EnginePeer.Id: EnginePeer] = [:]`
|
||||
Line 253: `peers[adminPeer.id] = adminPeer` → `peers[adminPeer.id] = EnginePeer(adminPeer)`
|
||||
Line 259: `peers[peer.id] = peer` → `peers[peer.id] = EnginePeer(peer)`
|
||||
|
||||
- [ ] **Step 3.6: Migrate `ChannelBlacklist.swift`**
|
||||
|
||||
File: `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift`
|
||||
|
||||
Line 128: `var peers: [PeerId: Peer] = [:]` → `var peers: [EnginePeer.Id: EnginePeer] = [:]`
|
||||
Line 130: `peers[memberPeer.id] = memberPeer` → `peers[memberPeer.id] = EnginePeer(memberPeer)`
|
||||
Line 136: `peers[peer.id] = peer` → `peers[peer.id] = EnginePeer(peer)`
|
||||
|
||||
- [ ] **Step 3.7: Migrate `Ranks.swift`**
|
||||
|
||||
File: `submodules/TelegramCore/Sources/TelegramEngine/Peers/Ranks.swift`
|
||||
|
||||
Line 60: `var peers: [PeerId: Peer] = [:]` → `var peers: [EnginePeer.Id: EnginePeer] = [:]`
|
||||
Line 62: `peers[user.id] = user` → `peers[user.id] = EnginePeer(user)`
|
||||
Line 68: `peers[peer.id] = peer` → `peers[peer.id] = EnginePeer(peer)`
|
||||
|
||||
- [ ] **Step 3.8: Migrate `ChannelMembers.swift`**
|
||||
|
||||
File: `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelMembers.swift`
|
||||
|
||||
Line 102: `var peers: [PeerId: Peer] = [:]` → `var peers: [EnginePeer.Id: EnginePeer] = [:]`
|
||||
Line 105: `peers[peer.id] = peer` → `peers[peer.id] = EnginePeer(peer)`
|
||||
|
||||
- [ ] **Step 3.9: Post-producer verification**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep -rnE "^[[:space:]]+var peers: \[PeerId: Peer\] = \[:\]" submodules/TelegramCore/Sources/TelegramEngine/ 2>/dev/null
|
||||
```
|
||||
|
||||
Expected: no output (all 8 have been converted).
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep -rnE "^[[:space:]]+var peers: \[EnginePeer\.Id: EnginePeer\] = \[:\]" submodules/TelegramCore/Sources/TelegramEngine/ 2>/dev/null | wc -l
|
||||
```
|
||||
|
||||
Expected: `8` (or ` 8`).
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Drop 5 consumer `.mapValues({ $0._asPeer() })` transforms
|
||||
|
||||
**Purpose:** These consumer-side constructors build a `[EnginePeer.Id: EnginePeer]` source dict locally and currently unwrap to `[PeerId: Peer]` via `.mapValues({ $0._asPeer() })` to feed the old constructor signature. After Task 2, the constructor expects engine values directly — the transform becomes a no-op and is removed.
|
||||
|
||||
**Pattern (applies to every sub-step):**
|
||||
```swift
|
||||
// before
|
||||
peers: peers.mapValues({ $0._asPeer() })
|
||||
|
||||
// after
|
||||
peers: peers
|
||||
```
|
||||
|
||||
- [ ] **Step 4.1: `ChannelAdminsController.swift:926`**
|
||||
|
||||
File: `submodules/PeerInfoUI/Sources/ChannelAdminsController.swift`
|
||||
|
||||
Line 926 (long line): locate the substring `peers: peers.mapValues({ $0._asPeer() })` and replace with `peers: peers`.
|
||||
|
||||
- [ ] **Step 4.2: `ChannelMembersSearchContainerNode.swift:994`**
|
||||
|
||||
File: `submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift`
|
||||
|
||||
Line 994: replace `peers: peers.mapValues({ $0._asPeer() })` → `peers: peers`.
|
||||
|
||||
- [ ] **Step 4.3: `ChannelMembersSearchContainerNode.swift:998`**
|
||||
|
||||
Same file, line 998: replace `peers: peers.mapValues({ $0._asPeer() })` → `peers: peers`.
|
||||
|
||||
- [ ] **Step 4.4: `ChannelMembersSearchControllerNode.swift:409`**
|
||||
|
||||
File: `submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift`
|
||||
|
||||
Line 409: replace `peers: peers.mapValues({ $0._asPeer() })` → `peers: peers`.
|
||||
|
||||
- [ ] **Step 4.5: `ChannelMembersSearchControllerNode.swift:413`**
|
||||
|
||||
Same file, line 413: replace `peers: peers.mapValues({ $0._asPeer() })` → `peers: peers`.
|
||||
|
||||
- [ ] **Step 4.6: Post-Task-4 verification**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep -rnE "peers\.mapValues\(\{ \$0\._asPeer\(\) \}\)" --include="*.swift" submodules/ 2>/dev/null
|
||||
```
|
||||
|
||||
Expected: no output (all 5 drops applied). If any remain, locate and drop.
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Drop 6 consumer `EnginePeer(peer).displayTitle(...)` read-wraps
|
||||
|
||||
**Purpose:** Each site extracts `peer` from `participant.peers[X]`, wraps with `EnginePeer(peer)` to call `.displayTitle(...)`. After Task 2 the extracted `peer` is already `EnginePeer` — drop the wrap.
|
||||
|
||||
**Pattern (applies to every sub-step):**
|
||||
```swift
|
||||
// before
|
||||
EnginePeer(peer).displayTitle(strings: ..., displayOrder: ...)
|
||||
|
||||
// after
|
||||
peer.displayTitle(strings: ..., displayOrder: ...)
|
||||
```
|
||||
|
||||
- [ ] **Step 5.1: `ChannelAdminsController.swift:297`**
|
||||
|
||||
File: `submodules/PeerInfoUI/Sources/ChannelAdminsController.swift`, line 297.
|
||||
|
||||
Replace:
|
||||
```swift
|
||||
peerText = strings.Channel_Management_PromotedBy(EnginePeer(peer).displayTitle(strings: strings, displayOrder: nameDisplayOrder)).string
|
||||
```
|
||||
with:
|
||||
```swift
|
||||
peerText = strings.Channel_Management_PromotedBy(peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder)).string
|
||||
```
|
||||
|
||||
The adjacent `peer.id == participant.peer.id` comparison at line 294 stays unchanged (both are `EnginePeer.Id`).
|
||||
|
||||
- [ ] **Step 5.2: `ChannelMembersSearchContainerNode.swift:839`**
|
||||
|
||||
File: `submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift`, line 839.
|
||||
|
||||
Replace:
|
||||
```swift
|
||||
label = presentationData.strings.Channel_Management_PromotedBy(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||
```
|
||||
with:
|
||||
```swift
|
||||
label = presentationData.strings.Channel_Management_PromotedBy(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||
```
|
||||
|
||||
- [ ] **Step 5.3: `ChannelMembersSearchContainerNode.swift:870`**
|
||||
|
||||
Same file, line 870.
|
||||
|
||||
Replace:
|
||||
```swift
|
||||
label = presentationData.strings.Channel_Management_RemovedBy(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||
```
|
||||
with:
|
||||
```swift
|
||||
label = presentationData.strings.Channel_Management_RemovedBy(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||
```
|
||||
|
||||
- [ ] **Step 5.4: `ChannelMembersSearchContainerNode.swift:1091`**
|
||||
|
||||
Same file, line 1091.
|
||||
|
||||
Replace:
|
||||
```swift
|
||||
label = presentationData.strings.Channel_Management_PromotedBy(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||
```
|
||||
with:
|
||||
```swift
|
||||
label = presentationData.strings.Channel_Management_PromotedBy(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||
```
|
||||
|
||||
- [ ] **Step 5.5: `ChannelMembersSearchContainerNode.swift:1122`**
|
||||
|
||||
Same file, line 1122.
|
||||
|
||||
Replace:
|
||||
```swift
|
||||
label = presentationData.strings.Channel_Management_RemovedBy(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||
```
|
||||
with:
|
||||
```swift
|
||||
label = presentationData.strings.Channel_Management_RemovedBy(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||
```
|
||||
|
||||
- [ ] **Step 5.6: `ChannelBlacklistController.swift:165`**
|
||||
|
||||
File: `submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift`, line 165.
|
||||
|
||||
Replace:
|
||||
```swift
|
||||
text = .text(strings.Channel_Management_RemovedBy(EnginePeer(peer).displayTitle(strings: strings, displayOrder: nameDisplayOrder)).string, .secondary)
|
||||
```
|
||||
with:
|
||||
```swift
|
||||
text = .text(strings.Channel_Management_RemovedBy(peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder)).string, .secondary)
|
||||
```
|
||||
|
||||
- [ ] **Step 5.7: Post-Task-5 verification**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep -rnE "EnginePeer\(peer\)\.displayTitle" --include="*.swift" submodules/PeerInfoUI/ 2>/dev/null
|
||||
```
|
||||
|
||||
Expected: no output within PeerInfoUI. (Other modules may still have unrelated `EnginePeer(peer).displayTitle` usages on non-RCP-peers peers — those are out of scope.)
|
||||
|
||||
Run specifically for the 6 migrated sites:
|
||||
```bash
|
||||
grep -n "EnginePeer(peer)\.displayTitle" submodules/PeerInfoUI/Sources/ChannelAdminsController.swift submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift 2>/dev/null
|
||||
```
|
||||
|
||||
Expected: no output.
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Add 1 consumer unwrap at ChatRecentActionsHistoryTransition
|
||||
|
||||
**Purpose:** The one site that iterates `participant.peers` and inserts values into a `SimpleDictionary<PeerId, Peer>` container. After Task 2, the iterated `peer` is `EnginePeer`; the outer container still expects raw `Peer`. Unwrap at the insertion site.
|
||||
|
||||
**Files:**
|
||||
- Modify: `submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift:673`
|
||||
|
||||
- [ ] **Step 6.1: Replace insertion line**
|
||||
|
||||
In `submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift`:
|
||||
|
||||
Context (lines 672–674, unchanged outside line 673):
|
||||
```swift
|
||||
for (_, peer) in participant.peers {
|
||||
peers[peer.id] = peer
|
||||
}
|
||||
```
|
||||
|
||||
After edit:
|
||||
```swift
|
||||
for (_, peer) in participant.peers {
|
||||
peers[peer.id] = peer._asPeer()
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 6.2: Spot-check nearby wave-41 unwrap (reference, no change)**
|
||||
|
||||
Line 675 in the same function is `peers[participant.peer.id] = participant.peer._asPeer()` — a wave-41 artifact, unrelated to this wave. Leave unchanged.
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Full build verification
|
||||
|
||||
**Purpose:** Verify the atomic change set compiles. Produces the ONLY real test signal for this wave.
|
||||
|
||||
**Files:** None modified; this is a build run.
|
||||
|
||||
- [ ] **Step 7.1: Run the full build**
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
Expected: build succeeds. Look for `INFO: Build completed successfully` near the end.
|
||||
|
||||
- [ ] **Step 7.2: If build fails — triage**
|
||||
|
||||
Expected failure patterns (from wave-41 lesson, budget 2–3 iterations):
|
||||
|
||||
1. **Missing producer wrap** — compiler error `cannot assign value of type 'Peer' to subscript of type 'EnginePeer'` (or similar) at a TelegramCore producer file → check that file's `var peers:` decl was converted AND all insertion RHS values are wrapped.
|
||||
2. **Missed consumer site** — compiler error at a `.displayTitle` call on a raw Peer → find `EnginePeer(peer).displayTitle` site that Task 5 missed; drop the wrap.
|
||||
3. **Mismatched mapValues drop** — `cannot convert value of type '[EnginePeer.Id: EnginePeer]' to expected argument type '[PeerId: Peer]'` → the spec's risk #3 triggered (a `.mapValues` site had a raw-Peer source after all); replace the drop with `peers.mapValues(EnginePeer.init)` at that site instead.
|
||||
4. **New grep surface** — compiler complains about a site not in this plan → add it to the commit's scope; log it to the outcome doc.
|
||||
|
||||
Apply fixes, re-run Step 7.1. Repeat up to 3 iterations before re-evaluating scope.
|
||||
|
||||
- [ ] **Step 7.3: Post-build final grep audit**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep -rnE "participant\.peers\[[^]]+\]" --include="*.swift" submodules/ 2>/dev/null
|
||||
```
|
||||
|
||||
Expected: the same 6 read sites as Step 1.1 (now without `EnginePeer(peer)` wraps).
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep -rnE "peers\.mapValues\(\{ \$0\._asPeer\(\) \}\)" --include="*.swift" submodules/ 2>/dev/null
|
||||
```
|
||||
|
||||
Expected: no output.
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep -n "public let peers: \[" submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift
|
||||
```
|
||||
|
||||
Expected: `11: public let peers: [EnginePeer.Id: EnginePeer]`.
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Atomic commit
|
||||
|
||||
**Purpose:** Land all wave-44 edits in ONE commit. Explicitly enumerate files in `git add` (wave-39 lesson — re-confirmed in waves 41, 42, 43) to avoid pulling in the pre-existing working-tree WIP listed in the spec's risk section (`ListView.swift`, `ChatMessageTransitionNode.swift`, tulsi/, TgVoip/, libx264/).
|
||||
|
||||
**Files:** Commits all 14 wave-44 files.
|
||||
|
||||
- [ ] **Step 8.1: Confirm working-tree state**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
git status --short
|
||||
```
|
||||
|
||||
Expected (pre-existing WIP, unchanged):
|
||||
- ` m build-system/bazel-rules/sourcekit-bazel-bsp`
|
||||
- ` M submodules/Display/Source/ListView.swift` (do NOT include)
|
||||
- ` M submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift` (do NOT include)
|
||||
- `?? build-system/tulsi/` (do NOT include)
|
||||
- `?? submodules/TgVoip/` (do NOT include)
|
||||
- `?? third-party/libx264/` (do NOT include)
|
||||
|
||||
Plus the wave-44 modified files:
|
||||
- ` M submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift`
|
||||
- ` M submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestStartBot.swift`
|
||||
- ` M submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelOwnershipTransfer.swift`
|
||||
- ` M submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift`
|
||||
- ` M submodules/TelegramCore/Sources/TelegramEngine/Peers/AddPeerMember.swift`
|
||||
- ` M submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerAdmins.swift`
|
||||
- ` M submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift`
|
||||
- ` M submodules/TelegramCore/Sources/TelegramEngine/Peers/Ranks.swift`
|
||||
- ` M submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelMembers.swift`
|
||||
- ` M submodules/PeerInfoUI/Sources/ChannelAdminsController.swift`
|
||||
- ` M submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift`
|
||||
- ` M submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift`
|
||||
- ` M submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift`
|
||||
- ` M submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift`
|
||||
|
||||
If the set of wave-44-modified files doesn't match exactly (extra or missing), STOP and investigate before committing.
|
||||
|
||||
- [ ] **Step 8.2: Stage only wave-44 files**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
git add \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestStartBot.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelOwnershipTransfer.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/AddPeerMember.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerAdmins.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/Ranks.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelMembers.swift \
|
||||
submodules/PeerInfoUI/Sources/ChannelAdminsController.swift \
|
||||
submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift \
|
||||
submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift \
|
||||
submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift \
|
||||
submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift
|
||||
```
|
||||
|
||||
- [ ] **Step 8.3: Verify staged set matches expected**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
git diff --cached --stat
|
||||
```
|
||||
|
||||
Expected: exactly 14 files staged, all from the wave-44 list. If `ListView.swift`, `ChatMessageTransitionNode.swift`, `bazel-rules/sourcekit-bazel-bsp`, `tulsi/`, `TgVoip/`, or `libx264/` appear here, unstage them.
|
||||
|
||||
- [ ] **Step 8.4: Commit**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
git commit -m "$(cat <<'EOF'
|
||||
Postbox -> TelegramEngine wave 44
|
||||
|
||||
Migrate RenderedChannelParticipant.peers from [PeerId: Peer] to
|
||||
[EnginePeer.Id: EnginePeer]. Closes the wave-41 ratchet — the public
|
||||
struct no longer leaks raw Peer types in any field (presences stays
|
||||
Postbox-typed; separate migration).
|
||||
|
||||
Consumer-surface: -10 bridges. Dropped 6 EnginePeer(peer) read-wraps
|
||||
at participant.peers[...] extraction sites across
|
||||
ChannelAdminsController, ChannelMembersSearchContainerNode,
|
||||
ChannelBlacklistController. Dropped 5 .mapValues({ $0._asPeer() })
|
||||
constructor-unwrap transforms in ChannelAdminsController,
|
||||
ChannelMembersSearchContainerNode, ChannelMembersSearchControllerNode.
|
||||
Added 1 ._asPeer() at ChatRecentActionsHistoryTransition.swift:673
|
||||
where the iterated value is inserted into a raw-Peer SimpleDictionary.
|
||||
|
||||
TelegramCore producers: 8 files build the local peers dict inside
|
||||
postbox.transaction and wrap at the insertion point. ChannelMembers,
|
||||
RequestStartBot, ChannelOwnershipTransfer, JoinChannel, AddPeerMember,
|
||||
PeerAdmins, ChannelBlacklist, Ranks.
|
||||
|
||||
No unit tests in this project; full Telegram/Telegram build verified
|
||||
under configuration=debug_sim_arm64.
|
||||
|
||||
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
- [ ] **Step 8.5: Verify commit**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
git log -1 --stat
|
||||
```
|
||||
|
||||
Expected: commit with 14 files changed, message starting with `Postbox -> TelegramEngine wave 44`.
|
||||
|
||||
Run:
|
||||
```bash
|
||||
git status --short
|
||||
```
|
||||
|
||||
Expected: no M- or A-flagged wave-44 files (all committed); only the pre-existing WIP (`ListView.swift`, `ChatMessageTransitionNode.swift`, etc.) remains.
|
||||
|
||||
---
|
||||
|
||||
## Rollback
|
||||
|
||||
If the wave cannot be completed (e.g., build fails after 4+ iterations and the scope balloons beyond plan):
|
||||
|
||||
```bash
|
||||
git restore --staged \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestStartBot.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelOwnershipTransfer.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/AddPeerMember.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerAdmins.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/Ranks.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelMembers.swift \
|
||||
submodules/PeerInfoUI/Sources/ChannelAdminsController.swift \
|
||||
submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift \
|
||||
submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift \
|
||||
submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift \
|
||||
submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift
|
||||
|
||||
git checkout -- \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestStartBot.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelOwnershipTransfer.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/AddPeerMember.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerAdmins.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/Ranks.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelMembers.swift \
|
||||
submodules/PeerInfoUI/Sources/ChannelAdminsController.swift \
|
||||
submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift \
|
||||
submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift \
|
||||
submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift \
|
||||
submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift
|
||||
```
|
||||
|
||||
Then document what was learned in an outcome doc and update `project_postbox_refactor_next_wave.md`.
|
||||
|
||||
---
|
||||
|
||||
## Success criteria (from spec)
|
||||
|
||||
1. ✅ `ChannelParticipants.swift` has `peers: [EnginePeer.Id: EnginePeer]` declaration (Task 2).
|
||||
2. ✅ All 8 TelegramCore producers compile with wrapped inserts (Task 3).
|
||||
3. ✅ All 5 consumer `.mapValues({ $0._asPeer() })` transforms are removed (Task 4).
|
||||
4. ✅ All 6 consumer `EnginePeer(peer).displayTitle(...)` wraps on extracted dict values are removed (Task 5).
|
||||
5. ✅ `ChatRecentActionsHistoryTransition.swift:673` uses `peer._asPeer()` for the SimpleDictionary insertion value (Task 6).
|
||||
6. ✅ Full `Telegram/Telegram` build (`configuration=debug_sim_arm64`) is clean — **one** atomic commit (Tasks 7, 8).
|
||||
7. ✅ Grep post-migration: `participant.peers[` returns only engine-typed call sites; no residual `EnginePeer(peer)` on `.peers[...]` extractions (Steps 5.7, 7.3).
|
||||
|
|
@ -0,0 +1,860 @@
|
|||
# Wave 41 — `RenderedChannelParticipant.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 `TelegramCore.RenderedChannelParticipant.peer` from Postbox `Peer` to TelegramCore `EnginePeer`. Drop ~37 bridges (net ~−14 after adds) and eliminate 2 Shape-C ratchet wraps installed by wave 39.
|
||||
|
||||
**Architecture:** Single atomic commit. One TelegramCore struct field change + 16 TelegramCore internal construction sites wrapped with `EnginePeer(peer)` + 17 consumer files updated: ZERO sites untouched (~160), ~32 DROP sites unwrapped, 9 CAST sites rewritten to pattern-match, 3 ADD-ASPEER sites append `._asPeer()`, 7 ADD-WRAP consumer constructors wrap raw `Peer` with `EnginePeer`.
|
||||
|
||||
**Tech Stack:** Swift, Bazel (`Make.py` wrapper), TelegramCore, Postbox → TelegramEngine refactor conventions per `CLAUDE.md`.
|
||||
|
||||
**Build command:**
|
||||
```sh
|
||||
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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
**Created:** none.
|
||||
|
||||
**Modified (27 files):**
|
||||
|
||||
TelegramCore (10 files):
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift` — struct field type + init param + Equatable impl
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestStartBot.swift` — 1 constructor wrap
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Peers/AddPeerMember.swift` — 1 constructor wrap
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift` — 7 constructor wraps
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift` — 1 constructor wrap
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelMembers.swift` — 1 constructor wrap
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelOwnershipTransfer.swift` — 2 constructor wraps
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift` — 1 constructor wrap
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerAdmins.swift` — 1 constructor wrap
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Peers/Ranks.swift` — 1 constructor wrap
|
||||
|
||||
PeerInfoUI (6 files):
|
||||
- `submodules/PeerInfoUI/Sources/ChannelAdminsController.swift`
|
||||
- `submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift`
|
||||
- `submodules/PeerInfoUI/Sources/ChannelMembersController.swift`
|
||||
- `submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift`
|
||||
- `submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift`
|
||||
- `submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift`
|
||||
|
||||
Other consumers (11 files):
|
||||
- `submodules/SearchPeerMembers/Sources/SearchPeerMembers.swift`
|
||||
- `submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift`
|
||||
- `submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift`
|
||||
- `submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsFilterController.swift`
|
||||
- `submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift`
|
||||
- `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoMembers.swift`
|
||||
- `submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift`
|
||||
- `submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift`
|
||||
- `submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift`
|
||||
- `submodules/TemporaryCachedPeerDataManager/Sources/ChannelMemberCategoryListContext.swift` *(no `participant.peer` edits needed — all ZERO; file touched only if build surfaces type issues)*
|
||||
- `submodules/TemporaryCachedPeerDataManager/Sources/PeerChannelMemberCategoriesContextsManager.swift` *(no edits expected — only `item.peer.id` reference is ZERO)*
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Migrate the struct definition
|
||||
|
||||
**File:** `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift`
|
||||
|
||||
- [ ] **Step 1.1: Edit struct field, init param, and Equatable impl**
|
||||
|
||||
Replace the entire struct body:
|
||||
|
||||
```swift
|
||||
public struct RenderedChannelParticipant: Equatable {
|
||||
public let participant: ChannelParticipant
|
||||
public let peer: EnginePeer
|
||||
public let peers: [PeerId: Peer]
|
||||
public let presences: [PeerId: PeerPresence]
|
||||
|
||||
public init(participant: ChannelParticipant, peer: EnginePeer, peers: [PeerId: Peer] = [:], presences: [PeerId: PeerPresence] = [:]) {
|
||||
self.participant = participant
|
||||
self.peer = peer
|
||||
self.peers = peers
|
||||
self.presences = presences
|
||||
}
|
||||
|
||||
public static func ==(lhs: RenderedChannelParticipant, rhs: RenderedChannelParticipant) -> Bool {
|
||||
return lhs.participant == rhs.participant && lhs.peer == rhs.peer
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: the file already imports both `Postbox` (for `Peer`/`PeerId`/`PeerPresence`) and TelegramCore internal symbols (`EnginePeer` visible from within the same module). No import changes needed.
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Wrap TelegramCore-internal constructor sites
|
||||
|
||||
Each site receives a raw `Peer` and must now wrap it with `EnginePeer(peer)`. All edits are identical in shape.
|
||||
|
||||
- [ ] **Step 2.1:** `submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestStartBot.swift:65`
|
||||
|
||||
Before:
|
||||
```swift
|
||||
return .channelParticipant(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presences: presences))
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
return .channelParticipant(RenderedChannelParticipant(participant: participant, peer: EnginePeer(peer), peers: peers, presences: presences))
|
||||
```
|
||||
|
||||
- [ ] **Step 2.2:** `submodules/TelegramCore/Sources/TelegramEngine/Peers/AddPeerMember.swift:255`
|
||||
|
||||
Before:
|
||||
```swift
|
||||
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: memberPeer, peers: peers, presences: presences))
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: EnginePeer(memberPeer), peers: peers, presences: presences))
|
||||
```
|
||||
|
||||
- [ ] **Step 2.3:** `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift` — 7 constructor wraps
|
||||
|
||||
Line 271:
|
||||
```swift
|
||||
action = .participantInvite(RenderedChannelParticipant(participant: participant, peer: peer))
|
||||
// becomes:
|
||||
action = .participantInvite(RenderedChannelParticipant(participant: participant, peer: EnginePeer(peer)))
|
||||
```
|
||||
|
||||
Line 279 (two constructors on one line):
|
||||
```swift
|
||||
action = .participantToggleBan(prev: RenderedChannelParticipant(participant: prevParticipant, peer: prevPeer), new: RenderedChannelParticipant(participant: newParticipant, peer: newPeer))
|
||||
// becomes:
|
||||
action = .participantToggleBan(prev: RenderedChannelParticipant(participant: prevParticipant, peer: EnginePeer(prevPeer)), new: RenderedChannelParticipant(participant: newParticipant, peer: EnginePeer(newPeer)))
|
||||
```
|
||||
|
||||
Line 287 (two constructors on one line):
|
||||
```swift
|
||||
action = .participantToggleAdmin(prev: RenderedChannelParticipant(participant: prevParticipant, peer: prevPeer), new: RenderedChannelParticipant(participant: newParticipant, peer: newPeer))
|
||||
// becomes:
|
||||
action = .participantToggleAdmin(prev: RenderedChannelParticipant(participant: prevParticipant, peer: EnginePeer(prevPeer)), new: RenderedChannelParticipant(participant: newParticipant, peer: EnginePeer(newPeer)))
|
||||
```
|
||||
|
||||
Line 483 (two constructors on one line):
|
||||
```swift
|
||||
action = .participantSubscriptionExtended(prev: RenderedChannelParticipant(participant: prevParticipant, peer: prevPeer), new: RenderedChannelParticipant(participant: newParticipant, peer: newPeer))
|
||||
// becomes:
|
||||
action = .participantSubscriptionExtended(prev: RenderedChannelParticipant(participant: prevParticipant, peer: EnginePeer(prevPeer)), new: RenderedChannelParticipant(participant: newParticipant, peer: EnginePeer(newPeer)))
|
||||
```
|
||||
|
||||
- [ ] **Step 2.4:** `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift:140`
|
||||
|
||||
Before:
|
||||
```swift
|
||||
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: memberPeer, peers: peers, presences: presences), isMember)
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: EnginePeer(memberPeer), peers: peers, presences: presences), isMember)
|
||||
```
|
||||
|
||||
- [ ] **Step 2.5:** `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelMembers.swift:115`
|
||||
|
||||
Before:
|
||||
```swift
|
||||
items.append(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presences: renderedPresences))
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
items.append(RenderedChannelParticipant(participant: participant, peer: EnginePeer(peer), peers: peers, presences: renderedPresences))
|
||||
```
|
||||
|
||||
- [ ] **Step 2.6:** `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelOwnershipTransfer.swift:180`
|
||||
|
||||
Before:
|
||||
```swift
|
||||
return [(currentCreator, RenderedChannelParticipant(participant: updatedPreviousCreator, peer: accountUser, peers: peers, presences: presences)), (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: user, peers: peers, presences: presences))]
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
return [(currentCreator, RenderedChannelParticipant(participant: updatedPreviousCreator, peer: EnginePeer(accountUser), peers: peers, presences: presences)), (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: EnginePeer(user), peers: peers, presences: presences))]
|
||||
```
|
||||
|
||||
- [ ] **Step 2.7:** `submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift:82`
|
||||
|
||||
Before:
|
||||
```swift
|
||||
return RenderedChannelParticipant(participant: updatedParticipant, peer: peer, peers: peers, presences: presences)
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
return RenderedChannelParticipant(participant: updatedParticipant, peer: EnginePeer(peer), peers: peers, presences: presences)
|
||||
```
|
||||
|
||||
- [ ] **Step 2.8:** `submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerAdmins.swift:262`
|
||||
|
||||
Before:
|
||||
```swift
|
||||
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: adminPeer, peers: peers, presences: presences))
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: EnginePeer(adminPeer), peers: peers, presences: presences))
|
||||
```
|
||||
|
||||
- [ ] **Step 2.9:** `submodules/TelegramCore/Sources/TelegramEngine/Peers/Ranks.swift:95`
|
||||
|
||||
Before:
|
||||
```swift
|
||||
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: user, peers: peers, presences: presences))
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: EnginePeer(user), peers: peers, presences: presences))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Consumer — PeerInfoUI/ChannelAdminsController.swift
|
||||
|
||||
**File:** `submodules/PeerInfoUI/Sources/ChannelAdminsController.swift`
|
||||
|
||||
- [ ] **Step 3.1:** Line 326 — DROP `EnginePeer(participant.peer)` wrap.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: participant.presences[participant.peer.id].flatMap { EnginePeer.Presence($0) }, text: peerText.isEmpty ? .presence : .text(peerText, .secondary), label: label, editing: editing, revealOptions: revealOptions, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: action, setPeerIdWithRevealedOptions: { previousId, id in
|
||||
```
|
||||
After: replace `peer: EnginePeer(participant.peer)` → `peer: participant.peer` (leave the rest of the line intact).
|
||||
|
||||
- [ ] **Step 3.2:** Line 921 — DROP `._asPeer()` in constructor.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
result.append(RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: rank), peer: peer._asPeer(), presences: presences))
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
result.append(RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: rank), peer: peer, presences: presences))
|
||||
```
|
||||
(`peer` here is already `EnginePeer` — confirmed by surrounding code where `creatorPeer: EnginePeer?` is assigned from this same loop variable.)
|
||||
|
||||
- [ ] **Step 3.3:** Line 926 — DROP `._asPeer()` in constructor.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
result.append(RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: .internal_groupSpecific), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: rank, subscriptionUntilDate: nil), peer: peer._asPeer(), peers: peers.mapValues({ $0._asPeer() }), presences: presences))
|
||||
```
|
||||
After: change `peer: peer._asPeer()` → `peer: peer`. Leave `peers.mapValues({ $0._asPeer() })` intact — `peers` field is unchanged.
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Consumer — PeerInfoUI/ChannelBlacklistController.swift
|
||||
|
||||
**File:** `submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift`
|
||||
|
||||
- [ ] **Step 4.1:** Line 170 (or 381 — the site installed by wave 39; the file has one site `EnginePeer(participant.peer)`)
|
||||
|
||||
Before:
|
||||
```swift
|
||||
peer: EnginePeer(participant.peer)
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
peer: participant.peer
|
||||
```
|
||||
|
||||
Note: the file may have a single such site; use:
|
||||
```
|
||||
grep -n 'EnginePeer(participant\.peer)' submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift
|
||||
```
|
||||
and DROP every match.
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Consumer — PeerInfoUI/ChannelMembersController.swift
|
||||
|
||||
**File:** `submodules/PeerInfoUI/Sources/ChannelMembersController.swift`
|
||||
|
||||
- [ ] **Step 5.1:** Line 305 — CAST rewrite.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
if let user = participant.peer as? TelegramUser, let _ = user.botInfo {
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
if case let .user(user) = participant.peer, let _ = user.botInfo {
|
||||
```
|
||||
|
||||
- [ ] **Step 5.2:** Line 334 — DROP wrap.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
peer: EnginePeer(participant.peer)
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
peer: participant.peer
|
||||
```
|
||||
|
||||
- [ ] **Step 5.3:** Line 707 — DROP wrap (the wave-39-installed Shape-C wrap).
|
||||
|
||||
Before:
|
||||
```swift
|
||||
peer: EnginePeer(participant.peer)
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
peer: participant.peer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Consumer — PeerInfoUI/ChannelMembersSearchContainerNode.swift
|
||||
|
||||
**File:** `submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift`
|
||||
|
||||
This file has the most sites (4 CAST, 3 DROP pairs, 3 ADD-WRAP constructor sites).
|
||||
|
||||
- [ ] **Step 6.1:** Line 212 — DROP two wraps on one line.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
peer: .peer(peer: EnginePeer(participant.peer), chatPeer: EnginePeer(participant.peer)),
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
peer: .peer(peer: participant.peer, chatPeer: participant.peer),
|
||||
```
|
||||
|
||||
- [ ] **Step 6.2:** Line 223 — DROP wrap.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
interaction.peerSelected(EnginePeer(participant.peer), participant)
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
interaction.peerSelected(participant.peer, participant)
|
||||
```
|
||||
|
||||
- [ ] **Step 6.3:** Line 752 — CAST rewrite.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
if excludeBots, let user = participant.peer as? TelegramUser, user.botInfo != nil {
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
if excludeBots, case let .user(user) = participant.peer, user.botInfo != nil {
|
||||
```
|
||||
|
||||
- [ ] **Step 6.4:** Line 884 — CAST rewrite. Same pattern as 6.3.
|
||||
|
||||
- [ ] **Step 6.5:** Line 987 — ADD-WRAP constructor.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer)
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: EnginePeer(peer))
|
||||
```
|
||||
(`peer` here is raw `Peer` from `peerView.peers[participant.peerId]` — confirmed by surrounding iteration code.)
|
||||
|
||||
- [ ] **Step 6.6:** Line 994 — ADD-WRAP constructor.
|
||||
|
||||
Change `peer: peer` to `peer: EnginePeer(peer)`. Full site for reference:
|
||||
```swift
|
||||
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: .legacyGroup(group))), promotedBy: creatorPeer?.id ?? context.account.peerId, canBeEditedByAccountPeer: creatorPeer?.id == context.account.peerId), banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }))
|
||||
```
|
||||
Change only `peer: peer,` → `peer: EnginePeer(peer),`.
|
||||
|
||||
- [ ] **Step 6.7:** Line 998 — ADD-WRAP constructor.
|
||||
|
||||
```swift
|
||||
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }))
|
||||
```
|
||||
Change only `peer: peer,` → `peer: EnginePeer(peer),`.
|
||||
|
||||
- [ ] **Step 6.8:** Line 1052 — CAST rewrite. Same pattern as 6.3.
|
||||
|
||||
- [ ] **Step 6.9:** Line 1136 — CAST rewrite. Same pattern as 6.3.
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Consumer — PeerInfoUI/ChannelMembersSearchControllerNode.swift
|
||||
|
||||
**File:** `submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift`
|
||||
|
||||
- [ ] **Step 7.1:** Line 148 — DROP wrap.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
peer: EnginePeer(participant.peer)
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
peer: participant.peer
|
||||
```
|
||||
(The line has the wrap appearing twice — search the file for `EnginePeer(participant.peer)` and drop each occurrence. Use Edit with `replace_all` if unambiguous.)
|
||||
|
||||
- [ ] **Step 7.2:** Line 404 — ADD-WRAP constructor.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer, presences: peerView.peerPresences)
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: EnginePeer(peer), presences: peerView.peerPresences)
|
||||
```
|
||||
|
||||
- [ ] **Step 7.3:** Line 409 — ADD-WRAP constructor.
|
||||
|
||||
Change `peer: peer,` → `peer: EnginePeer(peer),` in the full line:
|
||||
```swift
|
||||
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: EnginePeer(mainPeer))), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }), presences: peerView.peerPresences)
|
||||
```
|
||||
|
||||
- [ ] **Step 7.4:** Line 413 — ADD-WRAP constructor. Same `peer: peer,` → `peer: EnginePeer(peer),`.
|
||||
|
||||
- [ ] **Step 7.5:** Line 516 — CAST rewrite.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
if let user = participant.peer as? TelegramUser, user.botInfo != nil {
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
if case let .user(user) = participant.peer, user.botInfo != nil {
|
||||
```
|
||||
|
||||
- [ ] **Step 7.6:** Line 558 — CAST rewrite. Same pattern as 7.5.
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Consumer — PeerInfoUI/ChannelPermissionsController.swift
|
||||
|
||||
**File:** `submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift`
|
||||
|
||||
- [ ] **Step 8.1:** Lines 480 and 483 — DROP wraps.
|
||||
|
||||
Both lines contain `EnginePeer(participant.peer)`. Change each to `participant.peer`.
|
||||
|
||||
If the two occurrences are unambiguous, use Edit with `replace_all=true` on `EnginePeer(participant.peer)` → `participant.peer`.
|
||||
|
||||
---
|
||||
|
||||
## Task 9: Consumer — SearchPeerMembers/SearchPeerMembers.swift
|
||||
|
||||
**File:** `submodules/SearchPeerMembers/Sources/SearchPeerMembers.swift`
|
||||
|
||||
- [ ] **Step 9.1:** Lines 30, 36, 61, 76 — DROP wraps.
|
||||
|
||||
All four sites are `EnginePeer(participant.peer)`. Use Edit with `replace_all=true`:
|
||||
- old: `EnginePeer(participant.peer)`
|
||||
- new: `participant.peer`
|
||||
|
||||
Verify with `grep -n 'EnginePeer(participant\.peer)' submodules/SearchPeerMembers/Sources/SearchPeerMembers.swift` → should return empty after edit.
|
||||
|
||||
---
|
||||
|
||||
## Task 10: Consumer — ChatRecentActionsController.swift
|
||||
|
||||
**File:** `submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift`
|
||||
|
||||
- [ ] **Step 10.1:** Line 359 — DROP wrap.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
EnginePeer(participant.peer)
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
participant.peer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 11: Consumer — ChatRecentActionsFilterController.swift
|
||||
|
||||
**File:** `submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsFilterController.swift`
|
||||
|
||||
- [ ] **Step 11.1:** Line 217 — DROP wrap.
|
||||
|
||||
Change `EnginePeer(participant.peer)` → `participant.peer` on line 217.
|
||||
|
||||
- [ ] **Step 11.2:** Line 445 — ADD-WRAP constructor rewrite.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
if let peer = peer, case let .user(user) = peer {
|
||||
return RenderedChannelParticipant(participant: .member(id: user.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: user)
|
||||
}
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
if let peer = peer, case let .user(user) = peer {
|
||||
return RenderedChannelParticipant(participant: .member(id: user.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: .user(user))
|
||||
}
|
||||
```
|
||||
(`.user(user)` is the enum case `EnginePeer.user(TelegramUser)`. Alternative: `peer: EnginePeer(user)` or `peer: peer` — but `peer: peer` reuses the already-unwrapped EnginePeer and is the cleanest. Use `peer: peer`.)
|
||||
|
||||
Preferred after:
|
||||
```swift
|
||||
if let peer = peer, case let .user(user) = peer {
|
||||
return RenderedChannelParticipant(participant: .member(id: user.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: peer)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 12: Consumer — ChatRecentActionsHistoryTransition.swift
|
||||
|
||||
**File:** `submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift`
|
||||
|
||||
This is the highest-volume consumer file (12 `EnginePeer(new.peer)` sites + 2 ADD-ASPEER sites).
|
||||
|
||||
- [ ] **Step 12.1:** DROP all `EnginePeer(new.peer)` wraps.
|
||||
|
||||
Use Edit with `replace_all=true`:
|
||||
- old: `EnginePeer(new.peer)`
|
||||
- new: `new.peer`
|
||||
|
||||
After: grep `EnginePeer(new\.peer)` should return empty.
|
||||
|
||||
- [ ] **Step 12.2:** Line 675 — ADD-ASPEER.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
peers[participant.peer.id] = participant.peer
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
peers[participant.peer.id] = participant.peer._asPeer()
|
||||
```
|
||||
(Target dict is `SimpleDictionary<PeerId, Peer>`; the value side needs raw Peer.)
|
||||
|
||||
- [ ] **Step 12.3:** Line 2275 — ADD-ASPEER.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
peers[new.peer.id] = new.peer
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
peers[new.peer.id] = new.peer._asPeer()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 13: Consumer — PeerInfoMembers.swift
|
||||
|
||||
**File:** `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoMembers.swift`
|
||||
|
||||
- [ ] **Step 13.1:** Line 33 — ADD-ASPEER.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
var peer: Peer {
|
||||
switch self {
|
||||
case let .channelMember(participant, _):
|
||||
return participant.peer
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
var peer: Peer {
|
||||
switch self {
|
||||
case let .channelMember(participant, _):
|
||||
return participant.peer._asPeer()
|
||||
```
|
||||
|
||||
No other edits in this file. The `participant.peer.id` accesses at lines 22, 44 are ZERO; `item.peer.id` at line 171 is ZERO.
|
||||
|
||||
---
|
||||
|
||||
## Task 14: Consumer — ShareWithPeersScreenState.swift
|
||||
|
||||
**File:** `submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift`
|
||||
|
||||
- [ ] **Step 14.1:** Line 558 — DROP wrap.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
peers.append(EnginePeer(participant.peer))
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
peers.append(participant.peer)
|
||||
```
|
||||
|
||||
- [ ] **Step 14.2:** Line 566 — CAST rewrite.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
if let user = participant.peer as? TelegramUser, user.botInfo != nil {
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
if case let .user(user) = participant.peer, user.botInfo != nil {
|
||||
```
|
||||
|
||||
- [ ] **Step 14.3:** Line 576 — DROP wrap.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
peers.append(EnginePeer(participant.peer))
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
peers.append(participant.peer)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 15: Consumer — AdminUserActionsSheet.swift
|
||||
|
||||
**File:** `submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift`
|
||||
|
||||
This file has ~6 `EnginePeer(peer.peer)` / `EnginePeer(component.peers[0].peer)` wraps and many ZERO sites.
|
||||
|
||||
- [ ] **Step 15.1:** Use Edit with `replace_all=true`:
|
||||
- old: `EnginePeer(peer.peer)`
|
||||
- new: `peer.peer`
|
||||
|
||||
This covers lines 284, 522, 523.
|
||||
|
||||
- [ ] **Step 15.2:** Edit the `EnginePeer(component.peers[0].peer)` sites at lines 404, 416, 417.
|
||||
|
||||
Use Edit with `replace_all=true`:
|
||||
- old: `EnginePeer(component.peers[0].peer)`
|
||||
- new: `component.peers[0].peer`
|
||||
|
||||
- [ ] **Step 15.3:** Verify no other `EnginePeer(` wraps around `.peer` accesses remain on `RenderedChannelParticipant`. Run:
|
||||
```
|
||||
grep -n 'EnginePeer(.*\.peer)' submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift
|
||||
```
|
||||
Confirm remaining matches are on non-RCP types (e.g., some other context-derived peer).
|
||||
|
||||
---
|
||||
|
||||
## Task 16: Consumer — StoryContentLiveChatComponent.swift
|
||||
|
||||
**File:** `submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift`
|
||||
|
||||
- [ ] **Step 16.1:** Line 370 — DROP `._asPeer()` in constructor.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
peer: author._asPeer()
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
peer: author
|
||||
```
|
||||
(`author` is `EnginePeer` — confirmed by the surrounding code that uses `author.id` and by the `chatPeer` signal's return type.)
|
||||
|
||||
---
|
||||
|
||||
## Task 17: Consumer — ChatControllerAdminBanUsers.swift
|
||||
|
||||
**File:** `submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift`
|
||||
|
||||
- [ ] **Step 17.1:** Line 226 — ADD-WRAP constructor.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
let peer = author
|
||||
renderedParticipants.append(RenderedChannelParticipant(
|
||||
participant: participant,
|
||||
peer: peer
|
||||
))
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
let peer = author
|
||||
renderedParticipants.append(RenderedChannelParticipant(
|
||||
participant: participant,
|
||||
peer: EnginePeer(peer)
|
||||
))
|
||||
```
|
||||
(Confirmed `author` is raw `Peer` via `presentMultiBanMessageOptions(... authors: [Peer], ...)` signature on line 45.)
|
||||
|
||||
- [ ] **Step 17.2:** Line 372 — DROP `._asPeer()` in constructor.
|
||||
|
||||
Before:
|
||||
```swift
|
||||
peer: authorPeer._asPeer()
|
||||
```
|
||||
After:
|
||||
```swift
|
||||
peer: authorPeer
|
||||
```
|
||||
(Confirmed `authorPeer` is `EnginePeer?` at line 327 via `engine.data.get(Peer.Peer(id:))` signal; already guard-unwrapped.)
|
||||
|
||||
- [ ] **Step 17.3:** Line 757 — DROP `._asPeer()` in constructor.
|
||||
|
||||
Same edit pattern as 17.2: `peer: authorPeer._asPeer()` → `peer: authorPeer`.
|
||||
|
||||
---
|
||||
|
||||
## Task 18: Full build verification
|
||||
|
||||
- [ ] **Step 18.1:** Run the full build with `--continueOnError`.
|
||||
|
||||
```sh
|
||||
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: build success. First-pass-clean is the goal (wave-39 pattern applies — classification is exact, migration is mechanical, no inference-bearing return types).
|
||||
|
||||
If the build fails, expect errors only in files in this plan. Any error outside the plan's file list is either:
|
||||
- a pre-existing unrelated WIP (e.g., `ChatMessageTransitionNode.swift`) — not a wave-41 issue
|
||||
- a genuine miss in pre-flight classification — record which file, update the plan, and re-run
|
||||
|
||||
For each error in wave-41 files:
|
||||
1. Read the error
|
||||
2. Classify: is it a shape we mis-identified (ZERO that's not actually transparent) or a new shape (dict subscript, function arg to a `Peer`-typed param, etc.)?
|
||||
3. Apply the appropriate fix (`._asPeer()` if raw Peer needed; unwrap the wrap if EnginePeer needed)
|
||||
4. Re-run the build
|
||||
|
||||
Budget: 1–3 build iterations.
|
||||
|
||||
- [ ] **Step 18.2:** Post-build grep verification.
|
||||
|
||||
Run these greps and confirm they return only the expected residual matches:
|
||||
|
||||
```sh
|
||||
grep -rn 'EnginePeer(participant\.peer)' submodules/ --include='*.swift' | grep -v submodules/TelegramCore/ | grep -v submodules/Postbox/
|
||||
```
|
||||
Expected: empty.
|
||||
|
||||
```sh
|
||||
grep -rn 'EnginePeer(new\.peer)' submodules/ --include='*.swift' | grep -v submodules/TelegramCore/
|
||||
```
|
||||
Expected: empty.
|
||||
|
||||
```sh
|
||||
grep -rn 'participant\.peer as\? TelegramUser' submodules/ --include='*.swift'
|
||||
```
|
||||
Expected: empty.
|
||||
|
||||
```sh
|
||||
grep -n 'public let peer:' submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift
|
||||
```
|
||||
Expected: `public let peer: EnginePeer`.
|
||||
|
||||
---
|
||||
|
||||
## Task 19: Commit
|
||||
|
||||
- [ ] **Step 19.1:** Stage only wave-41 files (explicitly enumerate — wave-39 lesson).
|
||||
|
||||
```sh
|
||||
git status --short
|
||||
```
|
||||
|
||||
Inspect the output. Only wave-41 files should appear as modified. If pre-existing WIP (e.g., `submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift`) is also modified, do NOT include it in the commit.
|
||||
|
||||
```sh
|
||||
git add \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestStartBot.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/AddPeerMember.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelMembers.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelOwnershipTransfer.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerAdmins.swift \
|
||||
submodules/TelegramCore/Sources/TelegramEngine/Peers/Ranks.swift \
|
||||
submodules/PeerInfoUI/Sources/ChannelAdminsController.swift \
|
||||
submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift \
|
||||
submodules/PeerInfoUI/Sources/ChannelMembersController.swift \
|
||||
submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift \
|
||||
submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift \
|
||||
submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift \
|
||||
submodules/SearchPeerMembers/Sources/SearchPeerMembers.swift \
|
||||
submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift \
|
||||
submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift \
|
||||
submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsFilterController.swift \
|
||||
submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift \
|
||||
submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoMembers.swift \
|
||||
submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift \
|
||||
submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift \
|
||||
submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift \
|
||||
docs/superpowers/specs/2026-04-24-renderedchannelparticipant-peer-engine-peer-migration-design.md \
|
||||
docs/superpowers/plans/2026-04-24-renderedchannelparticipant-peer-engine-peer-migration.md
|
||||
```
|
||||
|
||||
(Add any additional files the build iterations surfaced.)
|
||||
|
||||
Run `git status --short` and confirm only staged wave-41 files are green, and any unrelated WIP is still marked as unstaged.
|
||||
|
||||
- [ ] **Step 19.2:** Commit.
|
||||
|
||||
```sh
|
||||
git commit -m "$(cat <<'EOF'
|
||||
Postbox -> TelegramEngine wave 41
|
||||
|
||||
Migrate RenderedChannelParticipant.peer from Postbox `Peer` to
|
||||
TelegramCore `EnginePeer`. 27 files touched: 10 TelegramCore
|
||||
(1 struct + 9 files with constructor wraps) + 17 consumer files.
|
||||
|
||||
Drops the 2 Shape-C wraps installed by wave 39 (ChannelMembersController
|
||||
and ChannelBlacklistController) plus ~37 additional EnginePeer(...) /
|
||||
._asPeer() bridges across the consumer surface. Net ~-14 bridges
|
||||
after the 16 TelegramCore-internal EnginePeer(peer) wraps and the 7
|
||||
consumer ADD-WRAP constructor sites. RCP.peers and RCP.presences
|
||||
dictionaries remain Postbox-typed (deferred).
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
- [ ] **Step 19.3:** Confirm commit landed and working tree is clean except for pre-existing WIP.
|
||||
|
||||
```sh
|
||||
git status --short
|
||||
git log -1 --oneline
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 20: Log the wave outcome
|
||||
|
||||
- [ ] **Step 20.1:** Append wave 41 entry to `docs/superpowers/postbox-refactor-log.md`.
|
||||
|
||||
Format (matching prior wave entries):
|
||||
|
||||
```markdown
|
||||
## Wave 41 outcome — RenderedChannelParticipant.peer: Peer → EnginePeer (2026-04-24)
|
||||
|
||||
Landed as commit `<hash>`. 27 files / ~45 site edits / net ~-14 bridges.
|
||||
|
||||
**Shape distribution:**
|
||||
- TelegramCore: 16 constructor sites wrapped with `EnginePeer(peer)` across 9 files + struct field migrated in ChannelParticipants.swift
|
||||
- Consumers: ~32 DROP (EnginePeer/._asPeer unwraps), 9 CAST (as? TelegramUser → if case let .user), 3 ADD-ASPEER, 7 ADD-WRAP constructor sites
|
||||
|
||||
**First-pass-clean:** <yes|no, iterations count>. Extends wave-39 lesson: first-pass-clean
|
||||
is achievable when classification is exact and all patterns are mechanical.
|
||||
|
||||
**Ratchet economics:** drops 2 wave-39 Shape-C wraps
|
||||
(ChannelMembersController:707, ChannelBlacklistController:381) and installs 7 ADD-WRAP
|
||||
consumer constructor sites as ratchet markers for a future
|
||||
`RenderedChannelParticipant.peers: [PeerId: Peer] → [EnginePeer.Id: EnginePeer]` wave.
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-04-24-renderedchannelparticipant-peer-engine-peer-migration-design.md`.
|
||||
**Plan:** `docs/superpowers/plans/2026-04-24-renderedchannelparticipant-peer-engine-peer-migration.md`.
|
||||
```
|
||||
|
||||
- [ ] **Step 20.2:** Update the `project_postbox_refactor_next_wave.md` memory file with the wave 41 outcome and the wave 42 candidate (likely `PeerInfoScreenData.peer → EnginePeer`).
|
||||
|
||||
- [ ] **Step 20.3:** Commit docs updates.
|
||||
|
||||
```sh
|
||||
git add docs/superpowers/postbox-refactor-log.md
|
||||
git commit -m "$(cat <<'EOF'
|
||||
docs: log wave 41 outcome
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
|
@ -946,6 +946,239 @@ Plan / record: no plan doc this wave — user specified the migration pattern di
|
|||
|
||||
---
|
||||
|
||||
## Wave 37 outcome (2026-04-24)
|
||||
|
||||
`peerTokenTitle(peer: Peer → EnginePeer)`. Private free function in `submodules/TelegramUI/Sources/ContactMultiselectionController.swift`. Atomic single-file commit `734ab44dd2`. 7 insertions / 7 deletions.
|
||||
|
||||
**Migration shape.** Ring-2 cleanup of bridges wave 36 installed. The function body was already round-tripping: callers unwrapped `EnginePeer → Peer` with `._asPeer()` only for the body to re-wrap with `EnginePeer(peer).displayTitle(...)`. Flipping the parameter to `EnginePeer` drops both the 5 call-site bridges and the 1 body-side wrap. Zero semantic change — verified by code-quality reviewer: `displayTitle(strings:displayOrder:)` is defined only on `EnginePeer` (LocalizedPeerData/PeerTitle.swift:29), and `EnginePeer.Id` is a typealias for `PeerId` so `peer.id.isReplies` and `peer.id == accountPeerId` resolve identically.
|
||||
|
||||
**Final scope:**
|
||||
- 1 signature change (L21): `peer: Peer` → `peer: EnginePeer`
|
||||
- 1 body simplification (L27): `EnginePeer(peer).displayTitle(...)` → `peer.displayTitle(...)`
|
||||
- 5 `._asPeer()` bridge-drops at call sites (L171, L201, L386, L403, L748)
|
||||
|
||||
**Pre-flight inventory was exact.** Zero undercount: the pre-flight grep enumerated exactly 6 call-site matches (1 definition + 5 bridges), and the implementer's 3 Edit operations (one unique + two `replace_all=true`) hit all 5 bridge sites on first pass. No scope creep, no out-of-scope edits.
|
||||
|
||||
**First-pass-clean build.** 946 total actions, 259.250s elapsed, `INFO: Build completed successfully`, 0 errors. As a reset wave after wave 36's 6-iteration convergence, this was the expected outcome for a single-file mechanical change targeting a private function.
|
||||
|
||||
**Subagent-driven execution.** First wave executed via `superpowers:subagent-driven-development` with a consolidated implementer dispatch (Tasks 1–3 bundled since all three were grep + mechanical Edit on one file). Two-stage review (spec + code quality) passed cleanly. Controller directly handled build, commit, and log/memory updates.
|
||||
|
||||
**Lessons:**
|
||||
|
||||
- **Bundling mechanical plan-tasks is valid for same-file micro-waves.** When a plan's tasks are all Edit operations on a single file with no inter-task branches, consolidating into one implementer dispatch preserves the review structure (spec + quality still gated) while avoiding per-task subagent overhead. For larger waves with multi-file surface or branching logic, keep per-task dispatch.
|
||||
|
||||
- **Private-function ring-2 cleanups are first-pass-clean candidates.** The ratchet behavior observed in waves 33–36 — prior waves add bridges at next-ring boundaries — creates trivially-closable cleanup waves whenever the next-ring callee is itself a private or small-surface function. Wave 37's target (private free function, 5 bridge sites, all in-file) hit the ceiling of what this shape can yield.
|
||||
|
||||
- **Post-wave 36 bridge inventory reduced by 5.** ContactMultiselectionController:171/201/386/403/748 are now Peer-free at the `peerTokenTitle` arg; the file still retains `import Postbox` for other APIs (L459's `SelectedPeer.peer(peer: Peer, ...)` feed), so file-level Postbox-free remains a later-wave target.
|
||||
|
||||
**Plan / record:** `docs/superpowers/plans/2026-04-24-peertokentitle-engine-peer-migration.md`. Spec: `docs/superpowers/specs/2026-04-24-peertokentitle-engine-peer-migration-design.md`.
|
||||
|
||||
---
|
||||
|
||||
## Wave 38 outcome (2026-04-24)
|
||||
|
||||
`canSendMessagesToPeer(_ peer: Peer → EnginePeer, ignoreDefault:)`. Public utility function in `submodules/TelegramCore/Sources/Utils/CanSendMessagesToPeer.swift`. Atomic multi-file commit `45729bad1c`. 12 files changed, 25 insertions / 24 deletions.
|
||||
|
||||
**Migration shape.** Mixed-direction ring-2 cleanup: 18 Shape-A bridge drops (`._asPeer()` at call sites where caller already holds `EnginePeer`) plus 5 Shape-C bridge adds (`EnginePeer(peer)` wraps where caller holds raw Postbox `Peer`). Function body preserved by inserting a single `let peer = peer._asPeer()` shadow as the first body line — the four `peer as? TelegramUser/TelegramGroup/TelegramSecretChat/TelegramChannel` branches remain unchanged and the file still `import Postbox`.
|
||||
|
||||
**Final scope:**
|
||||
- 1 signature change (CanSendMessagesToPeer.swift:9): `peer: Peer` → `peer: EnginePeer`
|
||||
- 1 body insertion (CanSendMessagesToPeer.swift:10): `let peer = peer._asPeer()`
|
||||
- 18 Shape-A drops across 7 files: ContactsSearchContainerNode (3), ChatImageGalleryItem (1), UniversalVideoGalleryItem (1), StoryItemSetContainerViewSendMessage (1), ShareSearchContainerNode (3), ChatListSearchListPaneNode (6), ChatListNode (3)
|
||||
- 5 Shape-C adds across 5 files: LegacyAttachmentMenu, ChatInterfaceInputContexts, ShareSearchContainerNode, ShareController, ChatPresentationInterfaceState
|
||||
- ShareSearchContainerNode has mixed shape (3 drops + 1 add)
|
||||
|
||||
**Pre-flight inventory was exact.** 24 total grep matches = 1 definition + 23 call sites; 18 Shape-A + 5 Shape-C. Post-migration grep: 0 `_asPeer()` residue, 5 `EnginePeer(` wraps, 24 total matches. No scope creep. No WIP-drift adjustments needed.
|
||||
|
||||
**First-pass-clean build.** 568 total actions, 233.471s elapsed, `INFO: Build completed successfully`, 0 compilation errors. This contradicts the memory's pre-wave prediction of 3–5 build iterations per wave-36 lesson — the lesson remains valid for waves with cascading type-inference surfaces, but wave 38's surface (a utility function called only at points that already bridge `EnginePeer ↔ Peer` in both directions) had no such cascades. The Shape-A/C classification captured the full surface before code changes began.
|
||||
|
||||
**Subagent-driven execution.** Executed via `superpowers:subagent-driven-development`. Implementer dispatch bundled plan Tasks 1–5 (grep + all 24 edits + post-edit grep) since all were mechanical and on known files. Two-stage review (spec + code quality) passed without review-loop iterations. Controller handled build, commit, and log/memory updates.
|
||||
|
||||
**Lessons:**
|
||||
|
||||
- **Bundled multi-file dispatch is valid when pre-flight classification is exact.** Wave 36's per-task-dispatch rule (from CLAUDE.md) was formulated for waves with expected 3–5 build iterations driven by type-inference cascades. Wave 38 demonstrates that a multi-file wave with an exact upfront Shape-A/C classification and a cascading-free surface (all call sites are leaf arguments to a `Bool`-returning utility) can use the bundled dispatch shape safely. Gate: (a) all edits are mechanical Edit operations; (b) pre-flight grep enumerates every call-site explicitly; (c) return type of the migrated API does not propagate into caller type inference. When these hold, bundled dispatch saves subagent overhead without sacrificing review coverage.
|
||||
|
||||
- **First-pass-clean multi-file waves exist.** The memory's pre-wave prediction of "not first-pass-clean territory" was conservative and came from wave 36's 6-iteration experience. Wave 38's 12-file mechanical migration built clean on first attempt because the spec's explicit Shape-A/C classification enumerated all 23 sites, and the function's `Bool` return prevents caller-side type-inference cascades. Future multi-file waves should update expectations when (a) the classification is exact and (b) the return type does not propagate.
|
||||
|
||||
- **Shape-C wraps remain valid additions.** Five sites had to add `EnginePeer(peer)` at the call because the enclosing scope still holds raw Postbox `Peer` (from `RenderedPeer.peers` lookups, `ChatPresentationInterfaceState.renderedPeer.peer`, or `as? TelegramChannel` casts). These adds are acceptable — the alternative is a per-scope refactor (RenderedPeer → EngineRenderedPeer cascades) that would expand blast radius. Future waves targeting `RenderedPeer` would drop these 5 wraps.
|
||||
|
||||
**Plan / record:** `docs/superpowers/plans/2026-04-24-cansendmessagestopeer-engine-peer-migration.md`. Spec: `docs/superpowers/specs/2026-04-24-cansendmessagestopeer-engine-peer-migration-design.md`.
|
||||
|
||||
---
|
||||
|
||||
## Wave 39 outcome (2026-04-24)
|
||||
|
||||
`AccountContext.makePeerInfoController(... peer: Peer → EnginePeer ...)`. Public protocol method on `AccountContext` (`submodules/AccountContext/Sources/AccountContext.swift:1371`) and its `SharedAccountContextImpl` implementation (`submodules/TelegramUI/Sources/SharedAccountContext.swift:1937`). Atomic multi-file commit `5385abc9bd`. **52 files changed**, 80 insertions / 79 deletions.
|
||||
|
||||
**Migration shape.** Body-shadow ring-2 cleanup at the largest scale yet attempted. The protocol + impl signatures change to `peer: EnginePeer`; the impl body adds `let peer = peer._asPeer()` as its first statement, preserving the private downstream `peerInfoControllerImpl(peer: Peer, ...)` and all of its Peer-typed helpers as out-of-scope. Mixed-direction at consumer sites: 58 Shape-A drops + 3 Shape-A-variant guard-statement drops + 12 Shape-C `EnginePeer(...)` wraps. Net **-49 bridges**.
|
||||
|
||||
**Final scope:**
|
||||
- 1 protocol-signature change (AccountContext.swift:1371)
|
||||
- 1 impl-signature change + 1 body-shadow insertion (SharedAccountContext.swift:1937–1938)
|
||||
- 3 Shape-A self-call drops in same file (SharedAccountContext.swift:3335, 3483, 4016)
|
||||
- 3 Shape-A-variant guard-statement drops (SettingsSearchableItems.swift around 1020/1046/1080): `guard let peer = peer?._asPeer() else { return }` → `guard let peer = peer else { return }`. The call-site `peer:` argument line is unchanged; the upstream guard now binds `peer` as `EnginePeer` (the closure parameter from `engine.data.get(...)`) rather than re-shadowing as raw `Peer`.
|
||||
- 12 Shape-C `EnginePeer(...)` wraps across 8 files: BlockedPeersController:270, ChannelMembersController:707, ChannelBlacklistController:381, ChatRecentActionsControllerNode:1011, PeerInfoScreen:4306, ChatControllerNavigationButtonAction (4 sites: 441/461/471/492), ChatControllerOpenPeer (2 sites: 218/359), ChatControllerLoadDisplayNode:4362
|
||||
- 55 additional Shape-A drops across 42 consumer files
|
||||
- 4 incidental trailing-whitespace strips picked up by Edit tool (functionally identical)
|
||||
|
||||
**Pre-flight inventory was exact.** Total grep matches at planning time: 75 (1 protocol decl + 1 impl + 73 consumer call sites) of which 58 had inline `peer: <expr>._asPeer()` (Shape-A), 3 had upstream-guard `peer?._asPeer()` patterns (Shape-A-variant), and 12 had raw `peer: <expr>` (Shape-C). Post-migration grep for `_asPeer()` at `makePeerInfoController` call sites: 0. Post-migration grep for `EnginePeer(` at the 12 Shape-C sites: all present. No scope creep, no Postbox/TelegramCore/TelegramApi edits. The implementer noted that `BlockedPeersController.swift:270` already had `peer: peer` (no `_asPeer()`) — this was correctly classified Shape-C in the spec, and the wrap was applied as planned.
|
||||
|
||||
**Property-typing pre-verification matters.** Spec phase identified that `RenderedPeer.peer`/`chatMainPeer` (Postbox extension at PeerUtils.swift:512 and Postbox/RenderedPeer.swift:38) return raw `Peer?`, while `EngineRenderedPeer.peer`/`chatMainPeer` (TelegramCore/Peers/Peer.swift:623/627) return `EnginePeer?`. Six Shape-C sites depend on `RenderedPeer` (Postbox), so `EnginePeer(...)` wraps were correct. Sites that consume `EngineRenderedPeer` would need `peer: peer` only — not a concern here, but a checklist item for next-wave Shape-C planning.
|
||||
|
||||
**First-pass-clean build.** 658 total actions, 210.520s elapsed, 1565 cache hits + 142 disk-cache + 444 worker, `INFO: Build completed successfully`, 0 errors. Strongest confirmation yet of the wave-38 lesson: **first-pass-clean is achievable for 50+ file waves when (a) the migrated API's return type is non-propagating** (`ViewController?` is an optional reference type, like wave-38's `Bool` — caller-side type inference does not branch on it), **(b) pre-flight Shape-A/A-variant/C classification is exact, and (c) the body-shadow boundary is preserved.** Pre-wave estimate budgeted 2–4 iterations on the assumption that a 50-file change at a popular API surface would surface destructure cascades; the actual outcome exceeded expectations.
|
||||
|
||||
**Subagent-driven execution.** Executed via `superpowers:subagent-driven-development`. Implementer dispatch bundled plan Tasks 1–5 (signature + body-shadow + 3 self-call drops + 3 guard rewrites + 12 wraps + 55 Shape-A drops, 70+ Edit operations across 52 files). Two-stage review (spec + code quality) passed without re-review iterations. Spec reviewer verified all 73 sites, the body-shadow placement at line 1938, the guard-statement form change in SettingsSearchableItems, and the unchanged-out-of-scope confirmation for `peerInfoControllerImpl` at line 4434. Code quality reviewer noted only the 4 incidental trailing-whitespace strips as "minor — leave as-is, tiny improvement". Controller handled build, commit, and log/memory updates.
|
||||
|
||||
**Lessons:**
|
||||
|
||||
- **Shape-A-variant: drop the upstream `_asPeer()` rather than wrapping at the call site.** When a guard-statement immediately upstream of a `makePeerInfoController` call unwraps `EnginePeer? → Peer?` via `peer?._asPeer()`, prefer rewriting the guard to `guard let peer = peer else` (keeping the local as `EnginePeer`) over adding `EnginePeer(peer)` at the call. The variant: zero net change at the call line, -1 `_asPeer()` upstream. This pattern occurs whenever a closure parameter from `engine.data.get(...)` is opt-unwrapped immediately before a Peer-typed-API call.
|
||||
|
||||
- **First-pass-clean ceiling is higher than wave-36 implied.** Wave 36's 6-iteration convergence (15 files, ContactListPeer.peer enum-payload migration) led to a memory rule "multi-file = budget 3–5 iterations". Wave 38 first-pass-clean (12 files, Bool return) suggested the rule doesn't apply universally. Wave 39 first-pass-clean (52 files, ViewController? return) extends this: even at 50+ files, the determinant is return-type propagation behavior + pre-flight classification accuracy, not file count. Updated heuristic: **budget for first-pass-clean when** (return-type is non-propagating) AND (Shape classification is exact); **budget for 3–5 iterations when** (return type is a generic container, struct with associated types, or enum that participates in caller-side inference).
|
||||
|
||||
- **Bundled implementer dispatch scales to 70+ edits across 52 files.** The wave-38 lesson ("bundled multi-file dispatch is valid when pre-flight classification is exact") held at this scale. Two-stage review (spec then code-quality) over the aggregate output preserved review coverage. No per-task subagent dispatches were needed.
|
||||
|
||||
- **Atomic-stage exclusion of pre-existing WIP is necessary at large diff sizes.** Wave 39's working tree contained an unrelated WIP file (`ChatMessageTransitionNode.swift` — function-signature/animation changes on `beginAnimation`) that was modified outside the session. The commit phase explicitly enumerated all 52 wave-39 files in the `git add` invocation rather than using `git add -u` or `git add .`. This is a permanent rule for this project: never use bulk-stage commands; always enumerate files when committing a wave.
|
||||
|
||||
- **Ratchet expansion: 12 new Shape-C wraps for future waves.** The 12 `EnginePeer(...)` wraps installed at this wave become drop candidates for later waves migrating their upstream sources: `RenderedPeer → EngineRenderedPeer` (would drop 6 wraps at sites consuming `renderedPeer.peer`/`chatMainPeer`), `RenderedChannelParticipant.peer → EnginePeer` (would drop 2 at ChannelMembers/ChannelBlacklist), and others. Net economics still strongly favor the wave: -61 + 12 = -49 bridges.
|
||||
|
||||
**Plan / record:** `docs/superpowers/plans/2026-04-24-makePeerInfoController-engine-peer-migration.md`. Spec: `docs/superpowers/specs/2026-04-24-makePeerInfoController-engine-peer-migration-design.md`.
|
||||
|
||||
---
|
||||
|
||||
## Wave 40 outcome (2026-04-24)
|
||||
|
||||
Commit: `d3c48379fe`. Bundle of `AccountContext.makeChatQrCodeScreen` + `makeChatRecentActionsController` peer `Peer → EnginePeer` — the trivial sibling follow-up to wave 39, completing the "Option 1 cluster" (`makePeerInfoController` family) from the wave-38 memory. 8 Swift files + plan doc / 11 edits. Pre-flight classification was already done in the wave-39 design doc's "Out of scope" section — no fresh pre-flight needed.
|
||||
|
||||
**Classification:**
|
||||
- 2 protocol decls (`AccountContext.swift:1401`, `:1461`)
|
||||
- 2 impl decls + body-shadows (`SharedAccountContext.swift:2302`, `:2731`)
|
||||
- 2 Shape-A-variant guard rewrites (`SettingsSearchableItems.swift:971`, `:989` — `guard let peer = peer?._asPeer()` → `guard let peer = peer`, keeping the local as `EnginePeer`)
|
||||
- 3 Shape-A drops (`ContactsController.swift:478`, `ChannelAdminsController.swift:734`, `GroupStatsController.swift:915`)
|
||||
- 2 Shape-C wraps (`PeerInfoScreen.swift:4623`, `PeerInfoScreenOpenChat.swift:115`) — both consume `data.peer: Peer?` from `PeerInfoScreenData`, so they're ratchet markers for a future `PeerInfoScreenData.peer Peer → EnginePeer` wave.
|
||||
|
||||
Net −3 bridges (−5 `_asPeer()` drops, +2 `EnginePeer(...)` wraps).
|
||||
|
||||
**Build outcome:** First-pass-clean for wave-40 files. The initial run failed due to pre-existing unrelated WIP in `ChatMessageTransitionNode.swift` (unterminated string literals in debug `print` statements the user was mid-editing); the wave-40 files produced zero diagnostics. After the user fixed the WIP, the subsequent build completed in 23.9s (mostly cache-warm) with zero errors.
|
||||
|
||||
**Lessons:**
|
||||
|
||||
- **Bundled sibling migration with shared pre-flight is cheap.** Wave 39's "Out of scope" section pre-classified all 7 of this wave's consumer sites. That planning overhead was already paid; wave 40 only needed to verify classifications still hold (one grep per site-group) and apply mechanical edits. Total time from plan-write to commit: ~30 minutes (dominated by the ~3-minute build). This validates a general pattern: when a wave defers siblings with an explicit "Out of scope" classification section, the follow-up wave is structurally trivial.
|
||||
|
||||
- **Small-scale bundled implementer dispatch is still the right choice.** 11 edits across 8 files fits comfortably in one implementer call, matching the wave-38/39 lesson at smaller scale. No per-task dispatches were needed; two-stage review (spec then code-quality) took one round each.
|
||||
|
||||
- **Pre-existing WIP in the working tree can cause module-scope build failures that mask the wave's own status.** When `ChatMessageTransitionNode.swift` had parse errors from an unrelated user WIP, the whole `TelegramUI` Swift module failed to parse before type-checking ran — so wave-40 files got syntax-verified but not type-verified. Diagnostic approach: grep the build output for error lines whose file path is NOT in the wave's file list; if the only errors are outside, the wave is likely clean, but complete type verification requires the unrelated WIP to be reverted or fixed. In this case the user fixed it; in future cases either ask the user, temporarily stash the WIP, or note the incomplete verification in the commit.
|
||||
|
||||
**Plan / record:** `docs/superpowers/plans/2026-04-24-makeChatQrCodeScreen-recentActions-engine-peer-migration.md`. No separate spec — the pre-flight was reused from wave 39's spec ("Out of scope" section in `docs/superpowers/specs/2026-04-24-makePeerInfoController-engine-peer-migration-design.md`).
|
||||
|
||||
---
|
||||
|
||||
## Wave 41 outcome (2026-04-24)
|
||||
|
||||
Commit: `32573c9808`. `RenderedChannelParticipant.peer: Peer → EnginePeer` — a TelegramCore foundational-type field migration affecting 28 files (11 TelegramCore + 17 consumer) / 1124 insertions / 89 deletions. First foundational-type field migration since `FoundPeer.peer` / `SendAsPeer.peer` / `ContactListPeer.peer` (waves 34–36); differs in that `RenderedChannelParticipant` is a public TelegramCore struct with both TelegramCore-internal construction sites and a broad consumer surface, so the wave touched TelegramCore internals too, not just consumer modules.
|
||||
|
||||
**Classification:**
|
||||
- 1 struct field change (`ChannelParticipants.swift`): `peer: Peer → peer: EnginePeer` + init param + Equatable `lhs.peer.isEqual(rhs.peer) → lhs.peer == rhs.peer`
|
||||
- 16 TelegramCore-internal constructor sites wrapped with `EnginePeer(peer)` across 9 files (RequestStartBot, AddPeerMember, ChannelAdminEventLogs [7 calls], ChannelBlacklist, ChannelMembers, ChannelOwnershipTransfer [2 calls], JoinChannel, PeerAdmins, Ranks)
|
||||
- 1 TelegramCore-internal ADD-ASPEER (`SearchGroupMembers.swift:83` — pre-flight miss: `participants.map({ $0.peer })` consumed by outer `[Peer]`-returning closure)
|
||||
- ~32 consumer DROP sites (removing `EnginePeer(participant.peer)` wraps or `._asPeer()` downgrades)
|
||||
- 9 consumer CAST sites (`if let user = participant.peer as? TelegramUser, user.botInfo != nil` → `if case let .user(user) = participant.peer, user.botInfo != nil`)
|
||||
- 3 consumer ADD-ASPEER (PeerInfoMembers:33 contains the `PeerInfoMember.peer: Peer` accessor ratchet; ChatRecentActionsHistoryTransition:675 + :2275 for `SimpleDictionary<PeerId, Peer>` subscript assignment)
|
||||
- 7 consumer ADD-WRAP constructor sites (ChannelMembersSearchContainerNode + ChannelMembersSearchControllerNode legacy-group paths, ChatControllerAdminBanUsers:226) — ratchet markers for a future wave migrating `peerView.peers[id]` / `authors: [Peer]` upstream flows to EnginePeer
|
||||
- 2 consumer `is TelegramChannel` → `if case .channel = participant.peer` rewrites (ChannelBlacklistController:370, :377)
|
||||
|
||||
Net ~−13 bridges. Drops the 2 Shape-C wraps installed by wave 39 (ChannelMembersController:707, ChannelBlacklistController:381) as promised by the wave-39 ratchet plan.
|
||||
|
||||
**Build outcome:** Three iterations.
|
||||
1. First build surfaced `SearchGroupMembers.swift:83` — a TelegramCore-internal consumer of `$0.peer` on RCP that the plan's grep missed (grep only looked for `RenderedChannelParticipant(` constructors inside TelegramCore).
|
||||
2. Second build surfaced `ShareWithPeersScreen.swift:777, 780` — an entire file missed in pre-flight because the initial grep was scoped to files already known to import RCP; this file gets RCP indirectly via `TemporaryCachedPeerDataManager.recent(...)`'s `updated:` callback.
|
||||
3. Third build: clean (172 total actions).
|
||||
|
||||
**Lessons:**
|
||||
|
||||
- **Property-access migration has a broader grep surface than constructor migration.** Waves 34–36 migrated foundational types by grepping `<Type>(` constructors. That works because the consumer's use of the type is always preceded by a construction. Wave 41's RCP differs: RCP is constructed inside TelegramCore and handed to consumers as an opaque reference, so consumer usage looks like `participant.peer.X` (with any name, any subscript, any map closure). **Grep for the type's field-access patterns across the entire repo**, not just files that explicitly reference the type. For RCP specifically: `grep -rn 'RenderedChannelParticipant\|\.peer as?\|EnginePeer(.*\.peer)\|participant\.peer' submodules/` would have caught ShareWithPeersScreen.swift. File this as a **permanent pre-flight rule** for future property-access migrations.
|
||||
|
||||
- **TelegramCore-internal consumer sites need the same inventory discipline as consumer-module sites.** The wave-41 plan enumerated 10 TelegramCore files for *construction* site updates but didn't grep for *consumption* sites — `SearchGroupMembers.swift:83` consumes `$0.peer` on RCP inside TelegramCore. Pre-flight for any foundational-type field migration must grep both construction AND consumption across the whole repo, not stratified by module.
|
||||
|
||||
- **"Plan says N sites" numbers can be low; use `replace_all=true` defensively.** The plan listed 1 `EnginePeer(participant.peer)` site in ChannelBlacklistController.swift but the file actually had 5; listed 2 CAST sites in ChannelMembersSearchControllerNode.swift but the file had 4. The implementer correctly used `replace_all=true` on the unique-pattern greps, catching all sites. **Pre-flight should report "N site(s) per file at these lines" — if confidence is low, recommend replace_all directly.**
|
||||
|
||||
- **Spec review caught `is TelegramChannel` that grep did not.** `participant.peer is TelegramChannel` does not match any of the usual migration-pattern greps (`EnginePeer(...)`, `as? TelegramUser`, `._asPeer()`). The spec reviewer caught it by reading the code. Would have been a build error anyway — the project compiles with `-warnings-as-errors` across 658/665 modules, so Swift's "'is' test always fails" warning promotes to an error — but catching it at spec time saves one build iteration. **Add `is Telegram(Channel|User|Group|SecretChat)` to the pre-flight token set** for any Peer → EnginePeer field migration so it's flagged at plan time rather than iteration time.
|
||||
|
||||
- **First-pass-clean is NOT the right bar for foundational-type field migration.** Waves 37–40 achieved first-pass-clean. Wave 41 needed 3 iterations — and none of the three errors were the implementer's fault; all three were pre-flight misses. Heuristic update: **foundational-type field migrations (where the field is accessed via a generic property name like `.peer`) should budget 2–4 build iterations**, not first-pass-clean. The broader the grep surface, the more misses slip through.
|
||||
|
||||
- **Ratchet economics of ADD-WRAP consumer constructors.** Wave 41 added 7 consumer `peer: EnginePeer(peer)` wraps at `RenderedChannelParticipant(...)` constructor sites in ChannelMembersSearch*Node and ChatControllerAdminBanUsers, where the local is still raw `Peer` from a legacy path (`peerView.peers[id]` / `authors: [Peer]`). These are ratchet markers: when the upstream legacy path migrates to EnginePeer, these wraps drop. The economics are net positive (−13 bridges) even counting the adds.
|
||||
|
||||
**Plan:** `docs/superpowers/plans/2026-04-24-renderedchannelparticipant-peer-engine-peer-migration.md`.
|
||||
**Spec:** `docs/superpowers/specs/2026-04-24-renderedchannelparticipant-peer-engine-peer-migration-design.md`.
|
||||
|
||||
---
|
||||
|
||||
## Wave 42 outcome (2026-04-24)
|
||||
|
||||
`PeerInfoScreenData.peer: Peer? → EnginePeer?` — a single-module (18-file) struct-field migration entirely contained within `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/`. `PeerInfoScreenData` is a consumer-module data class, not a public TelegramCore type, so the wave never touched TelegramCore — contrast wave 41 which had to update TelegramCore construction sites.
|
||||
|
||||
**Classification:**
|
||||
- 1 struct-field change + 1 init-param change in PeerInfoData.swift
|
||||
- 4 construction-site edits adding `.flatMap(EnginePeer.init)` wraps at `peer:` arguments built from `peerView.peers[...]` (PeerInfoData.swift L1027, L1620, L1867, L2205); 1 construction site unchanged (L1100, `peer: nil`)
|
||||
- ~25 consumer DROP sites (removing `EnginePeer(peer)` wraps where the local `peer` was bound from `data.peer` / `self.data?.peer`)
|
||||
- ~25 consumer CAST sites (`if let user = data.peer as? TelegramUser, ...` → `if case let .user(user) = data.peer, ...`)
|
||||
- ~5 consumer `is TelegramXxx` rewrites on data.peer-derived locals (PeerInfoScreen.swift L3981, L4133, L4192, L4194)
|
||||
- ~10 consumer ADD-WRAP `?._asPeer()` helper bridges for internal helpers that stay `peer: Peer?` (PeerInfoScreenPerformButtonAction:62 calling `peerInfoIsChatMuted`; PeerInfoScreen:5399/5805 calling `self.headerNode.update(peer:...)`; PeerInfoScreen:5857 calling `peerInfoCanEdit`; etc.) plus one inline `_asPeer()` bridge inside `peerInfoIsCopyProtected` because `isCopyProtectionEnabled` is not exposed on `EnginePeer`
|
||||
|
||||
Net ~−30 bridges. Drops the ~5 wave-40 `EnginePeer(peer)` wraps that were directly in scope (the plan initially overestimated which sites would drop — some `EnginePeer(peer)` calls in PeerInfoScreen.swift are inside closures whose `peer` parameter comes from a caller, not from `data.peer`, and those correctly survived).
|
||||
|
||||
**Build outcome:** Two iterations.
|
||||
1. First build surfaced `peerInfoIsCopyProtected` helper: `peer.isCopyProtectionEnabled` failed against `EnginePeer` because that property is `Peer`-protocol-only and is not forwarded by `EnginePeer`. Fix: inline `peer._asPeer().isCopyProtectionEnabled` bridge at the helper callsite (helper signature stays `Peer?`).
|
||||
2. Second build: clean.
|
||||
|
||||
**Lessons:**
|
||||
|
||||
- **EnginePeer is not a strict superset of Peer's property surface.** `EnginePeer` forwards `id`, `displayTitle(...)`, `compactDisplayTitle`, `isPremium`, `smallProfileImage`, `largeProfileImage`, and other common properties, but Peer-only properties like `isCopyProtectionEnabled` are NOT forwarded. Migration-time surprise: a property access that worked on raw `Peer` can fail against `EnginePeer`. **Bridge pattern:** inline `peer._asPeer().isCopyProtectionEnabled` at the callsite, OR if the helper already takes `Peer?`, bridge upstream with `?._asPeer()` and keep the access unchanged. Pre-flight addition: grep migrated consumer surfaces for `.X` accesses where `X` is a Peer-protocol property not in EnginePeer's forwarding set. (Alternative: extend `EnginePeer` with more forwarders, but that's a larger TelegramCore change out of scope for a single wave.)
|
||||
|
||||
- **Wave-42 was not first-pass-clean despite being a consumer-module wave.** Contrast wave 41: 3 iterations due to broader grep-surface misses. Wave 42: 2 iterations due to a single property-forwarding-gap miss. Heuristic update: **even consumer-module property-access migrations budget 2 iterations**, because Peer/EnginePeer interface parity is never verified end-to-end at pre-flight time.
|
||||
|
||||
- **Plan-stated wrap-drop counts can be wrong in both directions.** The plan listed 15+ `EnginePeer(peer)` drop sites in PeerInfoScreen.swift citing specific lines (1331, 1339, 1346, 1561, 2353, ...) — but on inspection, lines 1331/1339/1346 are inside a `peer`-parameter closure (`openPeerContextAction = { ..., peer, ... in ... }`) where `peer` comes from the closure's caller, not from `data.peer`. Those wraps correctly stayed. The implementer correctly judged each binding context before deciding whether to drop. **Rule for plan writing:** when naming specific line numbers as drop-candidates, verify the `if let peer = ...` / closure-param binding in the actual file text, not just the `EnginePeer(peer)` grep result.
|
||||
|
||||
- **Wave-41 lesson reconfirmed (`is Telegram*` rewrite):** all 5 `is TelegramChannel|User|Group|SecretChat` checks on `data.peer`-derived locals were caught at plan time (via the pre-flight grep token set including `is Telegram...`), not at build time. `-warnings-as-errors` would have caught them at build time but pre-flight catches save an iteration.
|
||||
|
||||
- **Internal-helper bridge economics.** The PeerInfoScreen module has ~6 internal helpers (`canEditPeerInfo`, `peerInfoIsChatMuted`, `peerInfoHeaderButtons`, `peerInfoHeaderActionButtons`, `peerInfoCanEdit`, `availableActionsForMemberOfPeer`, `peerInfoIsCopyProtected`) that still take `peer: Peer?`. Wave 42 added ~10 `?._asPeer()` bridges at their callsites. A follow-up wave migrating those helper signatures would drop exactly those 10 bridges — small, well-scoped, strong candidate for wave 42.x.
|
||||
|
||||
**Plan:** `docs/superpowers/plans/2026-04-24-peerinfoscreendata-peer-engine-peer-migration.md`.
|
||||
|
||||
---
|
||||
|
||||
## Wave 43 outcome (2026-04-24)
|
||||
|
||||
Migrated six PeerInfoScreen module helpers — `canEditPeerInfo`, `availableActionsForMemberOfPeer`, `peerInfoHeaderActionButtons`, `peerInfoHeaderButtons`, `peerInfoCanEdit`, `peerInfoIsChatMuted` (plus nested `isPeerMuted`) — from `peer: Peer?` to `peer: EnginePeer?`. All internal `as? TelegramUser|Channel|Group` and `is TelegramX` patterns inside the helper bodies (PeerInfoData.swift lines 2265–2670) rewritten to `case let .user/.channel/.legacyGroup` / `case .x = peer` enum patterns. No TelegramCore changes. No new typealiases.
|
||||
|
||||
**Classification:**
|
||||
- 6 helper signature migrations + 12 `as?` / `is` body rewrites in PeerInfoData.swift
|
||||
- 7 DROPs of `._asPeer()` / `?._asPeer()` bridges installed by wave 42 (sites in PeerInfoScreenAvatarSetup, PeerInfoScreenPerformButtonAction ×3, PeerInfoScreenOpenMember, PeerInfoScreen:5857, PeerInfoProfileItems)
|
||||
- 2 CONVERTs at PeerInfoScreen.swift:1905/1961 (`peer: group` / `peer: channel` extracted from `case let` → `peer: data.peer`, preserving the `group`/`channel` binding for body use)
|
||||
- 10 ADD-WRAPs at call sites inside enclosing methods that still take raw `Peer?` (PeerInfoHeaderNode ×3, PeerInfoHeaderEditingContentNode ×5, PeerInfoEditingAvatarNode, PeerInfoEditingAvatarOverlayNode) — `.flatMap(EnginePeer.init)` for optional Peer, `EnginePeer(peer)` for non-optional (post-`guard let peer = peer`) Peer
|
||||
- 2 ADD-WRAPs at raw-`Peer` member-item sites (PeerInfoScreenMemberItem, PeerInfoMembersPane)
|
||||
|
||||
Net ~+5 wraps overall (7 drops, 12 adds) — less important than the headline: the helper signatures now live on the engine side. The 12 ADDs are all staged for drop in follow-up waves that migrate the four `update(peer: Peer?, ...)` methods (PeerInfoHeaderNode, PeerInfoHeaderEditingContentNode, PeerInfoEditingAvatarNode, PeerInfoEditingAvatarOverlayNode) and the two raw-`Peer` enclosingPeer storage fields (PeerInfoScreenMemberItem.enclosingPeer is actually `Peer?`, PeerInfoMembersPane's local `enclosingPeer: Peer`).
|
||||
|
||||
**Build outcome:** 2 iterations.
|
||||
1. First build surfaced one error: `PeerInfoScreenMemberItem.item.enclosingPeer` was declared `let enclosingPeer: Peer?` (optional) — the plan had prescribed `EnginePeer(item.enclosingPeer)` (non-optional form). Fix: `.flatMap(EnginePeer.init)` at the callsite. One-line correction.
|
||||
2. Second build: clean.
|
||||
|
||||
**Commit:** `d53e0d50f4c0e3e68c4e4c1ce255e76f43f56d4b` (12 files + plan).
|
||||
|
||||
**Lessons:**
|
||||
|
||||
- **Plan-declared property optionality can be wrong — verify `let X: T?` vs `let X: T` at plan-write time.** Wave 43's plan described `item.enclosingPeer` as non-optional `Peer` based on how it was used (`item.enclosingPeer as? TelegramChannel`, `item.enclosingPeer is TelegramChannel` compile against `Peer` non-optional OR optional protocol). The declaration was actually `Peer?`. The one-site plan-declaration mismatch cost one build iteration. **Rule for plan writing:** for every ADD-WRAP site, cite the declaration line, not just the usage. `grep -nE "(let|var)\s+\w+:\s*Peer\??" <file>` gives unambiguous optionality.
|
||||
|
||||
- **Helper-signature migrations with zero Peer-only property access are first-iteration-clean except for optionality surprises.** Wave 42's lesson (EnginePeer property-forwarding gap) did not recur here because wave 43's helpers only did `as?` / `is` casts — pure enum-rewrite territory. Pre-flight verified each helper body used only `.id` and concrete-type accesses (`TelegramChannel.hasPermission(...)` etc., which stay on concrete types post-migration). The only iteration cost was the optionality mismatch above. **Heuristic: if a helper's body does not touch the `peer` parameter outside of `as?`/`is`/`.id`/`.id.isX`, budget 1 iteration + optionality-audit pass.**
|
||||
|
||||
- **`case .x = peer` without binding is the correct `-warnings-as-errors` form when the branch body doesn't need the concrete value.** Wave 43's `peerInfoIsChatMuted` body at lines 2641/2643 uses `case .user = peer` and `case .legacyGroup = peer` (no inner binding) — because the branches only read `globalNotificationSettings.privateChats.enabled` / `.groupChats.enabled`, never the concrete `TelegramUser` / `TelegramGroup`. Had the migration emitted `case let .user(_) = peer` with `_`, or `case let .user(user) = peer` with `user` unused, `-warnings-as-errors` would fail. **Pre-flight rule: for each `peer is TelegramX` → `case .x = peer` rewrite, check whether the branch body accesses the concrete type. If not, use bare-case form; if yes, bind.**
|
||||
|
||||
- **Bundled-dispatch subagent flow + two-stage review works cleanly for ≥10-site single-commit migrations.** Wave 43 dispatched one implementer (bundled Tasks 1–7), then spec reviewer, then code quality reviewer. All three completed in roughly 10 minutes of subagent time. Spec reviewer caught zero misses (implementer self-caught the optionality deviation in iteration 1). Code quality reviewer surfaced only minor observational notes (no Critical / Important issues). **Continues to confirm wave-39/40/41/42 finding: for ≤30-file / ≤50-edit wave shapes, the bundled flow is reliable; TaskCreate tracking adds no value at this size.**
|
||||
|
||||
**Plan:** `docs/superpowers/plans/2026-04-24-peerinfoscreen-helpers-engine-peer-migration.md`.
|
||||
|
||||
---
|
||||
|
||||
## Modules currently free of `import Postbox` (running tally)
|
||||
|
||||
Consumer modules that no longer import Postbox, across all waves and standalone commits:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,158 @@
|
|||
# Wave 39 — `makePeerInfoController` peer: Peer → EnginePeer migration
|
||||
|
||||
Date: 2026-04-24
|
||||
|
||||
## Context
|
||||
|
||||
Ring-2 cleanup of the `AccountContext` Peer-typed-API surface. Waves 34 (FoundPeer.peer), 35 (SendAsPeer.peer), 36 (ContactListPeer.peer), 37 (peerTokenTitle), and 38 (canSendMessagesToPeer) migrated adjacent Peer-typed APIs to `EnginePeer`. `makePeerInfoController` is the largest remaining Peer-typed-API surface on `AccountContext` and a natural follow-up.
|
||||
|
||||
Scope: only `makePeerInfoController` this wave. The sibling methods `makeChatQrCodeScreen` (4 consumer sites) and `makeChatRecentActionsController` (3 consumer sites) are deferred to a trivial follow-up wave.
|
||||
|
||||
## Signature change
|
||||
|
||||
`AccountContext` protocol declaration (`submodules/AccountContext/Sources/AccountContext.swift:1371`) and its `SharedAccountContextImpl` implementation (`submodules/TelegramUI/Sources/SharedAccountContext.swift:1937`):
|
||||
|
||||
```swift
|
||||
// before
|
||||
func makePeerInfoController(
|
||||
context: AccountContext,
|
||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
|
||||
peer: Peer,
|
||||
mode: PeerInfoControllerMode,
|
||||
avatarInitiallyExpanded: Bool,
|
||||
fromChat: Bool,
|
||||
requestsContext: PeerInvitationImportersContext?
|
||||
) -> ViewController?
|
||||
|
||||
// after
|
||||
func makePeerInfoController(
|
||||
context: AccountContext,
|
||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
|
||||
peer: EnginePeer,
|
||||
mode: PeerInfoControllerMode,
|
||||
avatarInitiallyExpanded: Bool,
|
||||
fromChat: Bool,
|
||||
requestsContext: PeerInvitationImportersContext?
|
||||
) -> ViewController?
|
||||
```
|
||||
|
||||
Implementation body adds `let peer = peer._asPeer()` shadow at body-top. `peerInfoControllerImpl` (private, same file) and all downstream Peer-typed helpers keep raw `Peer` — out of scope for this wave.
|
||||
|
||||
```swift
|
||||
public func makePeerInfoController(... peer: EnginePeer ...) -> ViewController? {
|
||||
let peer = peer._asPeer()
|
||||
let controller = peerInfoControllerImpl(context: context, updatedPresentationData: updatedPresentationData, peer: peer, mode: mode, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: fromChat)
|
||||
controller?.navigationPresentation = .modalInLargeLayout
|
||||
return controller
|
||||
}
|
||||
```
|
||||
|
||||
## Consumer-side changes
|
||||
|
||||
**73 total consumer call sites** (75 raw occurrences minus 1 protocol declaration and 1 implementation). Classification (confirmed via full-repo grep):
|
||||
|
||||
- **58 Shape-A** — inline `peer: x._asPeer()` drops to `peer: x`. Mechanical edits.
|
||||
- **3 Shape-A-variant** — `SettingsSearchableItems.swift` lines 1023, 1049, 1083. The upstream `guard let peer = peer?._asPeer() else` changes to `guard let peer = peer else`, making the local `peer` stay `EnginePeer`. The call-site line does not change.
|
||||
- **12 Shape-C** — raw Peer local, add `EnginePeer(...)` wrap at call site.
|
||||
|
||||
### Shape-C site list
|
||||
|
||||
| File | Line | Current peer argument | New |
|
||||
|---|---|---|---|
|
||||
| `submodules/SettingsUI/Sources/Privacy and Security/BlockedPeersController.swift` | 270 | `peer: peer` | `peer: EnginePeer(peer)` |
|
||||
| `submodules/PeerInfoUI/Sources/ChannelMembersController.swift` | 707 | `peer: participant.peer` | `peer: EnginePeer(participant.peer)` |
|
||||
| `submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift` | 381 | `peer: participant.peer` | `peer: EnginePeer(participant.peer)` |
|
||||
| `submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift` | 1011 | `peer: peer` | `peer: EnginePeer(peer)` |
|
||||
| `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift` | 4306 | `peer: peer` | `peer: EnginePeer(peer)` |
|
||||
| `submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift` | 441 | `peer: peer` | `peer: EnginePeer(peer)` |
|
||||
| `submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift` | 461 | `peer: peer` | `peer: EnginePeer(peer)` |
|
||||
| `submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift` | 471 | `peer: peer` | `peer: EnginePeer(peer)` |
|
||||
| `submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift` | 492 | `peer: channel` | `peer: EnginePeer(channel)` |
|
||||
| `submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift` | 218 | `peer: peer` | `peer: EnginePeer(peer)` |
|
||||
| `submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift` | 359 | `peer: peer` | `peer: EnginePeer(peer)` |
|
||||
| `submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift` | 4362 | `peer: peer` | `peer: EnginePeer(peer)` |
|
||||
|
||||
Each Shape-C wrap is a future-wave drop candidate once the raw-Peer source (stored field, `participant.peer`, `renderedPeer.chatMainPeer`, etc.) migrates upstream.
|
||||
|
||||
### Shape-A-variant detail
|
||||
|
||||
`SettingsSearchableItems.swift` three sites share the same structure:
|
||||
|
||||
```swift
|
||||
// before
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { peer in // peer: EnginePeer?
|
||||
guard let peer = peer?._asPeer() else { // peer: Peer (shadowed)
|
||||
return
|
||||
}
|
||||
let controller = context.sharedContext.makePeerInfoController(
|
||||
context: context,
|
||||
updatedPresentationData: nil,
|
||||
peer: peer,
|
||||
mode: .myProfile,
|
||||
...
|
||||
)
|
||||
...
|
||||
})
|
||||
|
||||
// after
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { peer in // peer: EnginePeer?
|
||||
guard let peer = peer else { // peer: EnginePeer (shadowed)
|
||||
return
|
||||
}
|
||||
let controller = context.sharedContext.makePeerInfoController(
|
||||
context: context,
|
||||
updatedPresentationData: nil,
|
||||
peer: peer,
|
||||
mode: .myProfile,
|
||||
...
|
||||
)
|
||||
...
|
||||
})
|
||||
```
|
||||
|
||||
## Files touched (≈50)
|
||||
|
||||
Inventoried from the grep output. Not exhaustive here; per-site enumeration lives in the implementation plan.
|
||||
|
||||
Signature files: `AccountContext/Sources/AccountContext.swift`, `TelegramUI/Sources/SharedAccountContext.swift`.
|
||||
|
||||
Shape-A consumer files (sample, not exhaustive): `SelectivePrivacySettingsPeersController.swift`, `InstantPageControllerNode.swift`, `CallListController.swift`, `ContactsController.swift`, `ContactContextMenus.swift`, `SecureIdAuthController.swift`, `ChannelAdminController.swift`, `ChannelMembersController.swift`, `ChannelBannedMemberController.swift`, `ChannelPermissionsController.swift`, `MessageStatsController.swift`, `GroupStatsController.swift`, `InviteRequestsController.swift`, `BrowserInstantPageContent.swift`, `WebAppController.swift`, `PeersNearbyController.swift`, `ChatSendStarsScreen.swift`, `ChatRecentActionsControllerNode.swift`, `MiniAppListScreen.swift`, `JoinSubjectScreen.swift`, `NewContactScreen.swift`, `StarsTransactionScreen.swift`, `StoryItemSetContainerViewSendMessage.swift`, `StoryItemSetContainerComponent.swift`, `GiftViewScreen.swift`, `GiftOptionsScreen.swift`, `StorageUsageScreen.swift`, `TextProcessingScreen.swift`, `PeerInfoScreen.swift`, `PeerInfoScreenOpenURL.swift`, `JoinAffiliateProgramScreen.swift`, `ChatControllerScrollToPointInHistory.swift`, `OpenUrl.swift`, `OpenResolvedUrl.swift`, `TextLinkHandling.swift`, `ChatController.swift`, `OpenAddContact.swift`, `ChatManagingBotTitlePanelNode.swift`, `NavigateToChatController.swift`, `SharedAccountContext.swift` (3 self-call sites), `OverlayAudioPlayerControllerNode.swift`, `PollResultsController.swift`, `ChatControllerOpenWebApp.swift`, `ChatControllerNavigationButtonAction.swift`, `ChatListController.swift`, `ChatListSearchListPaneNode.swift`.
|
||||
|
||||
Shape-A-variant file: `SettingsSearchableItems.swift`.
|
||||
|
||||
Shape-C-only files (other than those with mixed shapes above): `BlockedPeersController.swift`, `ChannelBlacklistController.swift`, `ChatControllerOpenPeer.swift`, `ChatControllerLoadDisplayNode.swift`.
|
||||
|
||||
## Build/verification plan
|
||||
|
||||
1. Apply all edits atomically. Mechanical Edit-tool string replaces for the 58 Shape-A drops; focused Edits for the 3 Shape-A-variants (guard line) and 12 Shape-C wraps.
|
||||
2. Full project 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`.
|
||||
3. Fix any iteration-surfaced errors. Budget 2–4 iterations.
|
||||
4. Clean build → atomic commit with wave-39 message.
|
||||
5. Update `project_postbox_refactor_next_wave.md` memory, `docs/superpowers/postbox-refactor-log.md`, and `CLAUDE.md` wave tally.
|
||||
6. No test runs (project has no unit tests).
|
||||
|
||||
## Risks / watch-out
|
||||
|
||||
- **Destructure/binding cascades.** Locals named `peer` declared as `Peer` somewhere in a call chain and fed to `makePeerInfoController`. The body-shadow pattern contains divergence at the public API boundary, but transient Swift inference errors may surface at intermediate points.
|
||||
- **`chatMainPeer` / `renderedPeer.peer` property types.** Shape-C sites at `ChatControllerNavigationButtonAction.swift:441/461/471/492` and `ChatControllerLoadDisplayNode.swift:4362` assume these properties return raw `Peer`. If they already return `EnginePeer` in the current repo (unlikely but possible after earlier waves), the wrap should be `peer: peer` with no wrap. Verify in plan phase.
|
||||
- **Outflow sites in Shape-C files.** Some Shape-C files may have additional `peer: Peer` flows elsewhere that are unrelated to this wave. Do not chase — only touch the listed sites.
|
||||
|
||||
## Abandonment criteria
|
||||
|
||||
- Iteration count exceeds 5.
|
||||
- A cascade requires editing `peerInfoControllerImpl` (violates body-shadow boundary).
|
||||
- Any non-consumer file (e.g., anything in `TelegramCore`, `Postbox`, `TelegramApi`) surfaces an error.
|
||||
|
||||
## Net effect
|
||||
|
||||
- Public API: `AccountContext.makePeerInfoController` takes `EnginePeer` instead of raw `Peer`.
|
||||
- Bridges: -58 inline `_asPeer()` + -3 upstream-guard `_asPeer()` + 12 new `EnginePeer(...)` wraps = **net -49 bridges**.
|
||||
- Ratchet: 12 Shape-C wraps become future-wave drop candidates (e.g., `RenderedPeer → EngineRenderedPeer` migration, participant-object migrations).
|
||||
|
||||
## Out of scope
|
||||
|
||||
- `makeChatQrCodeScreen` (4 sites), `makeChatRecentActionsController` (3 sites) — deferred to a trivial follow-up wave.
|
||||
- `peerInfoControllerImpl` and downstream Peer-typed helpers.
|
||||
- Shape-C source migrations (participant objects, `renderedPeer.chatMainPeer`, etc.).
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
# Wave 44 — `RenderedChannelParticipant.peers: [PeerId: Peer] → [EnginePeer.Id: EnginePeer]`
|
||||
|
||||
**Date:** 2026-04-24
|
||||
**Status:** Approved, pending plan
|
||||
**Predecessor:** Wave 41 (commit `32573c9808`) migrated `RenderedChannelParticipant.peer` from `Peer` to `EnginePeer` and installed ADD-WRAP markers at consumer-side read sites that this wave drops.
|
||||
**Goal:** Close out the wave-41 ratchet by migrating the sibling `peers: [PeerId: Peer]` field to `[EnginePeer.Id: EnginePeer]`. After this wave, `RenderedChannelParticipant` has no raw `Peer` types in its public surface.
|
||||
|
||||
## Context
|
||||
|
||||
`RenderedChannelParticipant` is declared in `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift`:
|
||||
|
||||
```swift
|
||||
public struct RenderedChannelParticipant: Equatable {
|
||||
public let participant: ChannelParticipant
|
||||
public let peer: EnginePeer // migrated in wave 41
|
||||
public let peers: [PeerId: Peer] // target of this wave
|
||||
public let presences: [PeerId: PeerPresence] // out of scope (PeerPresence is Postbox protocol)
|
||||
|
||||
public init(participant: ChannelParticipant, peer: EnginePeer, peers: [PeerId: Peer] = [:], presences: [PeerId: PeerPresence] = [:]) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
`peers` is a supplementary dict of "referenced peers" (e.g. the admin who promoted this member, the admin who banned them). Consumers use it to render relationships — never with `as?`/`is` casts, only `.id` and `.displayTitle(...)` on extracted values.
|
||||
|
||||
## Migration target
|
||||
|
||||
- `peers: [PeerId: Peer]` → `peers: [EnginePeer.Id: EnginePeer]`
|
||||
- init default: `[:]` on both sides (type changes transparently)
|
||||
- `presences` field stays unchanged.
|
||||
|
||||
## Scope
|
||||
|
||||
### Declaration (1 file, 2 edits)
|
||||
|
||||
**`submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift`**
|
||||
|
||||
- Line 11: `public let peers: [PeerId: Peer]` → `public let peers: [EnginePeer.Id: EnginePeer]`
|
||||
- Line 14: `peers: [PeerId: Peer] = [:]` → `peers: [EnginePeer.Id: EnginePeer] = [:]`
|
||||
|
||||
### TelegramCore producer sites (8 files, 8 construction sites, ~16 edits)
|
||||
|
||||
All 8 producers follow the identical pattern of building a local `peers: [PeerId: Peer] = [:]` dict inside a `postbox.transaction` and passing it to an `RCP(peers: peers, ...)` constructor. Per-site edits: change local dict type, wrap each insertion value with `EnginePeer(...)`.
|
||||
|
||||
| File | `var peers:` decl | `peers[X.id] = X` insertions | RCP construction |
|
||||
|---|---|---|---|
|
||||
| `TelegramEngine/Messages/RequestStartBot.swift` | line 61 | line 64 | line 65 |
|
||||
| `TelegramEngine/Peers/ChannelOwnershipTransfer.swift` | line 170 | lines 172, 176 | line 180 (2 RCP constructions share `peers`) |
|
||||
| `TelegramEngine/Peers/JoinChannel.swift` | line 59 | lines 64, 77 | line 82 |
|
||||
| `TelegramEngine/Peers/AddPeerMember.swift` | line 242 | lines 244, 251 | line 255 |
|
||||
| `TelegramEngine/Peers/PeerAdmins.swift` | line 251 | lines 253, 259 | line 262 |
|
||||
| `TelegramEngine/Peers/ChannelBlacklist.swift` | line 128 | lines 130, 136 | line 140 |
|
||||
| `TelegramEngine/Peers/Ranks.swift` | line 60 | lines 62, 68 | line 95 |
|
||||
| `TelegramEngine/Peers/ChannelMembers.swift` | line 102 | line 105 | line 115 |
|
||||
|
||||
**Per-site rewrite:**
|
||||
```swift
|
||||
// before
|
||||
var peers: [PeerId: Peer] = [:]
|
||||
peers[peer.id] = peer
|
||||
|
||||
// after
|
||||
var peers: [EnginePeer.Id: EnginePeer] = [:]
|
||||
peers[peer.id] = EnginePeer(peer)
|
||||
```
|
||||
|
||||
### Consumer-side DROPs: `.mapValues({ $0._asPeer() })` transforms (5 sites)
|
||||
|
||||
These consumer-side constructors start from a `[PeerId: EnginePeer]` source dict and currently unwrap to `[PeerId: Peer]` to feed into the RCP constructor. After migration, the unwrap transform is a no-op and can be dropped.
|
||||
|
||||
| File | Line | Before → after |
|
||||
|---|---|---|
|
||||
| `PeerInfoUI/Sources/ChannelAdminsController.swift` | 926 | `peers: peers.mapValues({ $0._asPeer() })` → `peers: peers` |
|
||||
| `PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift` | 994 | same |
|
||||
| `PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift` | 998 | same |
|
||||
| `PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift` | 409 | same |
|
||||
| `PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift` | 413 | same |
|
||||
|
||||
**Verification required at plan time:** for each of these 5 sites, grep back up in the enclosing function to confirm the local `peers` variable is declared `[PeerId: EnginePeer]` (the source of the mapValues transform). If any of the sources turn out to be `[PeerId: Peer]` rather than `[PeerId: EnginePeer]`, that site's transform is NOT a no-op and instead becomes a wrap (`.mapValues(EnginePeer.init)`) — still a net-zero or gain depending on where the source originates.
|
||||
|
||||
### Consumer-side DROPs: `EnginePeer(peer).displayTitle(...)` wraps (6 sites)
|
||||
|
||||
These are the wave-41 ADD-WRAP markers. Pattern: extract `peer` from `participant.peers[X]`, wrap with `EnginePeer(peer)` to call `.displayTitle(...)`. After migration, `peer` is already `EnginePeer` — drop the wrap.
|
||||
|
||||
| File | Line | Pattern |
|
||||
|---|---|---|
|
||||
| `PeerInfoUI/Sources/ChannelAdminsController.swift` | 297 | `EnginePeer(peer).displayTitle(strings: strings, ...)` → `peer.displayTitle(strings: strings, ...)` |
|
||||
| `PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift` | 839 | same |
|
||||
| `PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift` | 870 | same |
|
||||
| `PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift` | 1091 | same |
|
||||
| `PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift` | 1122 | same |
|
||||
| `PeerInfoUI/Sources/ChannelBlacklistController.swift` | 165 | same |
|
||||
|
||||
The adjacent `peer.id == participant.peer.id` comparisons are unchanged: both sides are `EnginePeer.Id` (already a typealias of `PeerId`).
|
||||
|
||||
### Consumer-side ADD-UNWRAP (1 site)
|
||||
|
||||
**`submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift`**, lines 672–674:
|
||||
|
||||
```swift
|
||||
for (_, peer) in participant.peers {
|
||||
peers[peer.id] = peer // `peers` is SimpleDictionary<PeerId, Peer>
|
||||
}
|
||||
```
|
||||
|
||||
After migration `peer` is `EnginePeer`; the outer `peers` SimpleDictionary is still `[PeerId: Peer]`. Rewrite:
|
||||
|
||||
```swift
|
||||
for (_, peer) in participant.peers {
|
||||
peers[peer.id] = peer._asPeer()
|
||||
}
|
||||
```
|
||||
|
||||
### Constructor sites with no `peers:` arg — no change (12 sites)
|
||||
|
||||
Default value's *type* changes (`[PeerId: Peer] = [:]` → `[EnginePeer.Id: EnginePeer] = [:]`) but the literal `[:]` works for either. These sites compile unchanged:
|
||||
|
||||
- TelegramCore: `ChannelAdminEventLogs.swift:271, 279` (x2), `:287` (x2), `:483` (x2) — 7 constructions
|
||||
- `PeerInfoUI/.../ChannelAdminsController.swift:921`
|
||||
- `PeerInfoUI/.../ChannelMembersSearchContainerNode.swift:987`
|
||||
- `PeerInfoUI/.../ChannelMembersSearchControllerNode.swift:404`
|
||||
- `TelegramUI/.../ChatRecentActionsController/.../ChatRecentActionsFilterController.swift:445`
|
||||
- `TelegramUI/.../ChatControllerAdminBanUsers.swift:224, :370, :755` (3 constructions)
|
||||
- `TelegramUI/.../StoryContainerScreen/.../StoryContentLiveChatComponent.swift:361`
|
||||
|
||||
## Net impact
|
||||
|
||||
**Consumer-surface bridges:** −6 wraps + −5 unwrap transforms + +1 unwrap = **−10 bridges**.
|
||||
|
||||
**TelegramCore-internal bridges:** +~12 wraps (`EnginePeer(peer)` at producer insertion points, inside `import Postbox` modules). These do not regress Postbox-hygiene since every producer file already imports Postbox.
|
||||
|
||||
**Structural:** `RenderedChannelParticipant` public surface contains no raw `Peer` types after this wave (only `ChannelParticipant`, `EnginePeer`, `[EnginePeer.Id: EnginePeer]`, `[PeerId: PeerPresence]`). `presences` still leaks `PeerPresence` — separate future migration.
|
||||
|
||||
## Iteration budget
|
||||
|
||||
**2–3 iterations** (wave-41 foundational-type lesson: field migrations on passed-around structs budget 2–4 iterations, not first-pass-clean).
|
||||
|
||||
Verified absence of hidden grep surface:
|
||||
- No `as?` / `is TelegramX` casts on `participant.peers[X]` extractions (grepped).
|
||||
- No Peer-only properties accessed on extractions (uses `.id` and `.displayTitle(...)` only — both EnginePeer-forwarded).
|
||||
- All 8 TelegramCore producers build locally (verified) — no chain-migration.
|
||||
|
||||
## Risks
|
||||
|
||||
1. **Producer local-dict migration under `continueOnError`.** If a producer builds the dict with more than two insertions and misses one, the build flags mismatched dict-value types. Low blast radius (per-file local).
|
||||
2. **Hidden consumer site.** If a grep miss surfaces a `participant.peers` site not enumerated here, the wrap/unwrap balance changes. Mitigation: plan document must re-run the narrow grep (`participant\.peers|rcp\.peers|renderedParticipant\.peers`) at plan-write time and iteration-0 time.
|
||||
3. **mapValues source-dict check.** If any of the 5 consumer-side `.mapValues({ $0._asPeer() })` sites has a source `[PeerId: Peer]` (not `[PeerId: EnginePeer]`), the migration at that site inverts (becomes a wrap instead of a drop). Plan-time per-site verification required.
|
||||
4. **SimpleDictionary import.** The one ADD-UNWRAP site in `ChatRecentActionsHistoryTransition.swift` already uses `SimpleDictionary<PeerId, Peer>` — no new Postbox exposure.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- `RenderedChannelParticipant.presences: [PeerId: PeerPresence]` — `PeerPresence` is a Postbox protocol; separate migration with different shape.
|
||||
- `RenderedPeer → EngineRenderedPeer` foundational-type migration (listed in wave-44 memo as candidate 6; save for a dedicated session).
|
||||
- `PeerInfoHeader*` bundle (wave-44 memo candidate 1) — considered but not selected for wave 44; candidate for wave 45.
|
||||
|
||||
## Success criteria
|
||||
|
||||
1. `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift` has `peers: [EnginePeer.Id: EnginePeer]` declaration.
|
||||
2. All 8 TelegramCore producers compile with wrapped inserts.
|
||||
3. All 5 consumer `.mapValues({ $0._asPeer() })` transforms are removed.
|
||||
4. All 6 consumer `EnginePeer(peer).displayTitle(...)` wraps on extracted dict values are removed (`peer.displayTitle(...)`).
|
||||
5. `ChatRecentActionsHistoryTransition.swift:673` uses `peer._asPeer()` for the SimpleDictionary insertion value.
|
||||
6. Full `Telegram/Telegram` build (`configuration=debug_sim_arm64`) is clean — **one** atomic commit.
|
||||
7. Grep post-migration: `participant\.peers\[` returns only engine-typed call sites; no residual `EnginePeer(peer)` on `.peers[...]` extractions.
|
||||
|
||||
## Commit message template
|
||||
|
||||
```
|
||||
Postbox -> TelegramEngine wave 44
|
||||
|
||||
Migrate RenderedChannelParticipant.peers from [PeerId: Peer] to
|
||||
[EnginePeer.Id: EnginePeer]. Closes the wave-41 ratchet — the public
|
||||
struct no longer leaks raw Peer types in any field (presences stays
|
||||
Postbox-typed; separate migration).
|
||||
|
||||
Consumer-surface: -10 bridges (6 EnginePeer(peer) wraps dropped at
|
||||
read sites, 5 .mapValues({ $0._asPeer() }) transforms dropped at
|
||||
constructor sites, 1 ._asPeer() added at
|
||||
ChatRecentActionsHistoryTransition.swift:673 where the value is
|
||||
inserted into a raw-Peer SimpleDictionary).
|
||||
|
||||
TelegramCore producers: 8 files, each builds a local
|
||||
[EnginePeer.Id: EnginePeer] dict from transaction.getPeer() wrapping
|
||||
at the insertion point.
|
||||
|
||||
No unit tests in this project; full Telegram/Telegram build verified
|
||||
under configuration=debug_sim_arm64.
|
||||
```
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
# Wave 41 — `RenderedChannelParticipant.peer: Peer → EnginePeer` migration — Design
|
||||
|
||||
**Date:** 2026-04-24
|
||||
**Wave:** 41
|
||||
**Status:** spec
|
||||
|
||||
## Goal
|
||||
|
||||
Migrate the `peer` field of `TelegramCore.RenderedChannelParticipant` from the Postbox protocol `Peer` to the TelegramCore enum `EnginePeer`. All construction sites and consumer accesses are updated in one atomic commit.
|
||||
|
||||
## Motivation
|
||||
|
||||
- Drops 2 Shape-C `EnginePeer(participant.peer)` wraps installed by wave 39 (`ChannelMembersController.swift:707`, `ChannelBlacklistController.swift:381`).
|
||||
- Drops ~37 additional `EnginePeer(...)` / `._asPeer()` bridges across the consumer surface (total ~39 bridge drops after counting `EnginePeer(peer.peer).compactDisplayTitle` sites in `AdminUserActionsSheet.swift`).
|
||||
- Aligns `RenderedChannelParticipant.peer` with the pattern established for `FoundPeer.peer` (wave 34), `SendAsPeer.peer` (wave 35), `ContactListPeer.peer` (wave 36), and all `AccountContext.makeX(peer: ...)` facades (waves 37–40).
|
||||
- Ratchet candidate for future waves: once `.peer` is `EnginePeer`, the `peers: [PeerId: Peer]` dict field becomes the only Postbox-typed field on the struct — a follow-up wave can migrate `peers: [EnginePeer.Id: EnginePeer]` in isolation.
|
||||
|
||||
## Scope
|
||||
|
||||
### In scope
|
||||
|
||||
**TelegramCore:**
|
||||
- `submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift` — change struct field + init param + Equatable impl
|
||||
- 9 TelegramCore files containing 16 construction sites where `RenderedChannelParticipant(... peer: peer, ...)` is called with a raw `Peer` from `transaction.getPeer()` — wrap with `EnginePeer(peer)`:
|
||||
- `Messages/RequestStartBot.swift:65`
|
||||
- `Peers/AddPeerMember.swift:255`
|
||||
- `Peers/ChannelAdminEventLogs.swift:271, 279, 287, 483` (7 constructor calls total)
|
||||
- `Peers/ChannelBlacklist.swift:140`
|
||||
- `Peers/ChannelMembers.swift:115`
|
||||
- `Peers/ChannelOwnershipTransfer.swift:180` (2 constructor calls)
|
||||
- `Peers/JoinChannel.swift:82`
|
||||
- `Peers/PeerAdmins.swift:262`
|
||||
- `Peers/Ranks.swift:95`
|
||||
|
||||
**Consumer (17 files):** all sites accessing `participant.peer` or constructing `RenderedChannelParticipant`:
|
||||
- `submodules/PeerInfoUI/Sources/ChannelAdminsController.swift`
|
||||
- `submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift`
|
||||
- `submodules/PeerInfoUI/Sources/ChannelMembersController.swift`
|
||||
- `submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift`
|
||||
- `submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift`
|
||||
- `submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift`
|
||||
- `submodules/SearchPeerMembers/Sources/SearchPeerMembers.swift`
|
||||
- `submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift`
|
||||
- `submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift`
|
||||
- `submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsFilterController.swift`
|
||||
- `submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift`
|
||||
- `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoMembers.swift`
|
||||
- `submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift`
|
||||
- `submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift`
|
||||
- `submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift`
|
||||
- `submodules/TemporaryCachedPeerDataManager/Sources/ChannelMemberCategoryListContext.swift`
|
||||
- `submodules/TemporaryCachedPeerDataManager/Sources/PeerChannelMemberCategoriesContextsManager.swift`
|
||||
|
||||
### Out of scope (deferred)
|
||||
|
||||
- `RenderedChannelParticipant.peers: [PeerId: Peer]` — still `[PeerId: Peer]` dict. Not migrated this wave.
|
||||
- `RenderedChannelParticipant.presences: [PeerId: PeerPresence]` — still `[PeerId: PeerPresence]` dict. Not migrated this wave.
|
||||
- `PeerInfoScreenData.peer → EnginePeer` — future wave 42 candidate (drops 2 wave-40 wraps).
|
||||
- `RenderedPeer → EngineRenderedPeer` — future major wave; saved for a dedicated session.
|
||||
- `PeerInfoMember.peer: Peer` enum accessor in `PeerInfoMembers.swift:30-39` — retained as `Peer` for this wave (contained by a single `._asPeer()` inside the `.channelMember` branch). Migration of this accessor is a separate follow-up.
|
||||
|
||||
## Design
|
||||
|
||||
### Struct change
|
||||
|
||||
```swift
|
||||
// submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift
|
||||
|
||||
public struct RenderedChannelParticipant: Equatable {
|
||||
public let participant: ChannelParticipant
|
||||
public let peer: EnginePeer // ← was: Peer
|
||||
public let peers: [PeerId: Peer] // unchanged
|
||||
public let presences: [PeerId: PeerPresence] // unchanged
|
||||
|
||||
public init(participant: ChannelParticipant, peer: EnginePeer, peers: [PeerId: Peer] = [:], presences: [PeerId: PeerPresence] = [:]) {
|
||||
self.participant = participant
|
||||
self.peer = peer
|
||||
self.peers = peers
|
||||
self.presences = presences
|
||||
}
|
||||
|
||||
public static func ==(lhs: RenderedChannelParticipant, rhs: RenderedChannelParticipant) -> Bool {
|
||||
return lhs.participant == rhs.participant && lhs.peer == rhs.peer // ← was: lhs.peer.isEqual(rhs.peer)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`EnginePeer` is `Equatable` by enum synthesis (verified — each associated value type is Equatable: `TelegramUser`, `TelegramGroup`, `TelegramChannel`, `TelegramSecretChat`). `==` becomes cleaner.
|
||||
|
||||
### Consumer-site shapes
|
||||
|
||||
Per the pre-flight classification, sites fall into these shapes:
|
||||
|
||||
- **ZERO** (transparent) — `.id`, `.isDeleted`, `.indexName`, `.addressName`, `.compactDisplayTitle`, `.displayTitle(strings:displayOrder:)`, `.displayLetters`, `.debugDisplayTitle`, etc. All exposed on `EnginePeer`. ~160 sites. **No edit.**
|
||||
|
||||
- **DROP** — `EnginePeer(participant.peer)` → `participant.peer`. ~32 consumer sites + 2 `.peer._asPeer()` downgrades that also drop. The biggest class of edits. Key sites:
|
||||
- `ChannelAdminsController.swift:326, 921, 926` (921, 926 drop `._asPeer()` from constructor)
|
||||
- `ChannelBlacklistController.swift:170, 381`
|
||||
- `ChannelMembersController.swift:334, 707`
|
||||
- `ChannelMembersSearchContainerNode.swift:212 (×2), 223`
|
||||
- `ChannelMembersSearchControllerNode.swift:148`
|
||||
- `ChannelPermissionsController.swift:480, 483`
|
||||
- `SearchPeerMembers.swift:30, 36, 61, 76`
|
||||
- `ChatRecentActionsController.swift:359`
|
||||
- `ChatRecentActionsFilterController.swift:217`
|
||||
- `ChatRecentActionsHistoryTransition.swift:719, 730, 740, 828, 842, 870, 943, 955, 973, 990, 1026` (one `EnginePeer(new.peer)` drop per site)
|
||||
- `ShareWithPeersScreenState.swift:558, 576`
|
||||
- `AdminUserActionsSheet.swift:284, 404, 416, 417, 522, 523` (EnginePeer(peer.peer) wraps)
|
||||
- `StoryContentLiveChatComponent.swift:370` (drops `._asPeer()`)
|
||||
- `ChatControllerAdminBanUsers.swift:372, 757` (drops `._asPeer()`)
|
||||
|
||||
- **CAST** — `if let user = participant.peer as? TelegramUser, user.botInfo != nil` → `if case let .user(user) = participant.peer, user.botInfo != nil`. 9 sites across 4 files:
|
||||
- `ChannelMembersController.swift:305`
|
||||
- `ChannelMembersSearchContainerNode.swift:752, 884, 1052, 1136`
|
||||
- `ChannelMembersSearchControllerNode.swift:516, 558`
|
||||
- `ShareWithPeersScreenState.swift:566`
|
||||
|
||||
All 9 follow the identical 2-clause pattern (`as? TelegramUser`, `user.botInfo != nil`). Pattern-match rewrite is mechanically safe.
|
||||
|
||||
- **ADD-ASPEER** — site needs raw `Peer`. 3 sites:
|
||||
- `ChatRecentActionsHistoryTransition.swift:675` — `peers[participant.peer.id] = participant.peer` → `peers[participant.peer.id] = participant.peer._asPeer()` (assigning into `SimpleDictionary<PeerId, Peer>`).
|
||||
- `ChatRecentActionsHistoryTransition.swift:2275` — same pattern.
|
||||
- `PeerInfoMembers.swift:33` — `return participant.peer` → `return participant.peer._asPeer()` (outer enum accessor returns `Peer`; deliberately contained — migration of `PeerInfoMember.peer` deferred).
|
||||
|
||||
- **ADD-WRAP** — consumer construction site where the local is raw `Peer` but the field is now `EnginePeer`. 7 sites across 3 files:
|
||||
- `ChannelMembersSearchContainerNode.swift:987, 994, 998` — `peer: peer` where `peer = peerView.peers[participant.peerId]` is raw `Peer`. → `peer: EnginePeer(peer)`.
|
||||
- `ChannelMembersSearchControllerNode.swift:404, 409, 413` — same pattern.
|
||||
- `ChatRecentActionsFilterController.swift:445` — `peer: user` where `user: TelegramUser` (from `case let .user(user) = peer`). → `peer: .user(user)` or `peer: EnginePeer(user)`. Use `peer: .user(user)` (direct enum case) for clarity.
|
||||
- `ChatControllerAdminBanUsers.swift:226` — `peer: peer` where `peer = author: Peer`. → `peer: EnginePeer(peer)`.
|
||||
|
||||
### TelegramCore-internal constructor sites
|
||||
|
||||
All 16 sites receive a raw `Peer` (from `transaction.getPeer()` / `peers[id]`) and pass it as `peer:`. All become `peer: EnginePeer(peer)`:
|
||||
|
||||
```swift
|
||||
// Before:
|
||||
RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presences: presences)
|
||||
// After:
|
||||
RenderedChannelParticipant(participant: participant, peer: EnginePeer(peer), peers: peers, presences: presences)
|
||||
```
|
||||
|
||||
No shape-selection judgment required — all 16 sites follow this exact template. The `peers` and `presences` dictionaries are unchanged.
|
||||
|
||||
## Risks
|
||||
|
||||
- **R1: CAST semantic preservation.** The 9 `as? TelegramUser` sites all gate on `user.botInfo != nil`. Pattern-match rewrite is `if case let .user(user) = participant.peer, user.botInfo != nil`. Verified: `EnginePeer.user(TelegramUser)` gives access to the same `TelegramUser` instance; `.botInfo` is a `TelegramUser` property. Semantically equivalent.
|
||||
|
||||
- **R2: `==` implementation change.** The struct's `==` goes from `lhs.peer.isEqual(rhs.peer)` (protocol dispatch) to `lhs.peer == rhs.peer` (synthesized). `EnginePeer.==` uses Swift-synthesized enum equality: each case compares associated values. Each associated-value type (`TelegramUser`, `TelegramGroup`, `TelegramChannel`, `TelegramSecretChat`) is `Equatable` via its own `==` implementation. Semantically equivalent to the protocol `isEqual`.
|
||||
|
||||
- **R3: PeerInfoMembers.swift:33 cascade.** `PeerInfoMember.peer: Peer` enum accessor at line 30-39 returns `participant.peer` on the `.channelMember` branch. Fix is a single `._asPeer()`. The outer enum's API stays unchanged — no cascade beyond this file. Future wave can migrate `PeerInfoMember.peer` to `EnginePeer`.
|
||||
|
||||
- **R4: Consumer-side constructor sites in ChannelMembersSearch*Node.** 3 sites each in the `Container` and `Controller` node files construct `RenderedChannelParticipant` for the legacy-group search path. The `peer` local is raw `Peer` from `peerView.peers`. Mechanical wrap with `EnginePeer(peer)` at the `peer:` argument.
|
||||
|
||||
- **R5: `participant.peers` dict staying `[PeerId: Peer]`.** Current code uses `peers.mapValues({ $0._asPeer() })` at construction sites where the local dict is `[EnginePeer.Id: EnginePeer]`. This pattern is unchanged by the wave — the `peers` field is not being migrated.
|
||||
|
||||
- **R6: Hidden consumer sites.** Pre-flight searched: `RenderedChannelParticipant(` constructors across `submodules/`, `participant.peer` access (subagent classification), all files that import TelegramCore/Postbox and reference `RenderedChannelParticipant`. 17 consumer files + 10 TelegramCore files confirmed. Risk of overlooked third-party or sparse consumer: low.
|
||||
|
||||
- **R7: Pre-existing WIP contamination.** `git status` shows unrelated WIP: `submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift`, `build-system/bazel-rules/sourcekit-bazel-bsp` submodule marker, several untracked dirs. Wave-39 lesson: enumerate files explicitly in `git add`; run `git status --short` after staging.
|
||||
|
||||
## Verification
|
||||
|
||||
- Single full Bazel build with `--continueOnError` after all edits (extends wave-39 / wave-40 pattern).
|
||||
- Expected outcome: **first-pass-clean build** based on wave-39 precedent — 52 files / 73 sites / non-propagating signature migration → first-pass-clean. This wave is comparable scale (27 files / ~200+ sites including ZEROs) with even cleaner mechanics: ZERO sites are literally no edit; DROP/CAST/ADD-WRAP/ADD-ASPEER patterns are all mechanical; no inference-dependent return types.
|
||||
- Budget: 3–5 iterations if classification is wrong; first-pass-clean if classification is exact.
|
||||
|
||||
## Net ratchet economics
|
||||
|
||||
- Bridges dropped: ~37–39 (32 consumer DROPs + 2 `._asPeer()` drops in ChannelAdminsController + ~6 `EnginePeer(peer.peer).X` drops in AdminUserActionsSheet, possibly double-counted; final net post-commit grep will settle the number).
|
||||
- Bridges added: ~23 (16 TelegramCore `EnginePeer(peer)` wraps at constructor call sites + 4 ADD-WRAP consumer constructors + 3 ADD-ASPEER).
|
||||
- **Net:** ~−14 to −16 bridges. Positive economics even counting TelegramCore-internal adds.
|
||||
- Ratchet marker: the 4 consumer ADD-WRAP constructor sites (`ChannelMembersSearch*Node` + `ChatControllerAdminBanUsers:226`) are candidates for drop in a future wave that migrates the `peerView.peers[id]` / `authors: [Peer]` upstream flows to EnginePeer.
|
||||
|
||||
## Out-of-scope inventory (for the next wave)
|
||||
|
||||
If a follow-up wave migrates **`RenderedChannelParticipant.peers: [PeerId: Peer] → [EnginePeer.Id: EnginePeer]`**, the ADD-WRAP sites in this wave (all `peers: peers.mapValues({ $0._asPeer() })`) simplify to `peers: peers`. That's a high-ratchet candidate wave that becomes mechanical once this wave lands.
|
||||
|
|
@ -1368,7 +1368,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||
func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, tag: HistoryViewInputTag?) -> Signal<(MessageIndex?, Bool), NoError>
|
||||
|
||||
func makeOverlayAudioPlayerController(context: AccountContext, chatLocation: ChatLocation, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation?, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController
|
||||
func makePeerInfoController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool, requestsContext: PeerInvitationImportersContext?) -> ViewController?
|
||||
func makePeerInfoController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peer: EnginePeer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool, requestsContext: PeerInvitationImportersContext?) -> ViewController?
|
||||
func makeChannelAdminController(context: AccountContext, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant) -> ViewController?
|
||||
func makeDeviceContactInfoController(context: ShareControllerAccountContext, environment: ShareControllerEnvironment, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController
|
||||
func makePeersNearbyController(context: AccountContext) -> ViewController
|
||||
|
|
@ -1398,7 +1398,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||
func makeProxySettingsController(context: AccountContext) -> ViewController
|
||||
func makeLocalizationListController(context: AccountContext) -> ViewController
|
||||
func makeCreateGroupController(context: AccountContext, peerIds: [PeerId], initialTitle: String?, mode: CreateGroupMode, completion: ((PeerId, @escaping () -> Void) -> Void)?) -> ViewController
|
||||
func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?, starsState: StarsRevenueStats?) -> ViewController
|
||||
func makeChatRecentActionsController(context: AccountContext, peer: EnginePeer, adminPeerId: PeerId?, starsState: StarsRevenueStats?) -> ViewController
|
||||
func makePrivacyAndSecurityController(context: AccountContext) -> ViewController
|
||||
func makeBioPrivacyController(context: AccountContext, settings: Promise<AccountPrivacySettings?>, present: @escaping (ViewController) -> Void)
|
||||
func makeBirthdayPrivacyController(context: AccountContext, settings: Promise<AccountPrivacySettings?>, openedFromBirthdayScreen: Bool, present: @escaping (ViewController) -> Void)
|
||||
|
|
@ -1458,7 +1458,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||
func makeInstantPageController(context: AccountContext, webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation) -> ViewController
|
||||
func openChatWallpaper(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void)
|
||||
func makeRecentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext) -> ViewController & RecentSessionsController
|
||||
func makeChatQrCodeScreen(context: AccountContext, peer: Peer, threadId: Int64?, temporary: Bool) -> ViewController
|
||||
func makeChatQrCodeScreen(context: AccountContext, peer: EnginePeer, threadId: Int64?, temporary: Bool) -> ViewController
|
||||
func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController
|
||||
func makePremiumIntroController(sharedContext: SharedAccountContext, engine: TelegramEngineUnauthorized, inAppPurchaseManager: InAppPurchaseManager, source: PremiumIntroSource, proceed: (() -> Void)?) -> ViewController
|
||||
func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, forceDark: Bool, action: @escaping () -> Void, dismissed: (() -> Void)?) -> ViewController
|
||||
|
|
|
|||
|
|
@ -1498,7 +1498,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
|||
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id))
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
if let strongSelf = self, let peer = peer {
|
||||
if let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
strongSelf.getNavigationController()?.pushViewController(controller)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -372,7 +372,7 @@ public final class CallListController: TelegramBaseController {
|
|||
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
|
||||
)
|
||||
|> deliverOnMainQueue).startStandalone(next: { peer in
|
||||
if let strongSelf = self, let peer = peer, let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .calls(messages: messages.map({ $0._asMessage() })), avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let strongSelf = self, let peer = peer, let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .calls(messages: messages.map({ $0._asMessage() })), avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1910,7 +1910,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
strongSelf.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
} else if let peer = peer.peer, peer.id == strongSelf.context.account.peerId, peerData.displayAsTopicList {
|
||||
if let peerInfoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let peerInfoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
let source: ContextContentSource
|
||||
if let location = location {
|
||||
source = .location(ChatListContextLocationContentSource(controller: strongSelf, location: location))
|
||||
|
|
@ -3306,7 +3306,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
guard let self else {
|
||||
return
|
||||
}
|
||||
guard let peer = peer, let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
|
||||
guard let peer = peer, let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
|
||||
return
|
||||
}
|
||||
(self.navigationController as? NavigationController)?.pushViewController(controller)
|
||||
|
|
@ -3792,7 +3792,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
|
||||
)
|
||||
|> deliverOnMainQueue).startStandalone(next: { peer in
|
||||
guard let sourceController = sourceController, let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
|
||||
guard let sourceController = sourceController, let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
|
||||
return
|
||||
}
|
||||
(sourceController.navigationController as? NavigationController)?.pushViewController(controller)
|
||||
|
|
@ -7298,7 +7298,7 @@ private final class ChatListLocationContext {
|
|||
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
|
||||
)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
|
||||
guard let self, let peer = peer, let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
|
||||
guard let self, let peer = peer, let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
|
||||
return
|
||||
}
|
||||
(self.parentController?.navigationController as? NavigationController)?.pushViewController(controller)
|
||||
|
|
|
|||
|
|
@ -164,9 +164,9 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
|||
var enabled = true
|
||||
if filter.contains(.onlyWriteable) {
|
||||
if let peer = chatPeer {
|
||||
enabled = canSendMessagesToPeer(peer._asPeer())
|
||||
enabled = canSendMessagesToPeer(peer)
|
||||
} else {
|
||||
enabled = canSendMessagesToPeer(primaryPeer._asPeer())
|
||||
enabled = canSendMessagesToPeer(primaryPeer)
|
||||
}
|
||||
if requiresPremiumForMessaging {
|
||||
enabled = false
|
||||
|
|
@ -753,7 +753,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
|||
var enabled = true
|
||||
if filter.contains(.onlyWriteable) {
|
||||
if let peer = chatPeer {
|
||||
enabled = canSendMessagesToPeer(peer._asPeer())
|
||||
enabled = canSendMessagesToPeer(peer)
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
|
|
@ -892,7 +892,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
|||
var enabled = true
|
||||
if filter.contains(.onlyWriteable) {
|
||||
if let peer = chatPeer {
|
||||
enabled = canSendMessagesToPeer(peer._asPeer())
|
||||
enabled = canSendMessagesToPeer(peer)
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
|
|
@ -1015,7 +1015,7 @@ 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._asPeer())
|
||||
enabled = canSendMessagesToPeer(peer.peer)
|
||||
if requiresPremiumForMessaging {
|
||||
enabled = false
|
||||
}
|
||||
|
|
@ -1404,7 +1404,7 @@ private struct ChatListSearchListPaneNodeState: Equatable {
|
|||
|
||||
private func doesPeerMatchFilter(peer: EnginePeer, filter: ChatListNodePeersFilter) -> Bool {
|
||||
var enabled = true
|
||||
if filter.contains(.onlyWriteable), !canSendMessagesToPeer(peer._asPeer()) {
|
||||
if filter.contains(.onlyWriteable), !canSendMessagesToPeer(peer) {
|
||||
enabled = false
|
||||
}
|
||||
if filter.contains(.onlyPrivateChats) {
|
||||
|
|
@ -4461,7 +4461,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
keepStack: .always
|
||||
))
|
||||
case .info:
|
||||
if let peerInfoScreen = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let peerInfoScreen = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
navigationController.pushViewController(peerInfoScreen)
|
||||
}
|
||||
case .openApp:
|
||||
|
|
|
|||
|
|
@ -503,7 +503,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
|||
} else {
|
||||
if filter.contains(.onlyWriteable) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if !canSendMessagesToPeer(peer._asPeer()) {
|
||||
if !canSendMessagesToPeer(peer) {
|
||||
enabled = false
|
||||
}
|
||||
if peerEntry.requiresPremiumForMessaging {
|
||||
|
|
@ -864,7 +864,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
|||
} else {
|
||||
if filter.contains(.onlyWriteable) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if !canSendMessagesToPeer(peer._asPeer()) {
|
||||
if !canSendMessagesToPeer(peer) {
|
||||
enabled = false
|
||||
}
|
||||
if peerEntry.requiresPremiumForMessaging {
|
||||
|
|
@ -2226,7 +2226,7 @@ public final class ChatListNode: ListViewImpl {
|
|||
|
||||
if filter.contains(.onlyWriteable) && filter.contains(.excludeDisabled) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if !canSendMessagesToPeer(peer._asPeer()) {
|
||||
if !canSendMessagesToPeer(peer) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1572,7 +1572,7 @@ public func canBypassRestrictions(chatPresentationInterfaceState: ChatPresentati
|
|||
public func canSendMessagesToChat(_ state: ChatPresentationInterfaceState) -> Bool {
|
||||
if let peer = state.renderedPeer?.peer {
|
||||
let canBypassRestrictions = canBypassRestrictions(chatPresentationInterfaceState: state)
|
||||
if canSendMessagesToPeer(peer, ignoreDefault: canBypassRestrictions) {
|
||||
if canSendMessagesToPeer(EnginePeer(peer), ignoreDefault: canBypassRestrictions) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
|
|||
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
|
||||
guard let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
|
||||
return
|
||||
}
|
||||
(contactsController?.navigationController as? NavigationController)?.pushViewController(controller)
|
||||
|
|
|
|||
|
|
@ -475,7 +475,7 @@ public class ContactsController: ViewController {
|
|||
}
|
||||
|> 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._asPeer(), threadId: nil, temporary: false), in: .window(.root))
|
||||
controller.present(strongSelf.context.sharedContext.makeChatQrCodeScreen(context: strongSelf.context, peer: peer, threadId: nil, temporary: false), in: .window(.root))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -774,7 +774,7 @@ public class ContactsController: ViewController {
|
|||
}
|
||||
if let peer {
|
||||
Queue.mainQueue().async {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController {
|
||||
navigationController.pushViewController(infoController)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -485,7 +485,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
|
|||
var enabled = true
|
||||
var requiresPremiumForMessaging = false
|
||||
if onlyWriteable {
|
||||
enabled = canSendMessagesToPeer(peer._asPeer())
|
||||
enabled = canSendMessagesToPeer(peer)
|
||||
if let value = peerRequiresPremiumForMessaging[peer.id], value {
|
||||
requiresPremiumForMessaging = true
|
||||
enabled = false
|
||||
|
|
@ -525,7 +525,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
|
|||
var enabled = true
|
||||
var requiresPremiumForMessaging = false
|
||||
if onlyWriteable {
|
||||
enabled = canSendMessagesToPeer(peer.peer._asPeer())
|
||||
enabled = canSendMessagesToPeer(peer.peer)
|
||||
if let value = peerRequiresPremiumForMessaging[peer.peer.id], value {
|
||||
requiresPremiumForMessaging = true
|
||||
enabled = false
|
||||
|
|
@ -559,7 +559,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
|
|||
var enabled = true
|
||||
var requiresPremiumForMessaging = false
|
||||
if onlyWriteable {
|
||||
enabled = canSendMessagesToPeer(peer.peer._asPeer())
|
||||
enabled = canSendMessagesToPeer(peer.peer)
|
||||
if let value = peerRequiresPremiumForMessaging[peer.peer.id], value {
|
||||
requiresPremiumForMessaging = true
|
||||
enabled = false
|
||||
|
|
|
|||
|
|
@ -769,7 +769,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
}
|
||||
|
||||
if let peer, let message = self.message, canSendMessagesToPeer(peer._asPeer()) {
|
||||
if let peer, let message = self.message, canSendMessagesToPeer(peer) {
|
||||
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuReply, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reply"), color: theme.contextMenu.primaryColor)}, action: { [weak self] _, f in
|
||||
if let self, let navigationController = self.baseNavigationController() {
|
||||
self.beginCustomDismiss(.simpleAnimation)
|
||||
|
|
|
|||
|
|
@ -3905,7 +3905,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
}
|
||||
|
||||
if let peer, let (message, _, _) = strongSelf.contentInfo(), canSendMessagesToPeer(peer._asPeer()) {
|
||||
if let peer, let (message, _, _) = strongSelf.contentInfo(), canSendMessagesToPeer(peer) {
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuReply, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reply"), color: theme.contextMenu.primaryColor)}, action: { [weak self] _, f in
|
||||
if let self, let navigationController = self.baseNavigationController() {
|
||||
self.beginCustomDismiss(.simpleAnimation)
|
||||
|
|
|
|||
|
|
@ -1763,7 +1763,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id))
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
if let strongSelf = self, let peer = peer {
|
||||
if let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
strongSelf.getNavigationController()?.pushViewController(controller)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -376,7 +376,7 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
|
|||
}
|
||||
}
|
||||
navigateToProfileImpl = { [weak controller] peer in
|
||||
if let navigationController = controller?.navigationController as? NavigationController, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false, requestsContext: nil) {
|
||||
if let navigationController = controller?.navigationController as? NavigationController, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false, requestsContext: nil) {
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -621,7 +621,7 @@ public func legacyAttachmentMenu(
|
|||
peerSupportsPolls = true
|
||||
}
|
||||
}
|
||||
if let peer, peerSupportsPolls, canSendMessagesToPeer(peer) && canSendPolls {
|
||||
if let peer, peerSupportsPolls, canSendMessagesToPeer(EnginePeer(peer)) && canSendPolls {
|
||||
let pollItem = TGMenuSheetButtonItemView(title: presentationData.strings.AttachmentMenu_Poll, type: TGMenuSheetButtonTypeDefault, fontSize: fontSize, action: { [weak controller] in
|
||||
controller?.dismiss(animated: true)
|
||||
openPoll()
|
||||
|
|
|
|||
|
|
@ -340,7 +340,7 @@ public final class SecureIdAuthController: ViewController, StandalonePresentable
|
|||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(infoController)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1230,7 +1230,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
|
|||
guard let peer else {
|
||||
return
|
||||
}
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: updatedPresentationData, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: updatedPresentationData, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
pushControllerImpl?(controller)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -323,7 +323,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
|
|||
.init(type: .destructive, title: presentationData.strings.Channel_Management_DismissAdmin, action: { arguments.removeAdmin(participant.peer.id) })
|
||||
])
|
||||
|
||||
return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: participant.presences[participant.peer.id].flatMap { EnginePeer.Presence($0) }, text: peerText.isEmpty ? .presence : .text(peerText, .secondary), label: label, editing: editing, revealOptions: revealOptions, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: action, setPeerIdWithRevealedOptions: { previousId, id in
|
||||
return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: participant.peer, presence: participant.presences[participant.peer.id].flatMap { EnginePeer.Presence($0) }, text: peerText.isEmpty ? .presence : .text(peerText, .secondary), label: label, editing: editing, revealOptions: revealOptions, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: action, setPeerIdWithRevealedOptions: { previousId, id in
|
||||
arguments.setPeerIdWithRevealedOptions(previousId, id)
|
||||
}, removePeer: { peerId in
|
||||
arguments.removeAdmin(peerId)
|
||||
|
|
@ -731,7 +731,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
|
|||
}
|
||||
if case .legacyGroup = peer {
|
||||
} else {
|
||||
pushControllerImpl?(context.sharedContext.makeChatRecentActionsController(context: context, peer: peer._asPeer(), adminPeerId: nil, starsState: nil))
|
||||
pushControllerImpl?(context.sharedContext.makeChatRecentActionsController(context: context, peer: peer, adminPeerId: nil, starsState: nil))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -918,12 +918,12 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
|
|||
}
|
||||
switch participant {
|
||||
case let .creator(_, rank):
|
||||
result.append(RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: rank), peer: peer._asPeer(), presences: presences))
|
||||
result.append(RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: rank), peer: peer, presences: presences))
|
||||
case let .admin(_, _, _, rank):
|
||||
var peers: [EnginePeer.Id: EnginePeer] = [:]
|
||||
peers[creator.id] = creator
|
||||
peers[peer.id] = peer
|
||||
result.append(RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: .internal_groupSpecific), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: rank, subscriptionUntilDate: nil), peer: peer._asPeer(), peers: peers.mapValues({ $0._asPeer() }), presences: presences))
|
||||
result.append(RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: .internal_groupSpecific), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: rank, subscriptionUntilDate: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }), presences: presences))
|
||||
case .member:
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -782,7 +782,7 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
|
|||
guard let peer else {
|
||||
return
|
||||
}
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: updatedPresentationData, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: updatedPresentationData, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
pushControllerImpl?(controller)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry {
|
|||
default:
|
||||
break
|
||||
}
|
||||
return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: {
|
||||
return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: participant.peer, presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: {
|
||||
arguments.openPeer(participant)
|
||||
}, setPeerIdWithRevealedOptions: { previousId, id in
|
||||
arguments.setPeerIdWithRevealedOptions(previousId, id)
|
||||
|
|
@ -363,20 +363,20 @@ public func channelBlacklistController(context: AccountContext, updatedPresentat
|
|||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let actionSheet = ActionSheetController(presentationData: presentationData)
|
||||
var items: [ActionSheetItem] = []
|
||||
if !EnginePeer(participant.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder).isEmpty {
|
||||
items.append(ActionSheetTextItem(title: EnginePeer(participant.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)))
|
||||
if !participant.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder).isEmpty {
|
||||
items.append(ActionSheetTextItem(title: participant.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)))
|
||||
}
|
||||
let viewInfoTitle: String
|
||||
if participant.peer is TelegramChannel {
|
||||
if case .channel = participant.peer {
|
||||
viewInfoTitle = presentationData.strings.GroupRemoved_ViewChannelInfo
|
||||
} else {
|
||||
viewInfoTitle = presentationData.strings.GroupRemoved_ViewUserInfo
|
||||
}
|
||||
items.append(ActionSheetButtonItem(title: viewInfoTitle, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
if participant.peer is TelegramChannel {
|
||||
if case .channel = participant.peer {
|
||||
if let navigationController = getNavigationControllerImpl?() {
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(EnginePeer(participant.peer))))
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(participant.peer)))
|
||||
}
|
||||
} else if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: participant.peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
pushControllerImpl?(infoController)
|
||||
|
|
|
|||
|
|
@ -302,7 +302,7 @@ private enum ChannelMembersEntry: ItemListNodeEntry {
|
|||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .peerItem(_, _, strings, dateTimeFormat, nameDisplayOrder, participant, editing, enabled, _, isGroup):
|
||||
let text: ItemListPeerItemText
|
||||
if let user = participant.peer as? TelegramUser, let _ = user.botInfo {
|
||||
if case let .user(user) = participant.peer, let _ = user.botInfo {
|
||||
text = .text(strings.Bot_GenericBotStatus, .secondary)
|
||||
} else {
|
||||
text = .presence
|
||||
|
|
@ -330,7 +330,7 @@ private enum ChannelMembersEntry: ItemListNodeEntry {
|
|||
label = .none
|
||||
}
|
||||
|
||||
return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: participant.presences[participant.peer.id].flatMap(EnginePeer.Presence.init), text: text, label: label, editing: editing, switchValue: nil, enabled: enabled, selectable: participant.peer.id != arguments.context.account.peerId, sectionId: self.section, action: {
|
||||
return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: participant.peer, presence: participant.presences[participant.peer.id].flatMap(EnginePeer.Presence.init), text: text, label: label, editing: editing, switchValue: nil, enabled: enabled, selectable: participant.peer.id != arguments.context.account.peerId, sectionId: self.section, action: {
|
||||
arguments.openParticipant(participant, isGroup)
|
||||
}, setPeerIdWithRevealedOptions: { previousId, id in
|
||||
arguments.setPeerIdWithRevealedOptions(previousId, id)
|
||||
|
|
@ -782,7 +782,7 @@ public func channelMembersController(context: AccountContext, updatedPresentatio
|
|||
return state.withUpdatedSearchingMembers(false)
|
||||
}
|
||||
}, openPeer: { peer, _ in
|
||||
if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
pushControllerImpl?(infoController)
|
||||
}
|
||||
}, pushController: { c in
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ private final class ChannelMembersSearchEntry: Comparable, Identifiable {
|
|||
displayOrder: nameDisplayOrder,
|
||||
context: context,
|
||||
peerMode: .peer,
|
||||
peer: .peer(peer: EnginePeer(participant.peer), chatPeer: EnginePeer(participant.peer)),
|
||||
peer: .peer(peer: participant.peer, chatPeer: participant.peer),
|
||||
status: status,
|
||||
rightLabelText: label.flatMap { .init(text: $0, color: labelColor, hasBackground: labelBackground) },
|
||||
enabled: enabled,
|
||||
|
|
@ -220,7 +220,7 @@ private final class ChannelMembersSearchEntry: Comparable, Identifiable {
|
|||
index: nil,
|
||||
header: self.section.chatListHeaderType.flatMap({ ChatListSearchItemHeader(type: $0, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) }),
|
||||
action: { _ in
|
||||
interaction.peerSelected(EnginePeer(participant.peer), participant)
|
||||
interaction.peerSelected(participant.peer, participant)
|
||||
},
|
||||
setPeerIdWithRevealedOptions: { peerId, fromPeerId in
|
||||
interaction.setPeerIdWithRevealedOptions(RevealedPeerId(peerId: participant.peer.id, section: self.section), fromPeerId.flatMap({ RevealedPeerId(peerId: $0, section: self.section) }))
|
||||
|
|
@ -749,7 +749,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
|||
continue
|
||||
}
|
||||
|
||||
if excludeBots, let user = participant.peer as? TelegramUser, user.botInfo != nil {
|
||||
if excludeBots, case let .user(user) = participant.peer, user.botInfo != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -881,7 +881,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
|||
}
|
||||
|
||||
for participant in foundMembers {
|
||||
if excludeBots, let user = participant.peer as? TelegramUser, user.botInfo != nil {
|
||||
if excludeBots, case let .user(user) = participant.peer, user.botInfo != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -984,18 +984,18 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
|||
let renderedParticipant: RenderedChannelParticipant
|
||||
switch participant {
|
||||
case .creator:
|
||||
renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer)
|
||||
renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: EnginePeer(peer))
|
||||
case .admin:
|
||||
var peers: [EnginePeer.Id: EnginePeer] = [:]
|
||||
if let creator = creatorPeer {
|
||||
peers[creator.id] = creator
|
||||
}
|
||||
peers[peer.id] = EnginePeer(peer)
|
||||
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: .legacyGroup(group))), promotedBy: creatorPeer?.id ?? context.account.peerId, canBeEditedByAccountPeer: creatorPeer?.id == context.account.peerId), banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }))
|
||||
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: .legacyGroup(group))), promotedBy: creatorPeer?.id ?? context.account.peerId, canBeEditedByAccountPeer: creatorPeer?.id == context.account.peerId), banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: EnginePeer(peer), peers: peers.mapValues({ $0._asPeer() }))
|
||||
case .member:
|
||||
var peers: [EnginePeer.Id: EnginePeer] = [:]
|
||||
peers[peer.id] = EnginePeer(peer)
|
||||
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }))
|
||||
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: EnginePeer(peer), peers: peers.mapValues({ $0._asPeer() }))
|
||||
}
|
||||
matchingMembers.append(renderedParticipant)
|
||||
}
|
||||
|
|
@ -1049,7 +1049,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
|||
var index = 0
|
||||
|
||||
for participant in foundGroupMembers {
|
||||
if excludeBots, let user = participant.peer as? TelegramUser, user.botInfo != nil {
|
||||
if excludeBots, case let .user(user) = participant.peer, user.botInfo != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -1133,7 +1133,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
|||
}
|
||||
|
||||
for participant in foundMembers {
|
||||
if excludeBots, let user = participant.peer as? TelegramUser, user.botInfo != nil {
|
||||
if excludeBots, case let .user(user) = participant.peer, user.botInfo != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -144,8 +144,8 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable {
|
|||
headerType = isChannel ? .subscribers : .groupMembers
|
||||
}
|
||||
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(participant.peer), chatPeer: nil), status: status, enabled: enabled, selection: .none, editing: editing, index: nil, header: ChatListSearchItemHeader(type: headerType, theme: presentationData.theme, strings: presentationData.strings), action: { _ in
|
||||
interaction.openPeer(EnginePeer(participant.peer), participant)
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: participant.peer, chatPeer: nil), status: status, enabled: enabled, selection: .none, editing: editing, index: nil, header: ChatListSearchItemHeader(type: headerType, theme: presentationData.theme, strings: presentationData.strings), action: { _ in
|
||||
interaction.openPeer(participant.peer, participant)
|
||||
})
|
||||
case let .contact(_, peer, presence):
|
||||
let status: ContactsPeerItemStatus
|
||||
|
|
@ -401,16 +401,16 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||
let renderedParticipant: RenderedChannelParticipant
|
||||
switch participant {
|
||||
case .creator:
|
||||
renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer, presences: peerView.peerPresences)
|
||||
renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: EnginePeer(peer), presences: peerView.peerPresences)
|
||||
case .admin:
|
||||
var peers: [EnginePeer.Id: EnginePeer] = [:]
|
||||
peers[creator.id] = creator
|
||||
peers[peer.id] = EnginePeer(peer)
|
||||
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: EnginePeer(mainPeer))), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }), presences: peerView.peerPresences)
|
||||
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: EnginePeer(mainPeer))), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: EnginePeer(peer), peers: peers.mapValues({ $0._asPeer() }), presences: peerView.peerPresences)
|
||||
case .member:
|
||||
var peers: [EnginePeer.Id: EnginePeer] = [:]
|
||||
peers[peer.id] = EnginePeer(peer)
|
||||
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }), presences: peerView.peerPresences)
|
||||
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: EnginePeer(peer), peers: peers.mapValues({ $0._asPeer() }), presences: peerView.peerPresences)
|
||||
}
|
||||
|
||||
entries.append(.peer(index, renderedParticipant, ContactsPeerItemEditing(editable: false, editing: false, revealed: false), label, enabled, false, false))
|
||||
|
|
@ -513,7 +513,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||
case .excludeNonMembers:
|
||||
break
|
||||
case .excludeBots:
|
||||
if let user = participant.peer as? TelegramUser, user.botInfo != nil {
|
||||
if case let .user(user) = participant.peer, user.botInfo != nil {
|
||||
continue contactsLoop
|
||||
}
|
||||
}
|
||||
|
|
@ -555,7 +555,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||
case .excludeNonMembers:
|
||||
break
|
||||
case .excludeBots:
|
||||
if let user = participant.peer as? TelegramUser, user.botInfo != nil {
|
||||
if case let .user(user) = participant.peer, user.botInfo != nil {
|
||||
continue participantsLoop
|
||||
}
|
||||
}
|
||||
|
|
@ -568,7 +568,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||
if participant.peer.id == context.account.peerId {
|
||||
continue
|
||||
}
|
||||
if let user = participant.peer as? TelegramUser, user.botInfo != nil || user.flags.contains(.isSupport) {
|
||||
if case let .user(user) = participant.peer, user.botInfo != nil || user.flags.contains(.isSupport) {
|
||||
continue
|
||||
}
|
||||
for filter in filters {
|
||||
|
|
@ -584,7 +584,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||
case .excludeNonMembers:
|
||||
break
|
||||
case .excludeBots:
|
||||
if let user = participant.peer as? TelegramUser, user.botInfo != nil {
|
||||
if case let .user(user) = participant.peer, user.botInfo != nil {
|
||||
continue participantsLoop
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -477,10 +477,10 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
|
|||
default:
|
||||
break
|
||||
}
|
||||
return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: canOpen ? {
|
||||
return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: participant.peer, presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: canOpen ? {
|
||||
arguments.openPeer(participant.participant)
|
||||
} : {
|
||||
arguments.openPeerInfo(EnginePeer(participant.peer))
|
||||
arguments.openPeerInfo(participant.peer)
|
||||
}, setPeerIdWithRevealedOptions: { previousId, id in
|
||||
arguments.setPeerIdWithRevealedOptions(previousId, id)
|
||||
}, removePeer: { peerId in
|
||||
|
|
@ -1108,7 +1108,7 @@ public func channelPermissionsController(context: AccountContext, updatedPresent
|
|||
}), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
})
|
||||
}, openPeerInfo: { peer in
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
pushControllerImpl?(controller)
|
||||
}
|
||||
}, openKicked: {
|
||||
|
|
|
|||
|
|
@ -626,7 +626,7 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
|
|||
controller?.clearItemNodesHighlight(animated: true)
|
||||
}
|
||||
navigateToProfileImpl = { [weak controller] peer, distance in
|
||||
if let navigationController = controller?.navigationController as? NavigationController, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .nearbyPeer(distance: distance), avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false, requestsContext: nil) {
|
||||
if let navigationController = controller?.navigationController as? NavigationController, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .nearbyPeer(distance: distance), avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false, requestsContext: nil) {
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,13 +27,13 @@ public func searchPeerMembers(context: AccountContext, peerId: EnginePeer.Id, ch
|
|||
return nil
|
||||
}
|
||||
if normalizedQuery.isEmpty {
|
||||
return EnginePeer(participant.peer)
|
||||
return participant.peer
|
||||
} else {
|
||||
if participant.peer.indexName.matchesByTokens(normalizedQuery) || participant.peer.indexName.matchesByTokens(transformedQuery) {
|
||||
return EnginePeer(participant.peer)
|
||||
return participant.peer
|
||||
}
|
||||
if let addressName = participant.peer.addressName, addressName.lowercased().hasPrefix(normalizedQuery) || addressName.lowercased().hasPrefix(transformedQuery) {
|
||||
return EnginePeer(participant.peer)
|
||||
return participant.peer
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -58,7 +58,7 @@ public func searchPeerMembers(context: AccountContext, peerId: EnginePeer.Id, ch
|
|||
if participant.peer.isDeleted {
|
||||
return nil
|
||||
}
|
||||
return EnginePeer(participant.peer)
|
||||
return participant.peer
|
||||
}, true))
|
||||
}
|
||||
})
|
||||
|
|
@ -73,7 +73,7 @@ public func searchPeerMembers(context: AccountContext, peerId: EnginePeer.Id, ch
|
|||
if participant.peer.isDeleted {
|
||||
return nil
|
||||
}
|
||||
return EnginePeer(participant.peer)
|
||||
return participant.peer
|
||||
}, true))
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ public func blockedPeersController(context: AccountContext, blockedPeersContext:
|
|||
}
|
||||
}))
|
||||
}, openPeer: { 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: EnginePeer(peer), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
pushControllerImpl?(controller)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -506,7 +506,7 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri
|
|||
}))
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}, openPeer: { peer in
|
||||
guard let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
|
||||
guard let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
|
||||
return
|
||||
}
|
||||
pushControllerImpl?(controller)
|
||||
|
|
|
|||
|
|
@ -968,7 +968,7 @@ private func myProfileSearchableItems(context: AccountContext) -> [SettingsSearc
|
|||
present: { context, _, present in
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer = peer?._asPeer() else {
|
||||
guard let peer = peer else {
|
||||
return
|
||||
}
|
||||
let controller = context.sharedContext.makeChatQrCodeScreen(context: context, peer: peer, threadId: nil, temporary: false)
|
||||
|
|
@ -977,7 +977,7 @@ private func myProfileSearchableItems(context: AccountContext) -> [SettingsSearc
|
|||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
//TODO:fix
|
||||
items.append(
|
||||
SettingsSearchableItem(
|
||||
|
|
@ -986,7 +986,7 @@ private func myProfileSearchableItems(context: AccountContext) -> [SettingsSearc
|
|||
present: { context, _, present in
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer = peer?._asPeer() else {
|
||||
guard let peer = peer else {
|
||||
return
|
||||
}
|
||||
let controller = context.sharedContext.makeChatQrCodeScreen(context: context, peer: peer, threadId: nil, temporary: false)
|
||||
|
|
@ -1017,7 +1017,7 @@ private func myProfileSearchableItems(context: AccountContext) -> [SettingsSearc
|
|||
present: { context, _, present in
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer = peer?._asPeer() else {
|
||||
guard let peer = peer else {
|
||||
return
|
||||
}
|
||||
let controller = context.sharedContext.makePeerInfoController(
|
||||
|
|
@ -1034,7 +1034,7 @@ private func myProfileSearchableItems(context: AccountContext) -> [SettingsSearc
|
|||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
items.append(
|
||||
SettingsSearchableItem(
|
||||
id: "my-profile/edit",
|
||||
|
|
@ -1043,7 +1043,7 @@ private func myProfileSearchableItems(context: AccountContext) -> [SettingsSearc
|
|||
present: { context, _, present in
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer = peer?._asPeer() else {
|
||||
guard let peer = peer else {
|
||||
return
|
||||
}
|
||||
let controller = context.sharedContext.makePeerInfoController(
|
||||
|
|
@ -1056,7 +1056,7 @@ private func myProfileSearchableItems(context: AccountContext) -> [SettingsSearc
|
|||
requestsContext: nil
|
||||
)
|
||||
present(.push, controller)
|
||||
|
||||
|
||||
Queue.mainQueue().justDispatch {
|
||||
if let controller = controller as? PeerInfoScreen {
|
||||
controller.activateEdit()
|
||||
|
|
@ -1077,7 +1077,7 @@ private func myProfileSearchableItems(context: AccountContext) -> [SettingsSearc
|
|||
present: { context, _, present in
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer = peer?._asPeer() else {
|
||||
guard let peer = peer else {
|
||||
return
|
||||
}
|
||||
let controller = context.sharedContext.makePeerInfoController(
|
||||
|
|
|
|||
|
|
@ -2494,7 +2494,7 @@ public final class ShareController: ViewController {
|
|||
for entry in view.0.entries.reversed() {
|
||||
switch entry {
|
||||
case let .MessageEntry(entryData):
|
||||
if let peer = entryData.renderedPeer.peers[entryData.renderedPeer.peerId], peer.id != accountPeer.id, canSendMessagesToPeer(peer) {
|
||||
if let peer = entryData.renderedPeer.peers[entryData.renderedPeer.peerId], peer.id != accountPeer.id, canSendMessagesToPeer(EnginePeer(peer)) {
|
||||
peers.append(EngineRenderedPeer(entryData.renderedPeer))
|
||||
if let user = peer as? TelegramUser, user.flags.contains(.requirePremium) || user.flags.contains(.requireStars) {
|
||||
possiblePremiumRequiredPeers.insert(user.id)
|
||||
|
|
|
|||
|
|
@ -381,7 +381,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode {
|
|||
|
||||
for renderedPeer in foundLocalPeers {
|
||||
if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != accountPeer.id {
|
||||
if !existingPeerIds.contains(renderedPeer.peerId) && canSendMessagesToPeer(peer) {
|
||||
if !existingPeerIds.contains(renderedPeer.peerId) && canSendMessagesToPeer(EnginePeer(peer)) {
|
||||
existingPeerIds.insert(renderedPeer.peerId)
|
||||
entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(renderedPeer), presence: nil, requiresPremiumForMessaging: peerRequiresPremiumForMessaging[peer.id] ?? false, requiresStars: nil, theme: theme, strings: strings, isGlobal: false))
|
||||
index += 1
|
||||
|
|
@ -399,7 +399,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode {
|
|||
} else {
|
||||
for foundPeer in foundRemotePeers.0 {
|
||||
let peer = foundPeer.peer
|
||||
if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer._asPeer()) {
|
||||
if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) {
|
||||
existingPeerIds.insert(peer.id)
|
||||
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
|
||||
|
|
@ -408,7 +408,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode {
|
|||
|
||||
for foundPeer in foundRemotePeers.1 {
|
||||
let peer = foundPeer.peer
|
||||
if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer._asPeer()) {
|
||||
if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) {
|
||||
existingPeerIds.insert(peer.id)
|
||||
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
|
||||
|
|
@ -474,7 +474,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode {
|
|||
}
|
||||
var index = 0
|
||||
for (peer, requiresPremiumForMessaging) in recentPeerList {
|
||||
if let mainPeer = peer.peers[peer.peerId], canSendMessagesToPeer(mainPeer._asPeer()) {
|
||||
if let mainPeer = peer.peers[peer.peerId], canSendMessagesToPeer(mainPeer) {
|
||||
recentItemList.append(.peer(index: index, theme: theme, peer: mainPeer, associatedPeer: mainPeer._asPeer().associatedPeerId.flatMap { peer.peers[$0] }, presence: nil, requiresPremiumForMessaging: requiresPremiumForMessaging, requiresStars: nil, strings: strings))
|
||||
index += 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -880,7 +880,7 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat
|
|||
}
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
})
|
||||
|
|
@ -912,7 +912,7 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat
|
|||
}
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
let controller = context.sharedContext.makeChatRecentActionsController(context: context, peer: peer._asPeer(), adminPeerId: participantPeerId, starsState: nil)
|
||||
let controller = context.sharedContext.makeChatRecentActionsController(context: context, peer: peer, adminPeerId: participantPeerId, starsState: nil)
|
||||
navigationController.pushViewController(controller)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -601,7 +601,7 @@ public func messageStatsController(context: AccountContext, updatedPresentationD
|
|||
return
|
||||
}
|
||||
if case .user = peer {
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false, requestsContext: nil) {
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false, requestsContext: nil) {
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ extension VideoChatScreenComponent.View {
|
|||
} else {
|
||||
text = environment.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string
|
||||
}
|
||||
self.presentToast(icon: .peer(EnginePeer(participant.peer)), text: text, duration: 3)
|
||||
self.presentToast(icon: .peer(participant.peer), text: text, duration: 3)
|
||||
}
|
||||
} else {
|
||||
if case let .channel(groupPeer) = groupPeer, let listenerLink = inviteLinks?.listenerLink, !groupPeer.hasPermission(.inviteMembers) {
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ func _internal_requestStartBotInGroup(account: Account, botPeerId: PeerId, group
|
|||
let presences: [PeerId: PeerPresence] = [:]
|
||||
|
||||
peers[peer.id] = peer
|
||||
return .channelParticipant(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presences: presences))
|
||||
return .channelParticipant(RenderedChannelParticipant(participant: participant, peer: EnginePeer(peer), peers: peers, presences: presences))
|
||||
} else {
|
||||
return .none
|
||||
}
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ func _internal_addChannelMember(account: Account, peerId: PeerId, memberId: Peer
|
|||
}
|
||||
}
|
||||
}
|
||||
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: memberPeer, peers: peers, presences: presences))
|
||||
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: EnginePeer(memberPeer), peers: peers, presences: presences))
|
||||
}
|
||||
|> mapError { _ -> AddChannelMemberError in }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net
|
|||
let participant = ChannelParticipant(apiParticipant: channelAdminLogEventActionParticipantInviteData.participant)
|
||||
|
||||
if let peer = peers[participant.peerId] {
|
||||
action = .participantInvite(RenderedChannelParticipant(participant: participant, peer: peer))
|
||||
action = .participantInvite(RenderedChannelParticipant(participant: participant, peer: EnginePeer(peer)))
|
||||
}
|
||||
case let .channelAdminLogEventActionParticipantToggleBan(channelAdminLogEventActionParticipantToggleBanData):
|
||||
let (prev, new) = (channelAdminLogEventActionParticipantToggleBanData.prevParticipant, channelAdminLogEventActionParticipantToggleBanData.newParticipant)
|
||||
|
|
@ -276,7 +276,7 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net
|
|||
let newParticipant = ChannelParticipant(apiParticipant: new)
|
||||
|
||||
if let prevPeer = peers[prevParticipant.peerId], let newPeer = peers[newParticipant.peerId] {
|
||||
action = .participantToggleBan(prev: RenderedChannelParticipant(participant: prevParticipant, peer: prevPeer), new: RenderedChannelParticipant(participant: newParticipant, peer: newPeer))
|
||||
action = .participantToggleBan(prev: RenderedChannelParticipant(participant: prevParticipant, peer: EnginePeer(prevPeer)), new: RenderedChannelParticipant(participant: newParticipant, peer: EnginePeer(newPeer)))
|
||||
}
|
||||
case let .channelAdminLogEventActionParticipantToggleAdmin(channelAdminLogEventActionParticipantToggleAdminData):
|
||||
let (prev, new) = (channelAdminLogEventActionParticipantToggleAdminData.prevParticipant, channelAdminLogEventActionParticipantToggleAdminData.newParticipant)
|
||||
|
|
@ -284,7 +284,7 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net
|
|||
let newParticipant = ChannelParticipant(apiParticipant: new)
|
||||
|
||||
if let prevPeer = peers[prevParticipant.peerId], let newPeer = peers[newParticipant.peerId] {
|
||||
action = .participantToggleAdmin(prev: RenderedChannelParticipant(participant: prevParticipant, peer: prevPeer), new: RenderedChannelParticipant(participant: newParticipant, peer: newPeer))
|
||||
action = .participantToggleAdmin(prev: RenderedChannelParticipant(participant: prevParticipant, peer: EnginePeer(prevPeer)), new: RenderedChannelParticipant(participant: newParticipant, peer: EnginePeer(newPeer)))
|
||||
}
|
||||
case let .channelAdminLogEventActionChangeStickerSet(channelAdminLogEventActionChangeStickerSetData):
|
||||
let (prevStickerset, newStickerset) = (channelAdminLogEventActionChangeStickerSetData.prevStickerset, channelAdminLogEventActionChangeStickerSetData.newStickerset)
|
||||
|
|
@ -480,7 +480,7 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net
|
|||
let newParticipant = ChannelParticipant(apiParticipant: new)
|
||||
|
||||
if let prevPeer = peers[prevParticipant.peerId], let newPeer = peers[newParticipant.peerId] {
|
||||
action = .participantSubscriptionExtended(prev: RenderedChannelParticipant(participant: prevParticipant, peer: prevPeer), new: RenderedChannelParticipant(participant: newParticipant, peer: newPeer))
|
||||
action = .participantSubscriptionExtended(prev: RenderedChannelParticipant(participant: prevParticipant, peer: EnginePeer(prevPeer)), new: RenderedChannelParticipant(participant: newParticipant, peer: EnginePeer(newPeer)))
|
||||
}
|
||||
case let .channelAdminLogEventActionToggleAutotranslation(channelAdminLogEventActionToggleAutotranslationData):
|
||||
let newValue = channelAdminLogEventActionToggleAutotranslationData.newValue
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ func _internal_updateChannelMemberBannedRights(account: Account, peerId: PeerId,
|
|||
}
|
||||
}
|
||||
|
||||
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: memberPeer, peers: peers, presences: presences), isMember)
|
||||
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: EnginePeer(memberPeer), peers: peers, presences: presences), isMember)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ func _internal_channelMembers(postbox: Postbox, network: Network, accountPeerId:
|
|||
if let presence = transaction.getPeerPresence(peerId: participant.peerId) {
|
||||
renderedPresences[participant.peerId] = presence
|
||||
}
|
||||
items.append(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presences: renderedPresences))
|
||||
items.append(RenderedChannelParticipant(participant: participant, peer: EnginePeer(peer), peers: peers, presences: renderedPresences))
|
||||
}
|
||||
}
|
||||
case .channelParticipantsNotModified:
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ func _internal_updateChatOwnership(account: Account, peerId: PeerId, memberId: P
|
|||
if let presence = transaction.getPeerPresence(peerId: user.id) {
|
||||
presences[user.id] = presence
|
||||
}
|
||||
return [(currentCreator, RenderedChannelParticipant(participant: updatedPreviousCreator, peer: accountUser, peers: peers, presences: presences)), (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: user, peers: peers, presences: presences))]
|
||||
return [(currentCreator, RenderedChannelParticipant(participant: updatedPreviousCreator, peer: EnginePeer(accountUser), peers: peers, presences: presences)), (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: EnginePeer(user), peers: peers, presences: presences))]
|
||||
}
|
||||
|> mapError { _ -> ChatOwnershipTransferError in }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,18 +7,18 @@ import MtProtoKit
|
|||
|
||||
public struct RenderedChannelParticipant: Equatable {
|
||||
public let participant: ChannelParticipant
|
||||
public let peer: Peer
|
||||
public let peer: EnginePeer
|
||||
public let peers: [PeerId: Peer]
|
||||
public let presences: [PeerId: PeerPresence]
|
||||
|
||||
public init(participant: ChannelParticipant, peer: Peer, peers: [PeerId: Peer] = [:], presences: [PeerId: PeerPresence] = [:]) {
|
||||
|
||||
public init(participant: ChannelParticipant, peer: EnginePeer, peers: [PeerId: Peer] = [:], presences: [PeerId: PeerPresence] = [:]) {
|
||||
self.participant = participant
|
||||
self.peer = peer
|
||||
self.peers = peers
|
||||
self.presences = presences
|
||||
}
|
||||
|
||||
|
||||
public static func ==(lhs: RenderedChannelParticipant, rhs: RenderedChannelParticipant) -> Bool {
|
||||
return lhs.participant == rhs.participant && lhs.peer.isEqual(rhs.peer)
|
||||
return lhs.participant == rhs.participant && lhs.peer == rhs.peer
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ func _internal_joinChannel(account: Account, peerId: PeerId, hash: String?) -> S
|
|||
}
|
||||
}
|
||||
|
||||
return RenderedChannelParticipant(participant: updatedParticipant, peer: peer, peers: peers, presences: presences)
|
||||
return RenderedChannelParticipant(participant: updatedParticipant, peer: EnginePeer(peer), peers: peers, presences: presences)
|
||||
}
|
||||
|> castError(JoinChannelError.self)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@ func _internal_updateChannelAdminRights(account: Account, peerId: PeerId, adminI
|
|||
peers[peer.id] = peer
|
||||
}
|
||||
}
|
||||
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: adminPeer, peers: peers, presences: presences))
|
||||
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: EnginePeer(adminPeer), peers: peers, presences: presences))
|
||||
} |> mapError { _ -> UpdateChannelAdminRightsError in }
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ func _internal_updateChatRank(account: Account, peerId: PeerId, userId: PeerId,
|
|||
return .update(StoreMessage(id: currentMessage.id, customStableId: nil, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||
})
|
||||
}
|
||||
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: user, peers: peers, presences: presences))
|
||||
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: EnginePeer(user), peers: peers, presences: presences))
|
||||
}
|
||||
}
|
||||
|> castError(UpdateChatRankError.self)
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ func _internal_searchGroupMembers(postbox: Postbox, network: Network, accountPee
|
|||
let existingIds = Set(local.map { $0.id })
|
||||
let filtered: [Peer]
|
||||
if let participants = participants {
|
||||
filtered = participants.map({ $0.peer }).filter({ peer in
|
||||
filtered = participants.map({ $0.peer._asPeer() }).filter({ peer in
|
||||
if existingIds.contains(peer.id) {
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import Postbox
|
|||
private final class LinkHelperClass: NSObject {
|
||||
}
|
||||
|
||||
public func canSendMessagesToPeer(_ peer: Peer, ignoreDefault: Bool = false) -> Bool {
|
||||
public func canSendMessagesToPeer(_ peer: EnginePeer, ignoreDefault: Bool = false) -> Bool {
|
||||
let peer = peer._asPeer()
|
||||
if let peer = peer as? TelegramUser, peer.addressName == "replies" {
|
||||
return false
|
||||
} else if peer is TelegramUser || peer is TelegramGroup {
|
||||
|
|
|
|||
|
|
@ -281,7 +281,7 @@ private func adminUserActionsTitle(
|
|||
switch mode {
|
||||
case .monoforum:
|
||||
if let peer = peers.first {
|
||||
return strings.Monoforum_DeleteTopic_Title(EnginePeer(peer.peer).compactDisplayTitle).string
|
||||
return strings.Monoforum_DeleteTopic_Title(peer.peer.compactDisplayTitle).string
|
||||
} else {
|
||||
return strings.Common_Delete
|
||||
}
|
||||
|
|
@ -401,7 +401,7 @@ private final class AdminUserActionsContentComponent: Component {
|
|||
additionalSelectedPeers = component.sheetState.optionDeleteAllReactionsSelectedPeers
|
||||
isExpanded = component.sheetState.isOptionDeleteAllExpanded
|
||||
if component.peers.count == 1 {
|
||||
title = component.strings.Chat_AdminActionSheet_DeleteAllSingle(EnginePeer(component.peers[0].peer).compactDisplayTitle).string
|
||||
title = component.strings.Chat_AdminActionSheet_DeleteAllSingle(component.peers[0].peer.compactDisplayTitle).string
|
||||
} else {
|
||||
title = component.strings.Chat_AdminActionSheet_DeleteAllMultiple
|
||||
}
|
||||
|
|
@ -413,8 +413,8 @@ private final class AdminUserActionsContentComponent: Component {
|
|||
let banTitle: String
|
||||
let restrictTitle: String
|
||||
if component.peers.count == 1 {
|
||||
banTitle = component.strings.Chat_AdminActionSheet_BanSingle(EnginePeer(component.peers[0].peer).compactDisplayTitle).string
|
||||
restrictTitle = component.strings.Chat_AdminActionSheet_RestrictSingle(EnginePeer(component.peers[0].peer).compactDisplayTitle).string
|
||||
banTitle = component.strings.Chat_AdminActionSheet_BanSingle(component.peers[0].peer.compactDisplayTitle).string
|
||||
restrictTitle = component.strings.Chat_AdminActionSheet_RestrictSingle(component.peers[0].peer.compactDisplayTitle).string
|
||||
} else {
|
||||
banTitle = component.strings.Chat_AdminActionSheet_BanMultiple
|
||||
restrictTitle = component.strings.Chat_AdminActionSheet_RestrictMultiple
|
||||
|
|
@ -519,8 +519,8 @@ private final class AdminUserActionsContentComponent: Component {
|
|||
strings: component.strings,
|
||||
baseFontSize: component.presentationData.listsFontSize.baseDisplaySize,
|
||||
sideInset: 0.0,
|
||||
title: EnginePeer(peer.peer).displayTitle(strings: component.strings, displayOrder: .firstLast),
|
||||
peer: EnginePeer(peer.peer),
|
||||
title: peer.peer.displayTitle(strings: component.strings, displayOrder: .firstLast),
|
||||
peer: peer.peer,
|
||||
selectionState: .editing(isSelected: selectedPeers.contains(peer.peer.id)),
|
||||
action: { peer in
|
||||
component.togglePeerSelection(section, peer)
|
||||
|
|
|
|||
|
|
@ -356,7 +356,7 @@ public final class ChatRecentActionsController: TelegramBaseController {
|
|||
var adminPeers: [EnginePeer] = []
|
||||
if let result {
|
||||
for participant in result {
|
||||
adminPeers.append(EnginePeer(participant.peer))
|
||||
adminPeers.append(participant.peer)
|
||||
}
|
||||
}
|
||||
let controller = RecentActionsSettingsSheet(
|
||||
|
|
|
|||
|
|
@ -1008,7 +1008,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||
if peer is TelegramChannel, let navigationController = strongSelf.getNavigationController() {
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)), peekData: peekData, animated: true))
|
||||
} else {
|
||||
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: EnginePeer(peer), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
strongSelf.pushController(infoController)
|
||||
}
|
||||
}
|
||||
|
|
@ -1028,7 +1028,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||
|> deliverOnMainQueue).startStrict(next: { [weak self] peer in
|
||||
if let strongSelf = self {
|
||||
if let peer = peer {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
strongSelf.pushController(infoController)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry {
|
|||
peerText = strings.ChatAdmins_AdminLabel.lowercased()
|
||||
}
|
||||
}
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: .text(peerText, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: ItemListPeerItemSwitch(value: checked, style: .check), enabled: true, selectable: true, sectionId: self.section, action: {
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: participant.peer, presence: nil, text: .text(peerText, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: ItemListPeerItemSwitch(value: checked, style: .check), enabled: true, selectable: true, sectionId: self.section, action: {
|
||||
arguments.toggleAdmin(participant.peer.id)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in
|
||||
}, removePeer: { _ in })
|
||||
|
|
@ -442,7 +442,7 @@ public func channelRecentActionsFilterController(context: AccountContext, update
|
|||
antiSpamBotPeerPromise.set(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: antiSpamBotId))
|
||||
|> map { peer in
|
||||
if let peer = peer, case let .user(user) = peer {
|
||||
return RenderedChannelParticipant(participant: .member(id: user.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: user)
|
||||
return RenderedChannelParticipant(participant: .member(id: user.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: peer)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -672,7 +672,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||
for (_, peer) in participant.peers {
|
||||
peers[peer.id] = peer
|
||||
}
|
||||
peers[participant.peer.id] = participant.peer
|
||||
peers[participant.peer.id] = participant.peer._asPeer()
|
||||
|
||||
let action: TelegramMediaActionType
|
||||
action = TelegramMediaActionType.addedMembers(peerIds: [participant.peer.id])
|
||||
|
|
@ -716,7 +716,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||
}
|
||||
|
||||
if (prevBanInfo == nil || !prevBanInfo!.rights.flags.contains(.banReadMessages)) && newFlags.contains(.banReadMessages) {
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageKickedName(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageKickedNameUsername(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageKickedName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageKickedNameUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in
|
||||
var result: [MessageTextEntityType] = []
|
||||
if index == 0 {
|
||||
result.append(.TextMention(peerId: new.peer.id))
|
||||
|
|
@ -727,7 +727,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||
}, to: &text, entities: &entities)
|
||||
text += "\n"
|
||||
} else if isBroadcast, newBanInfo == nil, prevBanInfo != nil {
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageUnkickedName(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageUnkickedNameUsername(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageUnkickedName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageUnkickedNameUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in
|
||||
var result: [MessageTextEntityType] = []
|
||||
if index == 0 {
|
||||
result.append(.TextMention(peerId: new.peer.id))
|
||||
|
|
@ -737,7 +737,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||
return result
|
||||
}, to: &text, entities: &entities)
|
||||
} else {
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRestrictedName(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageRestrictedNameUsername(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRestrictedName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageRestrictedNameUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in
|
||||
var result: [MessageTextEntityType] = []
|
||||
if index == 0 {
|
||||
result.append(.TextMention(peerId: new.peer.id))
|
||||
|
|
@ -825,7 +825,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||
var entities: [MessageTextEntity] = []
|
||||
|
||||
if case .member = prev.participant, case .creator = new.participant {
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageTransferedName(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageTransferedNameUsername(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageTransferedName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageTransferedNameUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in
|
||||
var result: [MessageTextEntityType] = []
|
||||
if index == 0 {
|
||||
result.append(.TextMention(peerId: new.peer.id))
|
||||
|
|
@ -839,7 +839,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||
|
||||
if case let .creator(_, prevAdminInfo, prevRank) = prev.participant, case let .creator(_, newAdminInfo, newRank) = new.participant, (prevRank != newRank || prevAdminInfo?.rights.rights.contains(.canBeAnonymous) != newAdminInfo?.rights.rights.contains(.canBeAnonymous)) {
|
||||
if prevRank != newRank {
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRankNameNew(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), newRank ?? "") : self.presentationData.strings.Channel_AdminLog_MessageRankUsernameNew(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!, newRank ?? ""), generateEntities: { index in
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRankNameNew(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), newRank ?? "") : self.presentationData.strings.Channel_AdminLog_MessageRankUsernameNew(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!, newRank ?? ""), generateEntities: { index in
|
||||
var result: [MessageTextEntityType] = []
|
||||
if index == 0 {
|
||||
result.append(.TextMention(peerId: new.peer.id))
|
||||
|
|
@ -867,7 +867,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||
if prevAdminInfo?.rights.rights.contains(flag) != newAdminInfo?.rights.rights.contains(flag) {
|
||||
if !appendedRightsHeader {
|
||||
appendedRightsHeader = true
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessagePromotedName(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessagePromotedNameUsername(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessagePromotedName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessagePromotedNameUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in
|
||||
var result: [MessageTextEntityType] = []
|
||||
if index == 0 {
|
||||
result.append(.TextMention(peerId: new.peer.id))
|
||||
|
|
@ -940,7 +940,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||
if !appendedRightsHeader {
|
||||
appendedRightsHeader = true
|
||||
if prevAdminRights == nil {
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageAddedAdminName(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageAddedAdminNameUsername(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageAddedAdminName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageAddedAdminNameUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in
|
||||
var result: [MessageTextEntityType] = []
|
||||
if index == 0 {
|
||||
result.append(.TextMention(peerId: new.peer.id))
|
||||
|
|
@ -952,7 +952,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||
return result
|
||||
}, to: &text, entities: &entities)
|
||||
} else {
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRemovedAdminName(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageRemovedAdminNameUsername(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRemovedAdminName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageRemovedAdminNameUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in
|
||||
var result: [MessageTextEntityType] = []
|
||||
if index == 0 {
|
||||
result.append(.TextMention(peerId: new.peer.id))
|
||||
|
|
@ -970,7 +970,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||
if !prevFlags.isEmpty && newFlags.isEmpty {
|
||||
if !appendedRightsHeader {
|
||||
appendedRightsHeader = true
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRemovedAdminName(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageRemovedAdminNameUsername(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRemovedAdminName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageRemovedAdminNameUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in
|
||||
var result: [MessageTextEntityType] = []
|
||||
if index == 0 {
|
||||
result.append(.TextMention(peerId: new.peer.id))
|
||||
|
|
@ -987,7 +987,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||
if prevFlags.contains(flag) != newFlags.contains(flag) {
|
||||
if !appendedRightsHeader {
|
||||
appendedRightsHeader = true
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessagePromotedName(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessagePromotedNameUsername(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessagePromotedName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessagePromotedNameUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in
|
||||
var result: [MessageTextEntityType] = []
|
||||
if index == 0 {
|
||||
result.append(.TextMention(peerId: new.peer.id))
|
||||
|
|
@ -1023,7 +1023,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||
return result
|
||||
}, to: &text, entities: &entities)
|
||||
} else {
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRankNameNew(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), newRank ?? "") : self.presentationData.strings.Channel_AdminLog_MessageRankUsernameNew(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!, newRank ?? ""), generateEntities: { index in
|
||||
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRankNameNew(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), newRank ?? "") : self.presentationData.strings.Channel_AdminLog_MessageRankUsernameNew(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!, newRank ?? ""), generateEntities: { index in
|
||||
var result: [MessageTextEntityType] = []
|
||||
if index == 0 {
|
||||
result.append(.TextMention(peerId: new.peer.id))
|
||||
|
|
@ -2272,7 +2272,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||
for (_, peer) in new.peers {
|
||||
peers[peer.id] = peer
|
||||
}
|
||||
peers[new.peer.id] = new.peer
|
||||
peers[new.peer.id] = new.peer._asPeer()
|
||||
|
||||
var text: String = ""
|
||||
var entities: [MessageTextEntity] = []
|
||||
|
|
|
|||
|
|
@ -2293,7 +2293,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||
if let peerInfoController = context.sharedContext.makePeerInfoController(
|
||||
context: context,
|
||||
updatedPresentationData: nil,
|
||||
peer: peer._asPeer(),
|
||||
peer: peer,
|
||||
mode: .generic,
|
||||
avatarInitiallyExpanded: false,
|
||||
fromChat: false,
|
||||
|
|
|
|||
|
|
@ -583,7 +583,7 @@ final class NewContactScreenComponent: Component {
|
|||
return
|
||||
}
|
||||
if case let .peer(peer, _) = self.resolvedPeer {
|
||||
if let infoController = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let infoController = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let navigationController = component.context.sharedContext.mainWindow?.viewController as? NavigationController {
|
||||
navigationController.pushViewController(infoController)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -927,7 +927,7 @@ final class GiftOptionsScreenComponent: Component {
|
|||
if let controller = context.sharedContext.makePeerInfoController(
|
||||
context: context,
|
||||
updatedPresentationData: nil,
|
||||
peer: peer._asPeer(),
|
||||
peer: peer,
|
||||
mode: .gifts,
|
||||
avatarInitiallyExpanded: false,
|
||||
fromChat: false,
|
||||
|
|
|
|||
|
|
@ -410,7 +410,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
if let profileController = context.sharedContext.makePeerInfoController(
|
||||
context: context,
|
||||
updatedPresentationData: nil,
|
||||
peer: peer._asPeer(),
|
||||
peer: peer,
|
||||
mode: peer.id == context.account.peerId ? .myProfileGifts : .gifts,
|
||||
avatarInitiallyExpanded: false,
|
||||
fromChat: false,
|
||||
|
|
@ -558,7 +558,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
if let controller = self.context.sharedContext.makePeerInfoController(
|
||||
context: self.context,
|
||||
updatedPresentationData: nil,
|
||||
peer: peer._asPeer(),
|
||||
peer: peer,
|
||||
mode: giftsPeerId == self.context.account.peerId ? .myProfileGifts : .gifts,
|
||||
avatarInitiallyExpanded: false,
|
||||
fromChat: false,
|
||||
|
|
@ -2249,7 +2249,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
if let controller = context.sharedContext.makePeerInfoController(
|
||||
context: context,
|
||||
updatedPresentationData: nil,
|
||||
peer: peer._asPeer(),
|
||||
peer: peer,
|
||||
mode: .upgradableGifts,
|
||||
avatarInitiallyExpanded: false,
|
||||
fromChat: false,
|
||||
|
|
|
|||
|
|
@ -317,7 +317,7 @@ private final class JoinSubjectScreenComponent: Component {
|
|||
if let peerInfoController = context.sharedContext.makePeerInfoController(
|
||||
context: context,
|
||||
updatedPresentationData: nil,
|
||||
peer: peer._asPeer(),
|
||||
peer: peer,
|
||||
mode: .generic,
|
||||
avatarInitiallyExpanded: false,
|
||||
fromChat: false,
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ final class MiniAppListScreenComponent: Component {
|
|||
return
|
||||
}
|
||||
|
||||
if let peerInfoScreen = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let peerInfoScreen = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
peerInfoScreen.navigationPresentation = .modal
|
||||
controller.push(peerInfoScreen)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -875,7 +875,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
guard let infoController = component.context.sharedContext.makePeerInfoController(
|
||||
context: component.context,
|
||||
updatedPresentationData: nil,
|
||||
peer: component.sourcePeer._asPeer(),
|
||||
peer: component.sourcePeer,
|
||||
mode: .generic,
|
||||
avatarInitiallyExpanded: false,
|
||||
fromChat: false,
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ private final class PeerInfoScreenMemberItemNode: PeerInfoScreenItemNode {
|
|||
break
|
||||
}
|
||||
|
||||
let actions = availableActionsForMemberOfPeer(accountPeerId: item.context.accountPeerId, peer: item.enclosingPeer, member: item.member)
|
||||
let actions = availableActionsForMemberOfPeer(accountPeerId: item.context.accountPeerId, peer: item.enclosingPeer.flatMap(EnginePeer.init), member: item.member)
|
||||
|
||||
var options: [ItemListPeerItemRevealOption] = []
|
||||
if actions.contains(.promote) && item.enclosingPeer is TelegramChannel {
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ private enum PeerMembersListEntry: Comparable, Identifiable {
|
|||
break
|
||||
}
|
||||
|
||||
let actions = availableActionsForMemberOfPeer(accountPeerId: context.account.peerId, peer: enclosingPeer, member: member)
|
||||
let actions = availableActionsForMemberOfPeer(accountPeerId: context.account.peerId, peer: EnginePeer(enclosingPeer), member: member)
|
||||
|
||||
var options: [ItemListPeerItemRevealOption] = []
|
||||
if actions.contains(.promote) && enclosingPeer is TelegramChannel {
|
||||
|
|
|
|||
|
|
@ -383,7 +383,7 @@ final class PeerInfoPersonalChannelData: Equatable {
|
|||
}
|
||||
|
||||
final class PeerInfoScreenData {
|
||||
let peer: Peer?
|
||||
let peer: EnginePeer?
|
||||
let chatPeer: Peer?
|
||||
let savedMessagesPeer: Peer?
|
||||
let cachedData: CachedPeerData?
|
||||
|
|
@ -439,7 +439,7 @@ final class PeerInfoScreenData {
|
|||
}
|
||||
|
||||
init(
|
||||
peer: Peer?,
|
||||
peer: EnginePeer?,
|
||||
chatPeer: Peer?,
|
||||
savedMessagesPeer: Peer?,
|
||||
cachedData: CachedPeerData?,
|
||||
|
|
@ -1024,7 +1024,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
|
|||
)
|
||||
|
||||
return PeerInfoScreenData(
|
||||
peer: peer,
|
||||
peer: peer.flatMap(EnginePeer.init),
|
||||
chatPeer: peer,
|
||||
savedMessagesPeer: nil,
|
||||
cachedData: peerView.cachedData,
|
||||
|
|
@ -1617,7 +1617,7 @@ func peerInfoScreenData(
|
|||
}
|
||||
|
||||
return PeerInfoScreenData(
|
||||
peer: peer,
|
||||
peer: peer.flatMap(EnginePeer.init),
|
||||
chatPeer: peerView.peers[peerId],
|
||||
savedMessagesPeer: savedMessagesPeer?._asPeer(),
|
||||
cachedData: peerView.cachedData,
|
||||
|
|
@ -1864,7 +1864,7 @@ func peerInfoScreenData(
|
|||
}
|
||||
|
||||
return PeerInfoScreenData(
|
||||
peer: peerView.peers[peerId],
|
||||
peer: peerView.peers[peerId].flatMap(EnginePeer.init),
|
||||
chatPeer: peerView.peers[peerId],
|
||||
savedMessagesPeer: nil,
|
||||
cachedData: peerView.cachedData,
|
||||
|
|
@ -2202,7 +2202,7 @@ func peerInfoScreenData(
|
|||
let appConfiguration: AppConfiguration = preferencesView?.get(AppConfiguration.self) ?? .defaultValue
|
||||
|
||||
return .single(PeerInfoScreenData(
|
||||
peer: peerView.peers[groupId],
|
||||
peer: peerView.peers[groupId].flatMap(EnginePeer.init),
|
||||
chatPeer: peerView.peers[groupId],
|
||||
savedMessagesPeer: nil,
|
||||
cachedData: peerView.cachedData,
|
||||
|
|
@ -2256,19 +2256,19 @@ func peerInfoIsCopyProtected(data: PeerInfoScreenData) -> Bool {
|
|||
var isCopyProtected = false
|
||||
if let cachedUserData = data.cachedData as? CachedUserData, cachedUserData.flags.contains(.copyProtectionEnabled) || cachedUserData.flags.contains(.myCopyProtectionEnabled) {
|
||||
isCopyProtected = true
|
||||
} else if let peer = data.peer, peer.isCopyProtectionEnabled {
|
||||
} else if let peer = data.peer, peer._asPeer().isCopyProtectionEnabled {
|
||||
isCopyProtected = true
|
||||
}
|
||||
return isCopyProtected
|
||||
}
|
||||
|
||||
func canEditPeerInfo(context: AccountContext, peer: Peer?, chatLocation: ChatLocation, threadData: MessageHistoryThreadData?) -> Bool {
|
||||
func canEditPeerInfo(context: AccountContext, peer: EnginePeer?, chatLocation: ChatLocation, threadData: MessageHistoryThreadData?) -> Bool {
|
||||
if context.account.peerId == peer?.id {
|
||||
return true
|
||||
}
|
||||
if let user = peer as? TelegramUser, let botInfo = user.botInfo {
|
||||
if case let .user(user) = peer, let botInfo = user.botInfo {
|
||||
return botInfo.flags.contains(.canEdit)
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
} else if case let .channel(channel) = peer {
|
||||
if let threadData = threadData {
|
||||
if chatLocation.threadId == 1 {
|
||||
return false
|
||||
|
|
@ -2284,7 +2284,7 @@ func canEditPeerInfo(context: AccountContext, peer: Peer?, chatLocation: ChatLoc
|
|||
return true
|
||||
}
|
||||
}
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
} else if case let .legacyGroup(group) = peer {
|
||||
switch group.role {
|
||||
case .admin, .creator:
|
||||
return true
|
||||
|
|
@ -2311,23 +2311,23 @@ struct PeerInfoMemberActions: OptionSet {
|
|||
static let editRank = PeerInfoMemberActions(rawValue: 1 << 3)
|
||||
}
|
||||
|
||||
func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer?, member: PeerInfoMember) -> PeerInfoMemberActions {
|
||||
func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: EnginePeer?, member: PeerInfoMember) -> PeerInfoMemberActions {
|
||||
var result: PeerInfoMemberActions = []
|
||||
|
||||
|
||||
if peer == nil {
|
||||
result.insert(.logout)
|
||||
} else if member.id == accountPeerId {
|
||||
if let channel = peer as? TelegramChannel {
|
||||
if case let .channel(channel) = peer {
|
||||
if channel.hasPermission(.editRank) {
|
||||
result.insert(.editRank)
|
||||
}
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
} else if case let .legacyGroup(group) = peer {
|
||||
if !group.hasBannedPermission(.banEditRank) {
|
||||
result.insert(.editRank)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let channel = peer as? TelegramChannel {
|
||||
if case let .channel(channel) = peer {
|
||||
if channel.flags.contains(.isCreator) {
|
||||
if !channel.flags.contains(.isGigagroup) {
|
||||
result.insert(.restrict)
|
||||
|
|
@ -2371,7 +2371,7 @@ func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer?, member:
|
|||
break
|
||||
}
|
||||
}
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
} else if case let .legacyGroup(group) = peer {
|
||||
switch group.role {
|
||||
case .creator:
|
||||
result.insert(.restrict)
|
||||
|
|
@ -2431,9 +2431,9 @@ func peerInfoHeaderButtonIsHiddenWhileExpanded(buttonKey: PeerInfoHeaderButtonKe
|
|||
return hiddenWhileExpanded
|
||||
}
|
||||
|
||||
func peerInfoHeaderActionButtons(peer: Peer?, isSecretChat: Bool, isContact: Bool) -> [PeerInfoHeaderButtonKey] {
|
||||
func peerInfoHeaderActionButtons(peer: EnginePeer?, isSecretChat: Bool, isContact: Bool) -> [PeerInfoHeaderButtonKey] {
|
||||
var result: [PeerInfoHeaderButtonKey] = []
|
||||
if !isContact && !isSecretChat, let user = peer as? TelegramUser, user.botInfo == nil {
|
||||
if !isContact && !isSecretChat, case let .user(user) = peer, user.botInfo == nil {
|
||||
result = [.message, .addContact]
|
||||
}
|
||||
|
||||
|
|
@ -2444,9 +2444,9 @@ func peerInfoHeaderActionButtons(peer: Peer?, isSecretChat: Bool, isContact: Boo
|
|||
return result
|
||||
}
|
||||
|
||||
func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFromChat: Bool, isExpanded: Bool, videoCallsEnabled: Bool, isSecretChat: Bool, isContact: Bool, threadInfo: EngineMessageHistoryThread.Info?) -> [PeerInfoHeaderButtonKey] {
|
||||
func peerInfoHeaderButtons(peer: EnginePeer?, cachedData: CachedPeerData?, isOpenedFromChat: Bool, isExpanded: Bool, videoCallsEnabled: Bool, isSecretChat: Bool, isContact: Bool, threadInfo: EngineMessageHistoryThread.Info?) -> [PeerInfoHeaderButtonKey] {
|
||||
var result: [PeerInfoHeaderButtonKey] = []
|
||||
if let user = peer as? TelegramUser {
|
||||
if case let .user(user) = peer {
|
||||
if !isOpenedFromChat {
|
||||
result.append(.message)
|
||||
}
|
||||
|
|
@ -2480,7 +2480,7 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
|
|||
} else {
|
||||
result.append(.more)
|
||||
}
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
} else if case let .channel(channel) = peer {
|
||||
if let _ = threadInfo {
|
||||
result.append(.mute)
|
||||
result.append(.search)
|
||||
|
|
@ -2555,7 +2555,7 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
} else if case let .legacyGroup(group) = peer {
|
||||
let hasVoiceChat = group.flags.contains(.hasVoiceChat)
|
||||
let canStartVoiceChat: Bool
|
||||
|
||||
|
|
@ -2582,8 +2582,8 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
|
|||
return result
|
||||
}
|
||||
|
||||
func peerInfoCanEdit(peer: Peer?, chatLocation: ChatLocation, threadData: MessageHistoryThreadData?, cachedData: CachedPeerData?, isContact: Bool?) -> Bool {
|
||||
if let user = peer as? TelegramUser {
|
||||
func peerInfoCanEdit(peer: EnginePeer?, chatLocation: ChatLocation, threadData: MessageHistoryThreadData?, cachedData: CachedPeerData?, isContact: Bool?) -> Bool {
|
||||
if case let .user(user) = peer {
|
||||
if user.isDeleted {
|
||||
return false
|
||||
}
|
||||
|
|
@ -2594,7 +2594,7 @@ func peerInfoCanEdit(peer: Peer?, chatLocation: ChatLocation, threadData: Messag
|
|||
return false
|
||||
}
|
||||
return true
|
||||
} else if let peer = peer as? TelegramChannel {
|
||||
} else if case let .channel(peer) = peer {
|
||||
if peer.isForumOrMonoForum, let threadData = threadData {
|
||||
if peer.flags.contains(.isCreator) {
|
||||
return true
|
||||
|
|
@ -2615,7 +2615,7 @@ func peerInfoCanEdit(peer: Peer?, chatLocation: ChatLocation, threadData: Messag
|
|||
}
|
||||
return false
|
||||
}
|
||||
} else if let peer = peer as? TelegramGroup {
|
||||
} else if case let .legacyGroup(peer) = peer {
|
||||
if case .creator = peer.role {
|
||||
return true
|
||||
} else if case let .admin(rights, _) = peer.role {
|
||||
|
|
@ -2630,19 +2630,19 @@ func peerInfoCanEdit(peer: Peer?, chatLocation: ChatLocation, threadData: Messag
|
|||
return false
|
||||
}
|
||||
|
||||
func peerInfoIsChatMuted(peer: Peer?, peerNotificationSettings: TelegramPeerNotificationSettings?, threadNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?) -> Bool {
|
||||
func isPeerMuted(peer: Peer?, peerNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?) -> Bool {
|
||||
func peerInfoIsChatMuted(peer: EnginePeer?, peerNotificationSettings: TelegramPeerNotificationSettings?, threadNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?) -> Bool {
|
||||
func isPeerMuted(peer: EnginePeer?, peerNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?) -> Bool {
|
||||
var peerIsMuted = false
|
||||
if let peerNotificationSettings {
|
||||
if case .muted = peerNotificationSettings.muteState {
|
||||
peerIsMuted = true
|
||||
} else if case .default = peerNotificationSettings.muteState, let globalNotificationSettings {
|
||||
if let peer {
|
||||
if peer is TelegramUser {
|
||||
if case .user = peer {
|
||||
peerIsMuted = !globalNotificationSettings.privateChats.enabled
|
||||
} else if peer is TelegramGroup {
|
||||
} else if case .legacyGroup = peer {
|
||||
peerIsMuted = !globalNotificationSettings.groupChats.enabled
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
} else if case let .channel(channel) = peer {
|
||||
switch channel.info {
|
||||
case .group:
|
||||
peerIsMuted = !globalNotificationSettings.groupChats.enabled
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
|
|||
return
|
||||
}
|
||||
|
||||
let canEdit = canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData)
|
||||
let canEdit = canEditPeerInfo(context: self.context, peer: EnginePeer(peer), chatLocation: chatLocation, threadData: threadData)
|
||||
|
||||
let previousItem = self.item
|
||||
var item = item
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ final class PeerInfoEditingAvatarOverlayNode: ASDisplayNode {
|
|||
isPersonal = true
|
||||
}
|
||||
|
||||
if canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData)
|
||||
if canEditPeerInfo(context: self.context, peer: EnginePeer(peer), chatLocation: chatLocation, threadData: threadData)
|
||||
|| isPersonal
|
||||
|| self.currentRepresentation != nil && updatingAvatar == nil {
|
||||
var overlayHidden = true
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
|
|||
|
||||
var contentHeight: CGFloat = statusBarHeight + 10.0 + avatarSize + 28.0
|
||||
|
||||
if canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData) {
|
||||
if canEditPeerInfo(context: self.context, peer: peer.flatMap(EnginePeer.init), chatLocation: chatLocation, threadData: threadData) {
|
||||
if self.avatarButtonNode.supernode == nil {
|
||||
self.addSubnode(self.avatarButtonNode)
|
||||
}
|
||||
|
|
@ -85,12 +85,12 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
|
|||
}
|
||||
} else if let _ = peer as? TelegramGroup {
|
||||
fieldKeys.append(.title)
|
||||
if canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData) {
|
||||
if canEditPeerInfo(context: self.context, peer: peer.flatMap(EnginePeer.init), chatLocation: chatLocation, threadData: threadData) {
|
||||
fieldKeys.append(.description)
|
||||
}
|
||||
} else if let _ = peer as? TelegramChannel {
|
||||
fieldKeys.append(.title)
|
||||
if canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData) {
|
||||
if canEditPeerInfo(context: self.context, peer: peer.flatMap(EnginePeer.init), chatLocation: chatLocation, threadData: threadData) {
|
||||
fieldKeys.append(.description)
|
||||
}
|
||||
}
|
||||
|
|
@ -156,10 +156,10 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
|
|||
} else {
|
||||
placeholder = presentationData.strings.GroupInfo_GroupNamePlaceholder
|
||||
}
|
||||
isEnabled = canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData)
|
||||
isEnabled = canEditPeerInfo(context: self.context, peer: peer.flatMap(EnginePeer.init), chatLocation: chatLocation, threadData: threadData)
|
||||
case .description:
|
||||
placeholder = presentationData.strings.Channel_Edit_AboutItem
|
||||
isEnabled = canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData) || isEditableBot
|
||||
isEnabled = canEditPeerInfo(context: self.context, peer: peer.flatMap(EnginePeer.init), chatLocation: chatLocation, threadData: threadData) || isEditableBot
|
||||
}
|
||||
let itemHeight = itemNode.update(width: width, safeInset: safeInset, isSettings: isSettings, hasPrevious: hasPrevious, hasNext: key != fieldKeys.last, placeholder: placeholder, isEnabled: isEnabled, presentationData: presentationData, updateText: updateText)
|
||||
transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: width, height: itemHeight)))
|
||||
|
|
|
|||
|
|
@ -545,8 +545,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
|
||||
let actionButtonKeys: [PeerInfoHeaderButtonKey] = (self.isSettings || self.isMyProfile) ? [] : peerInfoHeaderActionButtons(peer: peer, isSecretChat: isSecretChat, isContact: isContact)
|
||||
let buttonKeys: [PeerInfoHeaderButtonKey] = (self.isSettings || self.isMyProfile) ? [] : peerInfoHeaderButtons(peer: peer, cachedData: cachedData, isOpenedFromChat: self.isOpenedFromChat, isExpanded: true, videoCallsEnabled: width > 320.0 && self.videoCallsEnabled, isSecretChat: isSecretChat, isContact: isContact, threadInfo: threadData?.info)
|
||||
let actionButtonKeys: [PeerInfoHeaderButtonKey] = (self.isSettings || self.isMyProfile) ? [] : peerInfoHeaderActionButtons(peer: peer.flatMap(EnginePeer.init), isSecretChat: isSecretChat, isContact: isContact)
|
||||
let buttonKeys: [PeerInfoHeaderButtonKey] = (self.isSettings || self.isMyProfile) ? [] : peerInfoHeaderButtons(peer: peer.flatMap(EnginePeer.init), cachedData: cachedData, isOpenedFromChat: self.isOpenedFromChat, isExpanded: true, videoCallsEnabled: width > 320.0 && self.videoCallsEnabled, isSecretChat: isSecretChat, isContact: isContact, threadInfo: threadData?.info)
|
||||
|
||||
let backgroundCoverSubject: PeerInfoCoverComponent.Subject?
|
||||
var backgroundCoverAnimateIn = false
|
||||
|
|
@ -2358,7 +2358,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
}
|
||||
buttonIcon = .voiceChat
|
||||
case .mute:
|
||||
let chatIsMuted = peerInfoIsChatMuted(peer: peer, peerNotificationSettings: peerNotificationSettings, threadNotificationSettings: threadNotificationSettings, globalNotificationSettings: globalNotificationSettings)
|
||||
let chatIsMuted = peerInfoIsChatMuted(peer: peer.flatMap(EnginePeer.init), peerNotificationSettings: peerNotificationSettings, threadNotificationSettings: threadNotificationSettings, globalNotificationSettings: globalNotificationSettings)
|
||||
if chatIsMuted {
|
||||
buttonText = presentationData.strings.PeerInfo_ButtonUnmute
|
||||
buttonIcon = .unmute
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ enum PeerInfoMember: Equatable {
|
|||
var peer: Peer {
|
||||
switch self {
|
||||
case let .channelMember(participant, _):
|
||||
return participant.peer
|
||||
return participant.peer._asPeer()
|
||||
case let .legacyGroupMember(peer, _, _, _, _, _):
|
||||
return peer.peers[peer.peerId]!
|
||||
case let .account(peer):
|
||||
|
|
|
|||
|
|
@ -462,7 +462,7 @@ private final class PeerInfoPendingPane {
|
|||
if let cachedUserData = data.cachedData as? CachedUserData, cachedUserData.disallowedGifts == .All {
|
||||
canGift = false
|
||||
}
|
||||
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
if case let .channel(channel) = peer, case .broadcast = channel.info {
|
||||
if channel.hasPermission(.sendSomething) {
|
||||
canManage = true
|
||||
}
|
||||
|
|
@ -476,7 +476,7 @@ private final class PeerInfoPendingPane {
|
|||
if let peer = data.peer {
|
||||
if peer.id == context.account.peerId {
|
||||
canManage = true
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
} else if case let .channel(channel) = peer {
|
||||
if channel.hasPermission(.editStories) {
|
||||
canManage = true
|
||||
}
|
||||
|
|
@ -493,7 +493,7 @@ private final class PeerInfoPendingPane {
|
|||
scope = .botPreview(id: peerId)
|
||||
|
||||
if let peer = data.peer {
|
||||
if let user = peer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
|
||||
if case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
|
||||
canManage = true
|
||||
}
|
||||
}
|
||||
|
|
@ -1249,7 +1249,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
|
|||
if let peer = data?.peer {
|
||||
if peer.id == self.context.account.peerId {
|
||||
canManageTabs = true
|
||||
} else if let channel = data?.peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
} else if case let .channel(channel) = data?.peer, case .broadcast = channel.info {
|
||||
if channel.hasPermission(.changeInfo) {
|
||||
canManageTabs = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ func infoItems(
|
|||
interaction.openBirthdayContextMenu(node, gesture)
|
||||
}
|
||||
|
||||
if let user = data.peer as? TelegramUser {
|
||||
if case let .user(user) = data.peer {
|
||||
let ItemCallList = 1000
|
||||
let ItemPersonalChannelHeader = 2000
|
||||
let ItemPersonalChannel = 2001
|
||||
|
|
@ -529,7 +529,7 @@ func infoItems(
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if let channel = data.peer as? TelegramChannel {
|
||||
} else if case let .channel(channel) = data.peer {
|
||||
let ItemUsername = 1
|
||||
let ItemUsernameInfo = 2
|
||||
let ItemAbout = 3
|
||||
|
|
@ -795,7 +795,7 @@ func infoItems(
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if let group = data.peer as? TelegramGroup {
|
||||
} else if case let .legacyGroup(group) = data.peer {
|
||||
if let cachedData = data.cachedData as? CachedGroupData {
|
||||
let aboutText: String?
|
||||
if group.isFake {
|
||||
|
|
@ -820,7 +820,7 @@ func infoItems(
|
|||
|
||||
if let peer = data.peer, let members = data.members, case let .shortList(_, memberList) = members {
|
||||
var canAddMembers = false
|
||||
if let group = data.peer as? TelegramGroup {
|
||||
if case let .legacyGroup(group) = data.peer {
|
||||
switch group.role {
|
||||
case .admin, .creator:
|
||||
canAddMembers = true
|
||||
|
|
@ -830,7 +830,7 @@ func infoItems(
|
|||
if !group.hasBannedPermission(.banAddMembers) {
|
||||
canAddMembers = true
|
||||
}
|
||||
} else if let channel = data.peer as? TelegramChannel {
|
||||
} else if case let .channel(channel) = data.peer {
|
||||
switch channel.info {
|
||||
case .broadcast:
|
||||
break
|
||||
|
|
@ -849,7 +849,7 @@ func infoItems(
|
|||
|
||||
for member in memberList {
|
||||
let isAccountPeer = member.id == context.account.peerId
|
||||
items[.peerMembers]!.append(PeerInfoScreenMemberItem(id: member.id, context: .account(context), enclosingPeer: peer, member: member, isAccount: false, action: isAccountPeer ? { _ in
|
||||
items[.peerMembers]!.append(PeerInfoScreenMemberItem(id: member.id, context: .account(context), enclosingPeer: peer._asPeer(), member: member, isAccount: false, action: isAccountPeer ? { _ in
|
||||
let actions = availableActionsForMemberOfPeer(accountPeerId: context.account.peerId, peer: peer, member: member)
|
||||
if actions.contains(.editRank) {
|
||||
interaction.performMemberAction(member, .editRank)
|
||||
|
|
@ -903,7 +903,7 @@ func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, s
|
|||
}
|
||||
|
||||
if let data = data {
|
||||
if let user = data.peer as? TelegramUser {
|
||||
if case let .user(user) = data.peer {
|
||||
let ItemNote: AnyHashable = AnyHashable("note_edit")
|
||||
let ItemNoteInfo = 1
|
||||
|
||||
|
|
@ -1047,7 +1047,7 @@ func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, s
|
|||
interaction.requestDeleteContact()
|
||||
}))
|
||||
}
|
||||
} else if let channel = data.peer as? TelegramChannel {
|
||||
} else if case let .channel(channel) = data.peer {
|
||||
switch channel.info {
|
||||
case .broadcast:
|
||||
let ItemUsername = 1
|
||||
|
|
@ -1591,7 +1591,7 @@ func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, s
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if let group = data.peer as? TelegramGroup {
|
||||
} else if case let .legacyGroup(group) = data.peer {
|
||||
let ItemUsername = 101
|
||||
let ItemInviteLinks = 102
|
||||
let ItemPreHistory = 103
|
||||
|
|
|
|||
|
|
@ -760,14 +760,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, _ in
|
||||
c?.dismiss(completion: {
|
||||
if let strongSelf = self, let currentPeer = strongSelf.data?.peer, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
|
||||
if let channel = currentPeer as? TelegramChannel, channel.isForumOrMonoForum, let threadId = message.threadId {
|
||||
if case let .channel(channel) = currentPeer, channel.isForumOrMonoForum, let threadId = message.threadId {
|
||||
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: currentPeer.id, threadId: threadId, messageId: message.id, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .default, animated: true).startStandalone()
|
||||
} else {
|
||||
let targetLocation: NavigateToChatControllerParams.Location
|
||||
if case let .replyThread(message) = strongSelf.chatLocation {
|
||||
targetLocation = .replyThread(message)
|
||||
} else {
|
||||
targetLocation = .peer(EnginePeer(currentPeer))
|
||||
targetLocation = .peer(currentPeer)
|
||||
}
|
||||
|
||||
let currentPeerId = strongSelf.peerId
|
||||
|
|
@ -927,14 +927,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, f in
|
||||
c?.dismiss(completion: {
|
||||
if let strongSelf = self, let currentPeer = strongSelf.data?.peer, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
|
||||
if let channel = currentPeer as? TelegramChannel, channel.isForumOrMonoForum, let threadId = message.threadId {
|
||||
if case let .channel(channel) = currentPeer, channel.isForumOrMonoForum, let threadId = message.threadId {
|
||||
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: currentPeer.id, threadId: threadId, messageId: message.id, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .default, animated: true).startStandalone()
|
||||
} else {
|
||||
let targetLocation: NavigateToChatControllerParams.Location
|
||||
if case let .replyThread(message) = strongSelf.chatLocation {
|
||||
targetLocation = .replyThread(message)
|
||||
} else {
|
||||
targetLocation = .peer(EnginePeer(currentPeer))
|
||||
targetLocation = .peer(currentPeer)
|
||||
}
|
||||
|
||||
let currentPeerId = strongSelf.peerId
|
||||
|
|
@ -1558,7 +1558,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
return
|
||||
}
|
||||
|
||||
strongSelf.openAvatarGallery(peer: EnginePeer(peer), entries: entries, centralEntry: centralEntry, animateTransition: true)
|
||||
strongSelf.openAvatarGallery(peer: peer, entries: entries, centralEntry: centralEntry, animateTransition: true)
|
||||
|
||||
Queue.mainQueue().after(0.4) {
|
||||
strongSelf.resetHeaderExpansion()
|
||||
|
|
@ -1656,7 +1656,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil, nil)
|
||||
return
|
||||
}
|
||||
if let peer = data.peer as? TelegramUser {
|
||||
if case let .user(peer) = data.peer {
|
||||
if strongSelf.isSettings || strongSelf.isMyProfile, let cachedData = data.cachedData as? CachedUserData {
|
||||
let firstName = strongSelf.headerNode.editingContentNode.editingTextForKey(.firstName) ?? ""
|
||||
let lastName = strongSelf.headerNode.editingContentNode.editingTextForKey(.lastName) ?? ""
|
||||
|
|
@ -1902,7 +1902,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
} else {
|
||||
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil, nil)
|
||||
}
|
||||
} else if let group = data.peer as? TelegramGroup, canEditPeerInfo(context: strongSelf.context, peer: group, chatLocation: chatLocation, threadData: data.threadData) {
|
||||
} else if case let .legacyGroup(group) = data.peer, canEditPeerInfo(context: strongSelf.context, peer: data.peer, chatLocation: chatLocation, threadData: data.threadData) {
|
||||
let title = strongSelf.headerNode.editingContentNode.editingTextForKey(.title) ?? ""
|
||||
let description = strongSelf.headerNode.editingContentNode.editingTextForKey(.description) ?? ""
|
||||
|
||||
|
|
@ -1958,7 +1958,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil, nil)
|
||||
}))
|
||||
}
|
||||
} else if let channel = data.peer as? TelegramChannel, canEditPeerInfo(context: strongSelf.context, peer: channel, chatLocation: strongSelf.chatLocation, threadData: data.threadData) {
|
||||
} else if case let .channel(channel) = data.peer, canEditPeerInfo(context: strongSelf.context, peer: data.peer, chatLocation: strongSelf.chatLocation, threadData: data.threadData) {
|
||||
let title = strongSelf.headerNode.editingContentNode.editingTextForKey(.title) ?? ""
|
||||
let description = strongSelf.headerNode.editingContentNode.editingTextForKey(.description) ?? ""
|
||||
|
||||
|
|
@ -2129,7 +2129,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
|
||||
|
||||
self.headerNode.displayCopyContextMenu = { [weak self] node, copyPhone, copyUsername in
|
||||
guard let strongSelf = self, let data = strongSelf.data, let user = data.peer as? TelegramUser else {
|
||||
guard let strongSelf = self, let data = strongSelf.data, case let .user(user) = data.peer else {
|
||||
return
|
||||
}
|
||||
var actions: [ContextMenuAction] = []
|
||||
|
|
@ -2350,7 +2350,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}))
|
||||
]
|
||||
|
||||
let galleryController = AvatarGalleryController(context: strongSelf.context, peer: EnginePeer(peer), remoteEntries: nil, replaceRootController: { controller, ready in
|
||||
let galleryController = AvatarGalleryController(context: strongSelf.context, peer: peer, remoteEntries: nil, replaceRootController: { controller, ready in
|
||||
}, synchronousLoad: true)
|
||||
galleryController.setHintWillBePresentedInPreviewingContext(true)
|
||||
|
||||
|
|
@ -2402,9 +2402,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
guard let self, let navigationController = self.controller?.navigationController as? NavigationController, let peer = self.data?.peer else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer))))
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer)))
|
||||
}
|
||||
|
||||
|
||||
if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peerId.namespace) {
|
||||
self.displayAsPeersPromise.set(context.engine.calls.cachedGroupCallDisplayAsAvailablePeers(peerId: peerId))
|
||||
}
|
||||
|
|
@ -2717,7 +2717,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}
|
||||
}
|
||||
|
||||
if let channel = data.peer as? TelegramChannel, channel.isForumOrMonoForum, self.chatLocation.threadId == nil {
|
||||
if case let .channel(channel) = data.peer, channel.isForumOrMonoForum, self.chatLocation.threadId == nil {
|
||||
if self.forumTopicNotificationExceptionsDisposable == nil {
|
||||
self.forumTopicNotificationExceptionsDisposable = (self.context.engine.peers.forumChannelTopicNotificationExceptions(id: channel.id)
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] list in
|
||||
|
|
@ -2766,10 +2766,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
|
||||
var previousPhotoIsPersonal: Bool?
|
||||
var currentPhotoIsPersonal: Bool?
|
||||
if let previousUser = previousData?.peer as? TelegramUser {
|
||||
if case let .user(previousUser) = previousData?.peer {
|
||||
previousPhotoIsPersonal = previousUser.profileImageRepresentations.first?.isPersonal == true
|
||||
}
|
||||
if let user = data.peer as? TelegramUser {
|
||||
if case let .user(user) = data.peer {
|
||||
currentPhotoIsPersonal = user.profileImageRepresentations.first?.isPersonal == true
|
||||
}
|
||||
|
||||
|
|
@ -3406,7 +3406,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
|> deliverOnMainQueue).startStrict(completed: { [weak self] in
|
||||
if let strongSelf = self, let peer = strongSelf.data?.peer {
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Conversation_DeletedFromContacts(EnginePeer(peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false })
|
||||
let controller = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Conversation_DeletedFromContacts(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false })
|
||||
controller.keepOnParentDismissal = true
|
||||
strongSelf.controller?.present(controller, in: .window(.root))
|
||||
|
||||
|
|
@ -3521,7 +3521,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
self.view.endEditing(true)
|
||||
|
||||
let statsController: ViewController
|
||||
if let channel = peer as? TelegramChannel, case .group = channel.info {
|
||||
if case let .channel(channel) = peer, case .group = channel.info {
|
||||
if case .monetization = section {
|
||||
statsController = channelStatsController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: peer.id, section: section, existingStarsRevenueContext: data.starsRevenueStatsContext, boostStatus: boostStatus)
|
||||
} else {
|
||||
|
|
@ -3534,7 +3534,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}
|
||||
|
||||
func openBoost() {
|
||||
guard let peer = self.data?.peer, let channel = peer as? TelegramChannel, let controller = self.controller else {
|
||||
guard case let .channel(channel) = self.data?.peer, let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -3621,7 +3621,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
guard let data = self.data, let peer = data.peer, let encryptionKeyFingerprint = data.encryptionKeyFingerprint else {
|
||||
return
|
||||
}
|
||||
self.controller?.push(SecretChatKeyController(context: self.context, fingerprint: encryptionKeyFingerprint, peer: EnginePeer(peer)))
|
||||
self.controller?.push(SecretChatKeyController(context: self.context, fingerprint: encryptionKeyFingerprint, peer: peer))
|
||||
}
|
||||
|
||||
func openShareLink(url: String) {
|
||||
|
|
@ -3744,7 +3744,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
let _ = enqueueMessages(account: strongSelf.context.account, peerId: peer.id, messages: [.message(text: text, attributes: [], inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).startStandalone()
|
||||
|
||||
if let peer = strongSelf.data?.peer, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer))))
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -3768,7 +3768,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}
|
||||
|
||||
private func editingOpenPublicLinkSetup() {
|
||||
if let peer = self.data?.peer as? TelegramUser, peer.botInfo != nil {
|
||||
if case let .user(peer) = self.data?.peer, peer.botInfo != nil {
|
||||
let controller = usernameSetupController(context: self.context, mode: .bot(self.peerId))
|
||||
self.controller?.push(controller)
|
||||
} else {
|
||||
|
|
@ -3788,7 +3788,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}
|
||||
|
||||
private func editingOpenAffiliateProgram() {
|
||||
if let peer = self.data?.peer as? TelegramUser, let botInfo = peer.botInfo {
|
||||
if case let .user(peer) = self.data?.peer, let botInfo = peer.botInfo {
|
||||
if botInfo.flags.contains(.canEdit) {
|
||||
let _ = (self.context.sharedContext.makeAffiliateProgramSetupScreenInitialData(context: self.context, peerId: peer.id, mode: .editProgram)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] initialData in
|
||||
|
|
@ -3978,7 +3978,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}
|
||||
if self.peerId == self.context.account.peerId {
|
||||
controller.push(UserAppearanceScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData))
|
||||
} else if let peer = self.data?.peer, peer is TelegramChannel {
|
||||
} else if case .channel = self.data?.peer {
|
||||
controller.push(ChannelAppearanceScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId, boostStatus: self.boostStatus))
|
||||
}
|
||||
}
|
||||
|
|
@ -4076,7 +4076,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
guard let data = self.data, let peer = data.peer else {
|
||||
return
|
||||
}
|
||||
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
if case let .channel(channel) = peer, case .broadcast = channel.info {
|
||||
let subscription = Promise<PeerAllowedReactionsScreen.Content>()
|
||||
subscription.set(PeerAllowedReactionsScreen.content(context: self.context, peerId: peer.id))
|
||||
let _ = (subscription.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak self] content in
|
||||
|
|
@ -4130,7 +4130,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
guard let data = self.data, let peer = data.peer else {
|
||||
return
|
||||
}
|
||||
if peer is TelegramGroup {
|
||||
if case .legacyGroup = peer {
|
||||
if isEnabled {
|
||||
let context = self.context
|
||||
let signal: Signal<EnginePeer.Id?, NoError> = self.context.engine.peers.convertGroupToSupergroup(peerId: self.peerId, additionalProcessing: { upgradedPeerId -> Signal<Never, NoError> in
|
||||
|
|
@ -4189,9 +4189,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
case .members:
|
||||
self.controller?.push(channelMembersController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId))
|
||||
case .admins:
|
||||
if peer is TelegramGroup {
|
||||
if case .legacyGroup = peer {
|
||||
self.controller?.push(channelAdminsController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId))
|
||||
} else if peer is TelegramChannel {
|
||||
} else if case .channel = peer {
|
||||
self.controller?.push(channelAdminsController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId))
|
||||
}
|
||||
case .banned:
|
||||
|
|
@ -4253,7 +4253,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
|
||||
let context = self.context
|
||||
let presentationData = self.presentationData
|
||||
let map = TelegramMediaMap(latitude: location.latitude, longitude: location.longitude, heading: nil, accuracyRadius: nil, venue: MapVenue(title: EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), address: location.address, provider: nil, id: nil, type: nil), liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil)
|
||||
let map = TelegramMediaMap(latitude: location.latitude, longitude: location.longitude, heading: nil, accuracyRadius: nil, venue: MapVenue(title: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), address: location.address, provider: nil, id: nil, type: nil), liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil)
|
||||
|
||||
let controllerParams = LocationViewParams(sendLiveLocation: { _ in
|
||||
}, stopLiveLocation: { _ in
|
||||
|
|
@ -4262,7 +4262,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}, openPeer: { _ in
|
||||
}, showAll: false)
|
||||
|
||||
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peer, text: "", attributes: [], media: [map], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peer._asPeer(), text: "", attributes: [], media: [map], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||
|
||||
let controller = LocationViewController(context: context, updatedPresentationData: self.controller?.updatedPresentationData, subject: EngineMessage(message), params: controllerParams)
|
||||
self.controller?.push(controller)
|
||||
|
|
@ -4303,7 +4303,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
|
||||
private func openPeerInfo(peer: Peer, isMember: Bool) {
|
||||
let mode: PeerInfoControllerMode = .generic
|
||||
if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer, mode: mode, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: EnginePeer(peer), mode: mode, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
(self.controller?.navigationController as? NavigationController)?.pushViewController(infoController)
|
||||
}
|
||||
}
|
||||
|
|
@ -4603,7 +4603,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
return
|
||||
}
|
||||
|
||||
presentAddMembersImpl(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, parentController: controller, groupPeer: groupPeer, selectAddMemberDisposable: self.selectAddMemberDisposable, addMemberDisposable: self.addMemberDisposable)
|
||||
presentAddMembersImpl(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, parentController: controller, groupPeer: groupPeer._asPeer(), selectAddMemberDisposable: self.selectAddMemberDisposable, addMemberDisposable: self.addMemberDisposable)
|
||||
}
|
||||
|
||||
func openQrCode() {
|
||||
|
|
@ -4707,7 +4707,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}
|
||||
self.postingAvailabilityDisposable?.dispose()
|
||||
|
||||
if let data = self.data, let user = data.peer as? TelegramUser, let botInfo = user.botInfo {
|
||||
if let data = self.data, case let .user(user) = data.peer, let botInfo = user.botInfo {
|
||||
if !botInfo.flags.contains(.canEdit) {
|
||||
return
|
||||
}
|
||||
|
|
@ -4826,7 +4826,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
return nil
|
||||
}
|
||||
|
||||
if let data = self.data, let user = data.peer as? TelegramUser, let _ = user.botInfo {
|
||||
if let data = self.data, case let .user(user) = data.peer, let _ = user.botInfo {
|
||||
if let pane = self.paneContainerNode.currentPane?.node as? PeerInfoStoryPaneNode, let transitionView = pane.extractPendingStoryTransitionView() {
|
||||
return StoryCameraTransitionOut(
|
||||
destinationView: transitionView,
|
||||
|
|
@ -5199,7 +5199,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
navigationController: navigationController,
|
||||
chatController: nil,
|
||||
context: strongSelf.context,
|
||||
chatLocation: .peer(EnginePeer(peer)),
|
||||
chatLocation: .peer(peer),
|
||||
subject: .message(id: .id(index.id), highlight: nil, timecode: nil, setupReply: false),
|
||||
botStart: nil,
|
||||
updateTextInputState: nil,
|
||||
|
|
@ -5295,7 +5295,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
guard let peer = self.data?.peer else {
|
||||
return
|
||||
}
|
||||
let alertController = textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: self.presentationData.strings.UserInfo_ResetToOriginalAlertText(EnginePeer(peer).compactDisplayTitle).string, actions: [
|
||||
let alertController = textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: self.presentationData.strings.UserInfo_ResetToOriginalAlertText(peer.compactDisplayTitle).string, actions: [
|
||||
TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {
|
||||
|
||||
}),
|
||||
|
|
@ -5396,7 +5396,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}
|
||||
let headerInset = sectionInset
|
||||
|
||||
let headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, profileGiftsContext: self.data?.profileGiftsContext, screenData: self.data, isSearching: self.searchDisplayController != nil, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: self.headerNode.navigationTransition == nil ? transition : .immediate, additive: additive, animateHeader: transition.isAnimated && self.headerNode.navigationTransition == nil)
|
||||
let headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer?._asPeer(), cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, profileGiftsContext: self.data?.profileGiftsContext, screenData: self.data, isSearching: self.searchDisplayController != nil, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: self.headerNode.navigationTransition == nil ? transition : .immediate, additive: additive, animateHeader: transition.isAnimated && self.headerNode.navigationTransition == nil)
|
||||
|
||||
let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: layout.size.width, height: layout.size.height))
|
||||
if additive {
|
||||
|
|
@ -5635,13 +5635,13 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
let type: PeerType
|
||||
if isBot {
|
||||
type = .bot
|
||||
} else if let user = peer as? TelegramUser {
|
||||
} else if case let .user(user) = peer {
|
||||
if user.botInfo != nil && !user.id.isVerificationCodes {
|
||||
type = .bot
|
||||
} else {
|
||||
type = .user
|
||||
}
|
||||
} else if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
} else if case let .channel(channel) = peer, case .broadcast = channel.info {
|
||||
type = .channel
|
||||
} else {
|
||||
type = .group
|
||||
|
|
@ -5802,7 +5802,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}
|
||||
let headerInset = sectionInset
|
||||
|
||||
let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, profileGiftsContext: self.data?.profileGiftsContext, screenData: self.data, isSearching: self.searchDisplayController != nil, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: self.headerNode.navigationTransition == nil ? transition : .immediate, additive: additive, animateHeader: animateHeader && self.headerNode.navigationTransition == nil)
|
||||
let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer?._asPeer(), cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, profileGiftsContext: self.data?.profileGiftsContext, screenData: self.data, isSearching: self.searchDisplayController != nil, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: self.headerNode.navigationTransition == nil ? transition : .immediate, additive: additive, animateHeader: animateHeader && self.headerNode.navigationTransition == nil)
|
||||
}
|
||||
|
||||
let paneAreaExpansionDistance: CGFloat = 32.0
|
||||
|
|
@ -5857,7 +5857,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
} else if peerInfoCanEdit(peer: self.data?.peer, chatLocation: self.chatLocation, threadData: self.data?.threadData, cachedData: self.data?.cachedData, isContact: self.data?.isContact) {
|
||||
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false))
|
||||
}
|
||||
if let data = self.data, !data.isPremiumRequiredForStoryPosting || data.accountIsPremium, let channel = data.peer as? TelegramChannel, channel.hasPermission(.postStories) {
|
||||
if let data = self.data, !data.isPremiumRequiredForStoryPosting || data.accountIsPremium, case let .channel(channel) = data.peer, channel.hasPermission(.postStories) {
|
||||
rightNavigationButtons.insert(PeerInfoHeaderNavigationButtonSpec(key: .postStory, isForExpandedView: false), at: 0)
|
||||
} else if self.isMyProfile {
|
||||
rightNavigationButtons.insert(PeerInfoHeaderNavigationButtonSpec(key: .postStory, isForExpandedView: false), at: 0)
|
||||
|
|
@ -5884,7 +5884,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
case .media:
|
||||
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .more, isForExpandedView: true))
|
||||
case .botPreview:
|
||||
if let data = self.data, data.hasBotPreviewItems, let user = data.peer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
|
||||
if let data = self.data, data.hasBotPreviewItems, case let .user(user) = data.peer, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
|
||||
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .more, isForExpandedView: true))
|
||||
}
|
||||
case .stories:
|
||||
|
|
@ -6912,7 +6912,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
|
|||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: subject, keepStack: .always, peekData: peekData))
|
||||
case .info:
|
||||
if peer.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) == nil {
|
||||
if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
navigationController.pushViewController(infoController)
|
||||
}
|
||||
}
|
||||
|
|
@ -7215,7 +7215,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
|
|||
}) {
|
||||
let _ = navigationController.popToViewController(infoController, animated: false)
|
||||
} else {
|
||||
if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
navigationController.replaceController(sourceController, with: infoController, animated: false)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -439,7 +439,7 @@ extension PeerInfoScreenImpl {
|
|||
|
||||
let peerId = self.peerId
|
||||
var isForum = false
|
||||
if let peer = peer as? TelegramChannel, peer.isForumOrMonoForum {
|
||||
if case let .channel(peer) = peer, peer.isForumOrMonoForum {
|
||||
isForum = true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ extension PeerInfoScreenNode {
|
|||
return
|
||||
}
|
||||
|
||||
guard let peer = self.data?.peer as? TelegramUser, let cachedUserData = self.data?.cachedData as? CachedUserData else {
|
||||
guard case let .user(peer) = self.data?.peer, let cachedUserData = self.data?.cachedData as? CachedUserData else {
|
||||
return
|
||||
}
|
||||
if cachedUserData.callsPrivate {
|
||||
|
|
@ -128,7 +128,7 @@ extension PeerInfoScreenNode {
|
|||
case .generic, .scheduledTooLate:
|
||||
text = strongSelf.presentationData.strings.Login_UnknownError
|
||||
case .anonymousNotAllowed:
|
||||
if let channel = strongSelf.data?.peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
if case let .channel(channel) = strongSelf.data?.peer, case .broadcast = channel.info {
|
||||
text = strongSelf.presentationData.strings.LiveStream_AnonymousDisabledAlertText
|
||||
} else {
|
||||
text = strongSelf.presentationData.strings.VoiceChat_AnonymousDisabledAlertText
|
||||
|
|
@ -304,7 +304,7 @@ extension PeerInfoScreenNode {
|
|||
|
||||
let createVoiceChatTitle: String
|
||||
let scheduleVoiceChatTitle: String
|
||||
if let channel = strongSelf.data?.peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
if case let .channel(channel) = strongSelf.data?.peer, case .broadcast = channel.info {
|
||||
createVoiceChatTitle = strongSelf.presentationData.strings.ChannelInfo_CreateLiveStream
|
||||
scheduleVoiceChatTitle = strongSelf.presentationData.strings.ChannelInfo_ScheduleLiveStream
|
||||
} else {
|
||||
|
|
@ -327,11 +327,11 @@ extension PeerInfoScreenNode {
|
|||
var credentialsPromise: Promise<GroupCallStreamCredentials>?
|
||||
var canCreateStream = false
|
||||
switch chatPeer {
|
||||
case let group as TelegramGroup:
|
||||
case let .legacyGroup(group):
|
||||
if case .creator = group.role {
|
||||
canCreateStream = true
|
||||
}
|
||||
case let channel as TelegramChannel:
|
||||
case let .channel(channel):
|
||||
if channel.hasPermission(.manageCalls) {
|
||||
canCreateStream = true
|
||||
credentialsPromise = Promise()
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ extension PeerInfoScreenNode {
|
|||
let giftsContext = pane.giftsContext
|
||||
|
||||
var hasVisibility = false
|
||||
if let channel = data.peer as? TelegramChannel, channel.hasPermission(.sendSomething) {
|
||||
if case let .channel(channel) = data.peer, channel.hasPermission(.sendSomething) {
|
||||
hasVisibility = true
|
||||
} else if data.peer?.id == self.context.account.peerId {
|
||||
hasVisibility = true
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ extension PeerInfoScreenNode {
|
|||
}
|
||||
|
||||
if case .botPreview = pane.scope {
|
||||
guard let data = self.data, let user = data.peer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) else {
|
||||
guard let data = self.data, case let .user(user) = data.peer, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +141,7 @@ extension PeerInfoScreenNode {
|
|||
self.mediaGalleryContextMenu = contextController
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
} else if case .peer = pane.scope {
|
||||
guard let data = self.data, let user = data.peer as? TelegramUser else {
|
||||
guard let data = self.data, case let .user(user) = data.peer else {
|
||||
return
|
||||
}
|
||||
let _ = user
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ extension PeerInfoScreenNode {
|
|||
var items: [ActionSheetItem] = []
|
||||
var personalPeerName: String?
|
||||
var isChannel = false
|
||||
if let user = peer as? TelegramUser {
|
||||
personalPeerName = EnginePeer(user).compactDisplayTitle
|
||||
} else if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
if case .user = peer {
|
||||
personalPeerName = peer.compactDisplayTitle
|
||||
} else if case let .channel(channel) = peer, case .broadcast = channel.info {
|
||||
isChannel = true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ extension PeerInfoScreenNode {
|
|||
UIPasteboard.general.string = bioText
|
||||
|
||||
let toastText: String
|
||||
if let _ = self.data?.peer as? TelegramUser {
|
||||
if case .user = self.data?.peer {
|
||||
toastText = self.presentationData.strings.MyProfile_ToastBioCopied
|
||||
} else {
|
||||
toastText = self.presentationData.strings.ChannelProfile_ToastAboutCopied
|
||||
|
|
@ -87,7 +87,7 @@ extension PeerInfoScreenNode {
|
|||
}
|
||||
|
||||
let copyText: String
|
||||
if let _ = self.data?.peer as? TelegramUser {
|
||||
if case .user = self.data?.peer {
|
||||
copyText = self.presentationData.strings.MyProfile_BioActionCopy
|
||||
} else {
|
||||
copyText = self.presentationData.strings.ChannelProfile_AboutActionCopy
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ extension PeerInfoScreenNode {
|
|||
|
||||
func openChatForReporting(title: String, option: Data, message: String?) {
|
||||
if let peer = self.data?.peer, let navigationController = (self.controller?.navigationController as? NavigationController) {
|
||||
if let channel = peer as? TelegramChannel, channel.isForumOrMonoForum {
|
||||
if case let .channel(channel) = peer, channel.isForumOrMonoForum {
|
||||
//let _ = self.context.engine.peers.reportPeer(peerId: peer.id, reason: reason, message: "").startStandalone()
|
||||
//self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .emoji(name: "PoliceCar", text: self.presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
} else {
|
||||
|
|
@ -37,7 +37,7 @@ extension PeerInfoScreenNode {
|
|||
NavigateToChatControllerParams(
|
||||
navigationController: navigationController,
|
||||
context: self.context,
|
||||
chatLocation: .peer(EnginePeer(peer)),
|
||||
chatLocation: .peer(peer),
|
||||
keepStack: .default,
|
||||
reportReason: NavigateToChatControllerParams.ReportReason(title: title, option: option, message: message)
|
||||
)
|
||||
|
|
@ -48,13 +48,13 @@ extension PeerInfoScreenNode {
|
|||
|
||||
func openChatForThemeChange() {
|
||||
if let peer = self.data?.peer, let navigationController = (self.controller?.navigationController as? NavigationController) {
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: .default, changeColors: true))
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default, changeColors: true))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func openChatForTranslation() {
|
||||
if let peer = self.data?.peer, let navigationController = (self.controller?.navigationController as? NavigationController) {
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: .default, changeColors: false))
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default, changeColors: false))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -77,7 +77,7 @@ extension PeerInfoScreenNode {
|
|||
}
|
||||
|
||||
if let peer = self.data?.peer, let navigationController = self.controller?.navigationController as? NavigationController {
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: .default))
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ extension PeerInfoScreenNode {
|
|||
}
|
||||
|
||||
func openChannelMessages() {
|
||||
guard let channel = self.data?.peer as? TelegramChannel, let linkedMonoforumId = channel.linkedMonoforumId else {
|
||||
guard case let .channel(channel) = self.data?.peer, let linkedMonoforumId = channel.linkedMonoforumId else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.engine.data.get(
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ extension PeerInfoScreenNode {
|
|||
})))
|
||||
}
|
||||
|
||||
if actions.contains(.promote) && enclosingPeer is TelegramChannel {
|
||||
if actions.contains(.promote), case .channel = enclosingPeer {
|
||||
var actionTitle: String = self.presentationData.strings.GroupInfo_ActionPromote
|
||||
if case .admin = member.role {
|
||||
actionTitle = self.presentationData.strings.GroupInfo_ActionEditAdmin
|
||||
|
|
@ -66,7 +66,7 @@ extension PeerInfoScreenNode {
|
|||
}
|
||||
|
||||
if actions.contains(.restrict) {
|
||||
if enclosingPeer is TelegramChannel {
|
||||
if case .channel = enclosingPeer {
|
||||
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.GroupInfo_ActionRestrict, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
|
||||
c?.dismiss {
|
||||
guard let self else {
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ extension PeerInfoScreenNode {
|
|||
text = customLink
|
||||
content = .linkCopied(title: nil, text: self.presentationData.strings.Conversation_LinkCopied)
|
||||
} else if let addressName = peer.addressName {
|
||||
if peer is TelegramChannel {
|
||||
if case .channel = peer {
|
||||
text = "https://t.me/\(addressName)"
|
||||
content = .linkCopied(title: nil, text: self.presentationData.strings.Conversation_LinkCopied)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ extension PeerInfoScreenNode {
|
|||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), subject: subject, updateTextInputState: inputState, activateInput: inputState != nil ? .text : nil, keepStack: .always, peekData: peekData))
|
||||
case .info:
|
||||
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: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
strongSelf.controller?.push(infoController)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,20 +22,20 @@ extension PeerInfoScreenNode {
|
|||
switch key {
|
||||
case .message:
|
||||
if let navigationController = controller.navigationController as? NavigationController, let peer = self.data?.peer {
|
||||
if let channel = peer as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), let linkedMonoforumId = channel.linkedMonoforumId {
|
||||
if case let .channel(channel) = peer, case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), let linkedMonoforumId = channel.linkedMonoforumId {
|
||||
Task { @MainActor [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let peer = await self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: linkedMonoforumId)).get() else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default))
|
||||
}
|
||||
} else {
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: .default))
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default))
|
||||
}
|
||||
}
|
||||
case .discussion:
|
||||
|
|
@ -64,7 +64,7 @@ extension PeerInfoScreenNode {
|
|||
} else {
|
||||
displayCustomNotificationSettings = true
|
||||
}
|
||||
if self.data?.threadData == nil, let channel = self.data?.peer as? TelegramChannel, channel.isForumOrMonoForum {
|
||||
if self.data?.threadData == nil, case let .channel(channel) = self.data?.peer, channel.isForumOrMonoForum {
|
||||
displayCustomNotificationSettings = true
|
||||
}
|
||||
|
||||
|
|
@ -222,13 +222,13 @@ extension PeerInfoScreenNode {
|
|||
|
||||
let mode: NotificationExceptionMode
|
||||
let defaultSound: PeerMessageSound
|
||||
if let _ = peer as? TelegramUser {
|
||||
if case .user = peer {
|
||||
mode = .users([:])
|
||||
defaultSound = globalSettings.privateChats.sound._asMessageSound()
|
||||
} else if let _ = peer as? TelegramSecretChat {
|
||||
} else if case .secretChat = peer {
|
||||
mode = .users([:])
|
||||
defaultSound = globalSettings.privateChats.sound._asMessageSound()
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
} else if case let .channel(channel) = peer {
|
||||
if case .broadcast = channel.info {
|
||||
mode = .channels([:])
|
||||
defaultSound = globalSettings.channels.sound._asMessageSound()
|
||||
|
|
@ -244,7 +244,7 @@ extension PeerInfoScreenNode {
|
|||
|
||||
let canRemove = false
|
||||
|
||||
let exceptionController = notificationPeerExceptionController(context: context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, peer: EnginePeer(peer), threadId: threadId, isStories: nil, canRemove: canRemove, defaultSound: defaultSound, defaultStoriesSound: globalSettings.privateChats.storySettings.sound, edit: true, updatePeerSound: { peerId, sound in
|
||||
let exceptionController = notificationPeerExceptionController(context: context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, peer: peer, threadId: threadId, isStories: nil, canRemove: canRemove, defaultSound: defaultSound, defaultStoriesSound: globalSettings.privateChats.storySettings.sound, edit: true, updatePeerSound: { peerId, sound in
|
||||
let _ = (updatePeerSound(peer.id, sound)
|
||||
|> deliverOnMainQueue).startStandalone(next: { _ in
|
||||
})
|
||||
|
|
@ -477,7 +477,7 @@ extension PeerInfoScreenNode {
|
|||
})))
|
||||
}
|
||||
|
||||
if let user = peer as? TelegramUser {
|
||||
if case let .user(user) = peer {
|
||||
if user.botInfo == nil && strongSelf.data?.encryptionKeyFingerprint == nil && !user.isDeleted {
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_ChangeWallpaper, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ApplyTheme"), color: theme.contextMenu.primaryColor)
|
||||
|
|
@ -564,7 +564,7 @@ extension PeerInfoScreenNode {
|
|||
}
|
||||
}
|
||||
|
||||
if user.botInfo == nil && data.isContact, let peer = strongSelf.data?.peer as? TelegramUser, let phone = peer.phone {
|
||||
if user.botInfo == nil && data.isContact, case let .user(peer) = strongSelf.data?.peer, let phone = peer.phone {
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Profile_ShareContactButton, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
|
|
@ -771,7 +771,7 @@ extension PeerInfoScreenNode {
|
|||
})))
|
||||
}
|
||||
|
||||
if let user = data.peer as? TelegramUser, let cachedData = data.cachedData as? CachedUserData, user.botInfo == nil && !user.flags.contains(.isSupport) && user.id != strongSelf.context.account.peerId && strongSelf.peerId.namespace != Namespaces.Peer.SecretChat {
|
||||
if case let .user(user) = data.peer, let cachedData = data.cachedData as? CachedUserData, user.botInfo == nil && !user.flags.contains(.isSupport) && user.id != strongSelf.context.account.peerId && strongSelf.peerId.namespace != Namespaces.Peer.SecretChat {
|
||||
let copyProtectionEnabled = cachedData.flags.contains(.myCopyProtectionEnabled) || cachedData.flags.contains(.copyProtectionEnabled)
|
||||
items.append(.action(ContextMenuActionItem(text: !copyProtectionEnabled ? strongSelf.presentationData.strings.PeerInfo_DisableSharing : strongSelf.presentationData.strings.PeerInfo_EnableSharing, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: !copyProtectionEnabled ? "Chat/Context Menu/ForwardDisable" : "Chat/Context Menu/ForwardEnable"), color: theme.contextMenu.primaryColor)
|
||||
|
|
@ -801,7 +801,7 @@ extension PeerInfoScreenNode {
|
|||
}
|
||||
let _ = self.context.engine.peers.toggleMessageCopyProtection(peerId: user.id, enabled: true).start()
|
||||
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: .default, completion: { _ in }))
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default, completion: { _ in }))
|
||||
}
|
||||
let _ = (ApplicationSpecificNotice.getCopyProtectionTips(accountManager: self.context.sharedContext.accountManager)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] count in
|
||||
|
|
@ -827,7 +827,7 @@ extension PeerInfoScreenNode {
|
|||
let action = {
|
||||
let _ = self.context.engine.peers.toggleMessageCopyProtection(peerId: user.id, enabled: false).start()
|
||||
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: .default, scrollToEndIfExists: true, completion: { _ in }))
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default, scrollToEndIfExists: true, completion: { _ in }))
|
||||
}
|
||||
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
|
|
@ -835,7 +835,7 @@ extension PeerInfoScreenNode {
|
|||
if let cachedUserData = self.data?.cachedData as? CachedUserData, !cachedUserData.flags.contains(.copyProtectionEnabled), let date = cachedUserData.myCopyProtectionEnableDate, currentTime < date + timeout {
|
||||
action()
|
||||
} else {
|
||||
let peerName = self.data?.peer.flatMap(EnginePeer.init)?.compactDisplayTitle ?? ""
|
||||
let peerName = self.data?.peer?.compactDisplayTitle ?? ""
|
||||
let alertController = AlertScreen(context: self.context, configuration: .init(actionAlignment: .vertical), title: self.presentationData.strings.EnableSharing_Title, text: self.presentationData.strings.EnableSharing_Text(peerName).string, actions: [
|
||||
.init(title: self.presentationData.strings.EnableSharing_SendRequest, type: .default, action: {
|
||||
action()
|
||||
|
|
@ -890,7 +890,7 @@ extension PeerInfoScreenNode {
|
|||
if finalItemsCount > itemsCount {
|
||||
items.insert(.separator, at: itemsCount)
|
||||
}
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
} else if case let .channel(channel) = peer {
|
||||
if let cachedData = strongSelf.data?.cachedData as? CachedChannelData {
|
||||
if case .broadcast = channel.info, cachedData.flags.contains(.starGiftsAvailable) {
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Profile_SendGift, badge: nil, icon: { theme in
|
||||
|
|
@ -1112,7 +1112,7 @@ extension PeerInfoScreenNode {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
} else if case let .legacyGroup(group) = peer {
|
||||
if canSetupAutoremoveTimeout {
|
||||
let strings = strongSelf.presentationData.strings
|
||||
items.append(.action(ContextMenuActionItem(text: currentAutoremoveTimeout == nil ? strongSelf.presentationData.strings.PeerInfo_EnableAutoDelete : strongSelf.presentationData.strings.PeerInfo_AdjustAutoDelete, icon: { theme in
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ extension PeerInfoScreenNode {
|
|||
guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else {
|
||||
return
|
||||
}
|
||||
if let user = self.data?.peer as? TelegramUser, let phoneNumber = user.phone {
|
||||
if case let .user(user) = self.data?.peer, let phoneNumber = user.phone {
|
||||
let introController = PrivacyIntroController(context: self.context, mode: .changePhoneNumber(phoneNumber), proceedAction: { [weak self] in
|
||||
if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
|
||||
navigationController.replaceTopController(ChangePhoneNumberController(context: strongSelf.context), animated: true)
|
||||
|
|
@ -250,7 +250,7 @@ extension PeerInfoScreenNode {
|
|||
}
|
||||
})
|
||||
case .logout:
|
||||
if let user = self.data?.peer as? TelegramUser, let phoneNumber = user.phone {
|
||||
if case let .user(user) = self.data?.peer, let phoneNumber = user.phone {
|
||||
if let controller = self.controller, let navigationController = controller.navigationController as? NavigationController {
|
||||
self.controller?.push(logoutOptionsController(context: self.context, navigationController: navigationController, canAddAccounts: true, phoneNumber: phoneNumber))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ func settingsItems(data: PeerInfoScreenData?, context: AccountContext, presentat
|
|||
var setStatusTitle: String = ""
|
||||
let displaySetStatus: Bool
|
||||
var hasEmojiStatus = false
|
||||
if let peer = data.peer as? TelegramUser, peer.isPremium {
|
||||
if case let .user(peer) = data.peer, peer.isPremium {
|
||||
if peer.emojiStatus != nil {
|
||||
hasEmojiStatus = true
|
||||
setStatusTitle = presentationData.strings.PeerInfo_ChangeEmojiStatus
|
||||
|
|
@ -86,7 +86,7 @@ func settingsItems(data: PeerInfoScreenData?, context: AccountContext, presentat
|
|||
items[.phone]!.append(PeerInfoScreenActionItem(id: 1, text: "Restore Subscription", action: {
|
||||
interaction.openSettings(.premiumManagement)
|
||||
}))
|
||||
} else if settings.suggestPhoneNumberConfirmation, let peer = data.peer as? TelegramUser {
|
||||
} else if settings.suggestPhoneNumberConfirmation, case let .user(peer) = data.peer {
|
||||
let phoneNumber = formatPhoneNumber(context: context, number: peer.phone ?? "")
|
||||
items[.phone]!.append(PeerInfoScreenInfoItem(id: 0, title: presentationData.strings.Settings_CheckPhoneNumberTitle(phoneNumber).string, text: .markdown(presentationData.strings.Settings_CheckPhoneNumberText), linkAction: { link in
|
||||
if case .tap = link {
|
||||
|
|
@ -444,7 +444,7 @@ func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoState, conte
|
|||
}))
|
||||
}
|
||||
|
||||
if let user = data.peer as? TelegramUser {
|
||||
if case let .user(user) = data.peer {
|
||||
items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPhoneNumber, label: .text(user.phone.flatMap({ formatPhoneNumber(context: context, number: $0) }) ?? ""), text: presentationData.strings.Settings_PhoneNumber, icon: PresentationResourcesSettings.recentCalls, action: {
|
||||
interaction.openSettings(.phoneNumber)
|
||||
}))
|
||||
|
|
@ -457,7 +457,7 @@ func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoState, conte
|
|||
interaction.openSettings(.username)
|
||||
}))
|
||||
|
||||
if let peer = data.peer as? TelegramUser {
|
||||
if case let .user(peer) = data.peer {
|
||||
var colors: [PeerNameColors.Colors] = []
|
||||
if let nameColor = peer.nameColor {
|
||||
let nameColors: PeerNameColors.Colors
|
||||
|
|
|
|||
|
|
@ -774,10 +774,10 @@ final class ShareWithPeersScreenComponent: Component {
|
|||
if item.peer.id == context.account.peerId {
|
||||
continue
|
||||
}
|
||||
if let user = item.peer as? TelegramUser, user.botInfo != nil {
|
||||
if case let .user(user) = item.peer, user.botInfo != nil {
|
||||
continue
|
||||
}
|
||||
peers.append(EnginePeer(item.peer))
|
||||
peers.append(item.peer)
|
||||
}
|
||||
if !list.list.isEmpty {
|
||||
subscriber.putNext(peers)
|
||||
|
|
|
|||
|
|
@ -555,7 +555,7 @@ public extension ShareWithPeersScreen {
|
|||
continue
|
||||
}
|
||||
|
||||
peers.append(EnginePeer(participant.peer))
|
||||
peers.append(participant.peer)
|
||||
existingPeersIds.insert(participant.peer.id)
|
||||
}
|
||||
|
||||
|
|
@ -563,7 +563,7 @@ public extension ShareWithPeersScreen {
|
|||
if participant.peer.isDeleted || existingPeersIds.contains(participant.peer.id) || participant.participant.adminInfo != nil {
|
||||
continue
|
||||
}
|
||||
if let user = participant.peer as? TelegramUser, user.botInfo != nil {
|
||||
if case let .user(user) = participant.peer, user.botInfo != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -573,7 +573,7 @@ public extension ShareWithPeersScreen {
|
|||
continue
|
||||
}
|
||||
|
||||
peers.append(EnginePeer(participant.peer))
|
||||
peers.append(participant.peer)
|
||||
}
|
||||
|
||||
let state = State(
|
||||
|
|
|
|||
|
|
@ -1955,7 +1955,7 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
|
|||
return
|
||||
}
|
||||
if isProfile {
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -2075,7 +2075,7 @@ final class StorageUsageScreenComponent: Component {
|
|||
let peerInfoController = component.context.sharedContext.makePeerInfoController(
|
||||
context: component.context,
|
||||
updatedPresentationData: nil,
|
||||
peer: peer._asPeer(),
|
||||
peer: peer,
|
||||
mode: .generic,
|
||||
avatarInitiallyExpanded: false,
|
||||
fromChat: false,
|
||||
|
|
|
|||
|
|
@ -367,7 +367,7 @@ final class StoryContentLiveChatComponent: Component {
|
|||
rank: nil,
|
||||
subscriptionUntilDate: nil
|
||||
),
|
||||
peer: author._asPeer()
|
||||
peer: author
|
||||
)],
|
||||
mode: .liveStream(
|
||||
messageCount: 1,
|
||||
|
|
|
|||
|
|
@ -5626,7 +5626,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
}
|
||||
navigationController.setViewControllers(currentViewControllers, animated: true)
|
||||
} else {
|
||||
guard let chatController = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
|
||||
guard let chatController = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -7616,7 +7616,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
if let controller = component.context.sharedContext.makePeerInfoController(
|
||||
context: component.context,
|
||||
updatedPresentationData: nil,
|
||||
peer: peer._asPeer(),
|
||||
peer: peer,
|
||||
mode: .myProfile,
|
||||
avatarInitiallyExpanded: false,
|
||||
fromChat: false,
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue