MediaResource → EngineMediaResource refactor: wave 2

Drive raw `MediaResource` out of the `TelegramEngine` public facade for
photo-upload APIs, and complete a first batch of consumer-side
migrations. Behavior-preserving. The `_internal_*` Postbox-facing
functions are untouched — only the facade signatures change, bridging
inside via `_asResource()` / `EngineMediaResource(_:)`.

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

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

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

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

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Isaac 2026-04-17 09:07:54 +02:00
parent adc275af27
commit f84e94f507
14 changed files with 981 additions and 84 deletions

View file

@ -33,6 +33,7 @@ A gradual migration is underway to eliminate direct `import Postbox` from consum
4. **Discovery first:** before adding any new engine wrapper/typealias, grep `submodules/TelegramCore/Sources/TelegramEngine/` for existing equivalents. Record the search result in the commit message.
5. **Abandonment protocol:** if a module can only be refactored by violating rule 2 or by editing a module outside the current wave's list, mark the task Abandoned with a recorded reason. Do NOT substitute a new module mid-wave.
6. Full project build per module. No unit tests exist in this project.
7. **TelegramCore never imports UIKit/Display.** `TelegramCore` is shared with the Telegram-Mac codebase; its Bazel `deps` and source files must not reference UIKit, Display, or any Apple-UI framework. UIKit-needing helpers (image scaling, rendering, etc.) stay in consumer-side submodules.
### Engine typealias cheat sheet (existing aliases)
@ -56,6 +57,21 @@ AdaptedPostboxDecoder → EngineAdaptedPostboxDecoder (added 2026-04)
For the `MediaResource` Postbox protocol, prefer the TelegramCore subtype `TelegramMediaResource` when the consumer's usage allows (note: `EngineMediaResource` is a wrapper **class**, not a typealias, so it is not interchangeable with the protocol).
### MediaResource → EngineMediaResource consumer migration
`EngineMediaResource` is a `final class` in `TelegramCore` wrapping a `MediaResource` value. Unlike the typealiases above it is **not** interchangeable with the protocol, but it does provide wrap/unwrap helpers:
- `EngineMediaResource(rawResource)` — wrap a raw `MediaResource`.
- `engineResource._asResource()` — unwrap to the raw `MediaResource`.
- `EngineMediaResource.ResourceData(rawResourceData)` — wrap `MediaResourceData`.
- `EngineMediaResource.Id(rawMediaResourceId)` — wrap `MediaResourceId`.
**Pattern for facade functions:** when a `TelegramEngine.<Area>` method leaks raw `MediaResource` in its public signature, **change the facade signature in place** to `EngineMediaResource` (and change any closure parameter types the same way). Bridge inside the facade body by calling the existing `_internal_*` function with `engineResource._asResource()` / wrapping raw inputs from inner closures with `EngineMediaResource(rawResource)`. Update all call sites in the same commit. The `_internal_*` function stays on raw `MediaResource` — it is the Postbox-facing layer.
Do **not** add opt-in `EngineMediaResource` overloads alongside raw-`MediaResource` overloads. Duplicate signatures fragment the public API and leave the leak in place forever.
For consumer modules, prefer `EngineMediaResource` as the type in properties, locals, generic arguments and function parameters when the usage is a pure type reference. Do **not** try to use `EngineMediaResource` where a class must conform to `TelegramMediaResource` (Postbox protocol) or override `isEqual(to: MediaResource)` — those remain `import Postbox`.
### Wave-selection guidance (learned from wave 1)
The "leaf module, drop Postbox in isolation" approach only works for modules whose **public API doesn't leak Postbox domain types**. Most candidate leaf modules DO leak such types (`postbox: Postbox` / `account: Account` in public inits, `Media`/`Message` in public function parameters). Those modules need paired caller-migration waves, not isolated refactors.

View file

@ -0,0 +1,880 @@
# MediaResource → EngineMediaResource Refactor (Wave 2) — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Drive raw `MediaResource` (Postbox protocol) out of the `TelegramEngine` public facade by changing facade-function signatures in-place to take/return `EngineMediaResource`, bridging to the existing `_internal_*` Postbox-facing implementations via wrap/unwrap helpers. In the same commit as each facade change, update every call site. Follow up with a first small batch of consumer type-reference migrations.
**Architecture:** `TelegramEngine` facade methods live alongside `_internal_*` Postbox-using implementations in `submodules/TelegramCore/Sources/TelegramEngine/<Area>/`. Today the facade methods already bridge (storing an `Account` and delegating), but their public signatures still expose raw `MediaResource`. The fix: change facade signatures to `EngineMediaResource` (including the `mapResourceToAvatarSizes` closure types) and add the two-line wrap/unwrap bridging. `_internal_*` functions stay on raw `MediaResource` — they are the Postbox-facing layer and must remain so. Consumer call sites swap `MediaResource``EngineMediaResource` (usually via `EngineMediaResource(raw)` wrap or `engineResource._asResource()` unwrap at a nearby boundary).
**Tech Stack:** Swift, Bazel, Postbox (opaque storage), TelegramCore (public facade), SSignalKit.
**Design constraint (IMPORTANT):** `TelegramCore` is shared with the Telegram-Mac codebase and must **not** import UIKit/Display. Any UIKit-requiring logic (image scaling, `UIImage`, `generateScaledImage`, etc.) stays in consumer-side submodules. Engine API additions must not pull in UIKit.
**Why not overloads:** An earlier iteration of this plan added opt-in `EngineMediaResource` overloads and kept the raw overloads. That was rejected: duplicate signatures fragment the public API and leave raw-`MediaResource` leaks forever. The correct pattern is to change the single facade function in-place so it takes engine types and bridges inside, forcing callers to migrate in the same commit.
---
## Background the executor needs
### The full build command
Run from the repo root (`/Users/ali/build/telegram/telegram-ios`):
```bash
source ~/.zshrc 2>/dev/null; \
PATH=/opt/homebrew/opt/ruby/bin:`gem environment gemdir`/bin:$PATH \
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
```
The build is the only verification (no unit tests per `CLAUDE.md`). Every task ends with a full build that must go green before the next task starts.
### What `EngineMediaResource` gives you today (bridge primitives)
Defined in [TelegramEngineResources.swift](../../../submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift):
```swift
public final class EngineMediaResource: Equatable {
public init(_ resource: MediaResource)
public func _asResource() -> MediaResource
public var id: Id
public struct Id: Equatable, Hashable {
public init(_ id: MediaResourceId)
public init(_ stringRepresentation: String)
}
public final class ResourceData {
public let path: String; public let availableSize: Int64; public let isComplete: Bool
}
public enum FetchStatus: Equatable { /* Remote/Local/Fetching/Paused */ }
}
public extension EngineMediaResource.ResourceData {
convenience init(_ data: MediaResourceData)
}
```
### The bridging pattern
For each facade function whose public signature contains `MediaResource`:
**Before** (raw-protocol leak):
```swift
public func uploadedPeerPhoto(resource: MediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
return _internal_uploadedPeerPhoto(postbox: self.account.postbox, network: self.account.network, resource: resource)
}
```
**After** (engine-typed facade, internal bridge):
```swift
public func uploadedPeerPhoto(resource: EngineMediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
return _internal_uploadedPeerPhoto(postbox: self.account.postbox, network: self.account.network, resource: resource._asResource())
}
```
For closures that receive a `MediaResource`:
**Before:**
```swift
public func updatePeerPhoto(..., mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> ... {
return _internal_updatePeerPhoto(..., mapResourceToAvatarSizes: mapResourceToAvatarSizes)
}
```
**After:**
```swift
public func updatePeerPhoto(..., mapResourceToAvatarSizes: @escaping (EngineMediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> ... {
return _internal_updatePeerPhoto(..., mapResourceToAvatarSizes: { rawResource, representations in
mapResourceToAvatarSizes(EngineMediaResource(rawResource), representations)
})
}
```
`_internal_*` functions are **not** changed — they stay on raw `MediaResource` as the Postbox-facing layer.
### Call-site migration pattern
At each call site, the change is mechanical:
- `engine.peers.uploadedPeerPhoto(resource: someRawResource)``engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(someRawResource))`.
- `engine.peers.updatePeerPhoto(..., mapResourceToAvatarSizes: { resource, representations in ... resource ... })` — the closure's `resource` is now `EngineMediaResource`. Any expression inside the closure that previously treated `resource` as raw protocol (e.g. `postbox.mediaBox.resourceData(resource)`) must use `resource._asResource()`.
Where the consumer was carrying a `MediaResource?` property / local purely as a pipe into one of these APIs, migrate the property itself to `EngineMediaResource?` so no unwrap/wrap churn is needed.
### Static-check commands
```bash
grep -R "^import Postbox" submodules/<M>/Sources # expect: empty (only when a module is being fully de-Postboxed)
grep "submodules/Postbox" submodules/<M>/BUILD # expect: empty (same condition)
```
### Commit convention
- One commit per engine API family: `TelegramCore: migrate <function(s)> to EngineMediaResource` — bundles facade-signature change **and** all call sites updated in the same commit. The repo must build on every commit.
- Consumer-only type-ref commits: `<ModuleName>: migrate MediaResource property to EngineMediaResource` or `<ModuleName>: drop direct Postbox dependency`.
- Always use HEREDOC bodies. No `--amend`.
### What is explicitly out of scope
- Classes that **conform to `TelegramMediaResource`** (must implement `isEqual(to: MediaResource)`): remain `import Postbox`. Enumerated:
- `submodules/ICloudResources/Sources/ICloudResources.swift``ICloudFileResource`
- `submodules/InstantPageUI/Sources/InstantPageExternalMediaResource.swift``InstantPageExternalMediaResource`
- `submodules/LocalMediaResources/Sources/LocalMediaResources.swift``VideoLibraryMediaResource`
- `submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift``YoutubeEmbedStoryboardMediaResource`
- TelegramCore-internal `MediaResource` usage (SyncCore, Fetch, `_internal_*` functions, etc.) — Postbox-facing layer.
- Modules already abandoned in wave 1 for non-MediaResource reasons (`FetchManagerImpl` / `ICloudResources` have other umbrella-type blockers).
- The heavy-leak modules in the "Future waves" table at the bottom (`PassportUI`, `TelegramUI`, etc.).
- Importing UIKit/Display into TelegramCore under any circumstance.
---
## Task 0: Baseline verification
**Files:** No code changes.
- [ ] **Step 1: Confirm tree state**
```bash
git status
git log --oneline -5
```
Expected: working tree clean apart from pre-existing untracked (`build-system/tulsi/`, `submodules/TgVoip/`, `third-party/libx264/`) and submodule-content drift on `build-system/bazel-rules/sourcekit-bazel-bsp`. HEAD on `master`.
- [ ] **Step 2: Baseline build**
Run the full build command above. Expected: PASS.
If it fails, stop — a non-green baseline is out of scope.
- [ ] **Step 3: No commit.**
---
## Task 1: Record the new rules in CLAUDE.md
**Files:**
- Modify: `CLAUDE.md`
- [ ] **Step 1: Add the "TelegramCore no UIKit" rule**
In `CLAUDE.md`, inside the `## Postbox → TelegramEngine refactor (in progress)` section, under `### Rules that apply to every wave`, append a new numbered rule after the existing rule 6:
```markdown
7. **TelegramCore never imports UIKit/Display.** `TelegramCore` is shared with the Telegram-Mac codebase; its Bazel `deps` and source files must not reference UIKit, Display, or any Apple-UI framework. UIKit-needing helpers (image scaling, rendering, etc.) stay in consumer-side submodules.
```
- [ ] **Step 2: Add the MediaResource → EngineMediaResource migration pattern**
After the `### Engine typealias cheat sheet (existing aliases)` block (which ends with the `MediaResource` / `TelegramMediaResource` note), insert a new section:
```markdown
### MediaResource → EngineMediaResource consumer migration
`EngineMediaResource` is a `final class` in `TelegramCore` wrapping a `MediaResource` value. Unlike the typealiases above it is **not** interchangeable with the protocol, but it does provide wrap/unwrap helpers:
- `EngineMediaResource(rawResource)` — wrap a raw `MediaResource`.
- `engineResource._asResource()` — unwrap to the raw `MediaResource`.
- `EngineMediaResource.ResourceData(rawResourceData)` — wrap `MediaResourceData`.
- `EngineMediaResource.Id(rawMediaResourceId)` — wrap `MediaResourceId`.
**Pattern for facade functions:** when a `TelegramEngine.<Area>` method leaks raw `MediaResource` in its public signature, **change the facade signature in place** to `EngineMediaResource` (and change any closure parameter types the same way). Bridge inside the facade body by calling the existing `_internal_*` function with `engineResource._asResource()` / wrapping raw inputs from inner closures with `EngineMediaResource(rawResource)`. Update all call sites in the same commit. The `_internal_*` function stays on raw `MediaResource` — it is the Postbox-facing layer.
Do **not** add opt-in `EngineMediaResource` overloads alongside raw-`MediaResource` overloads. Duplicate signatures fragment the public API and leave the leak in place forever.
For consumer modules, prefer `EngineMediaResource` as the type in properties, locals, generic arguments and function parameters when the usage is a pure type reference. Do **not** try to use `EngineMediaResource` where a class must conform to `TelegramMediaResource` (Postbox protocol) or override `isEqual(to: MediaResource)` — those remain `import Postbox`.
```
- [ ] **Step 3: Full build (sanity — docs only)**
Run the full build. Expected: PASS.
- [ ] **Step 4: Commit**
```bash
git add CLAUDE.md
git commit -m "$(cat <<'EOF'
CLAUDE.md: record TelegramCore-no-UIKit rule and EngineMediaResource migration pattern
Wave-2 preparation. Codifies that TelegramCore is shared with
Telegram-Mac and must stay UIKit-free, and documents the
modify-in-place / bridge-inside pattern for migrating
MediaResource-leaking facade functions to EngineMediaResource.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)"
```
---
## Task 2: Migrate `TelegramEngine.Peers` photo APIs to `EngineMediaResource`
**Files:**
- Modify: `submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift`
- Modify: all call sites (13 + 11 + 7, with heavy overlap — see Step 2 grep).
**Functions migrated in this task:**
- `uploadedPeerPhoto(resource:)` (line 704) — `MediaResource``EngineMediaResource`
- `uploadedPeerVideo(resource:)` (line 708) — `MediaResource``EngineMediaResource`
- `updatePeerPhoto(..., mapResourceToAvatarSizes:)` (line 712) — closure parameter `MediaResource``EngineMediaResource`
- [ ] **Step 1: Read the current signatures**
Read lines 704720 of `submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift`. Confirm the three functions match the pattern `return _internal_<name>(postbox: self.account.postbox, ..., resource: resource)` or equivalent.
- [ ] **Step 2: Enumerate call sites**
```bash
grep -rnE "\\.(uploadedPeerPhoto|uploadedPeerVideo|updatePeerPhoto)\(" submodules/ \
| grep -v "submodules/TelegramCore"
```
Capture every hit — file path, line number, approximate surrounding context (what resource expression is passed in / what the closure body does). The distribution as of planning:
- `uploadedPeerPhoto`: 11 call sites (spread across TelegramUI, TelegramCallsUI, AuthorizationUI, etc.)
- `uploadedPeerVideo`: 7
- `updatePeerPhoto`: 13
Many call sites chain these (e.g. `updatePeerPhoto(photo: engine.peers.uploadedPeerPhoto(resource: ...))`) so a single file often touches two or three of them in one call.
- [ ] **Step 3: Change the facade signatures + bridge**
In `TelegramEnginePeers.swift`, change the three functions to:
```swift
public func uploadedPeerPhoto(resource: EngineMediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
return _internal_uploadedPeerPhoto(postbox: self.account.postbox, network: self.account.network, resource: resource._asResource())
}
public func uploadedPeerVideo(resource: EngineMediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
return _internal_uploadedPeerVideo(postbox: self.account.postbox, network: self.account.network, messageMediaPreuploadManager: self.account.messageMediaPreuploadManager, resource: resource._asResource())
}
public func updatePeerPhoto(peerId: PeerId, photo: Signal<UploadedPeerPhotoData, NoError>?, video: Signal<UploadedPeerPhotoData?, NoError>? = nil, videoStartTimestamp: Double? = nil, markup: UploadPeerPhotoMarkup? = nil, mapResourceToAvatarSizes: @escaping (EngineMediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updatePeerPhoto(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, accountPeerId: self.account.peerId, peerId: peerId, photo: photo, video: video, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { rawResource, representations in
return mapResourceToAvatarSizes(EngineMediaResource(rawResource), representations)
})
}
```
**Before editing, re-read the existing bodies** — the exact arg names passed into `_internal_updatePeerPhoto` etc. must match what's already there (the skeletons above reproduce what's in the file at planning time, but the executor should preserve every argument the current implementation passes). Only the outer signature and the closure-wrapping change.
- [ ] **Step 4: Update every call site** (same commit)
For each hit from Step 2, rewrite the call site per the patterns:
**Pattern A — passing a raw resource to `uploadedPeerPhoto` / `uploadedPeerVideo`:**
```swift
// Before:
engine.peers.uploadedPeerPhoto(resource: someRawResource)
// After:
engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(someRawResource))
```
**Pattern B — the `mapResourceToAvatarSizes` closure of `updatePeerPhoto`:**
```swift
// Before:
mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
}
// After (if the helper is still raw-MediaResource-facing at this point):
mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource._asResource(), representations: representations)
}
```
Task 6 will change `mapResourceToAvatarSizes` itself to accept `EngineMediaResource` and drop the `_asResource()` call. Until Task 6 lands, keep the `_asResource()` here. This keeps the build green between tasks.
**Pattern C — the consumer was already carrying the resource as a `MediaResource?` local purely as a pipe:**
If a nearby local/property typed `MediaResource?` only exists to feed `uploadedPeerPhoto(resource:)` or similar, change the local's type to `EngineMediaResource?` at the same time. This avoids wrap/unwrap churn at the call site.
- [ ] **Step 5: Full build**
Run the full build. Expected: PASS.
If it fails, the first error locates the broken call site. Apply Pattern A / B / C at that site and rebuild. If a file imports Postbox only for `MediaResource` and now has no other Postbox identifier, you may optionally remove `import Postbox` in the same commit — but that is not required here; it is a separate goal.
- [ ] **Step 6: Commit**
```bash
git add submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift submodules/
git commit -m "$(cat <<'EOF'
TelegramCore: migrate peer-photo facade to EngineMediaResource
Change TelegramEngine.Peers.uploadedPeerPhoto / uploadedPeerVideo /
updatePeerPhoto so their public signatures take EngineMediaResource
instead of raw MediaResource (and the mapResourceToAvatarSizes closure
receives EngineMediaResource). The facade bridges to the existing
_internal_* Postbox-facing implementations via _asResource() /
EngineMediaResource(_:). All call sites updated in this commit.
Part of the MediaResource -> EngineMediaResource migration (wave 2).
No behavior change.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)"
```
---
## Task 3: Migrate `TelegramEngine.AccountData.updateAccountPhoto` and `updateFallbackPhoto`
**Files:**
- Modify: `submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift`
- Modify: all call sites (5 + 4).
- [ ] **Step 1: Read the current signatures**
Read lines 5590 of `submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift`. Confirm both functions match the expected pattern.
- [ ] **Step 2: Enumerate call sites**
```bash
grep -rnE "\\.(updateAccountPhoto|updateFallbackPhoto)\(" submodules/ \
| grep -v "submodules/TelegramCore"
```
- [ ] **Step 3: Change the facade signatures + bridge**
Change both functions in place:
```swift
public func updateAccountPhoto(resource: EngineMediaResource?, videoResource: EngineMediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mapResourceToAvatarSizes: @escaping (EngineMediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updateAccountPhoto(account: self.account, resource: resource?._asResource(), videoResource: videoResource?._asResource(), videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { rawResource, representations in
return mapResourceToAvatarSizes(EngineMediaResource(rawResource), representations)
})
}
public func updateFallbackPhoto(resource: EngineMediaResource?, videoResource: EngineMediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mapResourceToAvatarSizes: @escaping (EngineMediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updateFallbackPhoto(account: self.account, resource: resource?._asResource(), videoResource: videoResource?._asResource(), videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { rawResource, representations in
return mapResourceToAvatarSizes(EngineMediaResource(rawResource), representations)
})
}
```
**Before editing, verify the exact argument names passed to `_internal_updateAccountPhoto` / `_internal_updateFallbackPhoto`** in the current file. Copy those argument spellings verbatim (only the outer signature and inner closure wrapping change).
- [ ] **Step 4: Update every call site** (same commit)
Apply Pattern A/B/C from Task 2 to every hit. Wrap `EngineMediaResource(...)` around raw-resource args; add `._asResource()` inside any `mapResourceToAvatarSizes:` closure body where it hands the value onward to a still-raw helper (removed in Task 6).
- [ ] **Step 5: Full build**
Run the full build. Expected: PASS.
- [ ] **Step 6: Commit**
```bash
git add submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift submodules/
git commit -m "$(cat <<'EOF'
TelegramCore: migrate account-photo facade to EngineMediaResource
Change TelegramEngine.AccountData.updateAccountPhoto and
updateFallbackPhoto so their public signatures take EngineMediaResource
(and the mapResourceToAvatarSizes closure receives
EngineMediaResource). Bridges to _internal_* functions via
_asResource()/EngineMediaResource(_:). All call sites updated in this
commit.
Part of the MediaResource -> EngineMediaResource migration (wave 2).
No behavior change.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)"
```
---
## Task 4: Migrate `TelegramEngine.Contacts.updateContactPhoto`
**Files:**
- Modify: `submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift`
- Modify: all call sites (8).
- [ ] **Step 1: Read the current signature**
Read around line 33 of `submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift`.
- [ ] **Step 2: Enumerate call sites**
```bash
grep -rn "\.updateContactPhoto(" submodules/ | grep -v "submodules/TelegramCore"
```
- [ ] **Step 3: Change the facade signature + bridge**
```swift
public func updateContactPhoto(peerId: PeerId, resource: EngineMediaResource?, videoResource: EngineMediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mode: SetCustomPeerPhotoMode, mapResourceToAvatarSizes: @escaping (EngineMediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updateContactPhoto(account: self.account, peerId: peerId, resource: resource?._asResource(), videoResource: videoResource?._asResource(), videoStartTimestamp: videoStartTimestamp, markup: markup, mode: mode, mapResourceToAvatarSizes: { rawResource, representations in
return mapResourceToAvatarSizes(EngineMediaResource(rawResource), representations)
})
}
```
Verify the `_internal_updateContactPhoto` call spelling against the existing file before committing.
- [ ] **Step 4: Update every call site** (same commit)
Pattern A/B/C as in Task 2.
- [ ] **Step 5: Full build**
Run the full build. Expected: PASS.
- [ ] **Step 6: Commit**
```bash
git add submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift submodules/
git commit -m "$(cat <<'EOF'
TelegramCore: migrate updateContactPhoto facade to EngineMediaResource
Change TelegramEngine.Contacts.updateContactPhoto so its public
signature takes EngineMediaResource parameters and the
mapResourceToAvatarSizes closure receives EngineMediaResource. Bridges
to _internal_updateContactPhoto via _asResource()/EngineMediaResource(_:).
All call sites updated in this commit.
Part of the MediaResource -> EngineMediaResource migration (wave 2).
No behavior change.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)"
```
---
## Task 5: Migrate `TelegramEngine.Auth.uploadedPeerVideo`
**Files:**
- Modify: `submodules/TelegramCore/Sources/TelegramEngine/Auth/TelegramEngineAuth.swift`
- Modify: call sites that route through `TelegramEngine.Auth.uploadedPeerVideo` (separate from `TelegramEngine.Peers.uploadedPeerVideo`).
- [ ] **Step 1: Read the current signature**
Read around line 51 of `submodules/TelegramCore/Sources/TelegramEngine/Auth/TelegramEngineAuth.swift`.
- [ ] **Step 2: Enumerate call sites**
```bash
grep -rn "engine\.auth\.uploadedPeerVideo\|\.auth\.uploadedPeerVideo" submodules/ | grep -v "submodules/TelegramCore"
```
The call site count is small (the sign-up flow). If zero, skip Step 4.
- [ ] **Step 3: Change the facade signature + bridge**
```swift
public func uploadedPeerVideo(resource: EngineMediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
return _internal_uploadedPeerVideo(postbox: self.account.postbox, network: self.account.network, messageMediaPreuploadManager: self.account.messageMediaPreuploadManager, resource: resource._asResource())
}
```
Preserve the exact argument spellings from the existing function body.
- [ ] **Step 4: Update call sites** (same commit)
Pattern A.
- [ ] **Step 5: Full build**
Run the full build. Expected: PASS.
- [ ] **Step 6: Commit**
```bash
git add submodules/TelegramCore/Sources/TelegramEngine/Auth/TelegramEngineAuth.swift submodules/
git commit -m "$(cat <<'EOF'
TelegramCore: migrate Auth.uploadedPeerVideo facade to EngineMediaResource
Signature change + call sites.
Part of the MediaResource -> EngineMediaResource migration (wave 2).
No behavior change.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)"
```
---
## Task 6: Migrate `mapResourceToAvatarSizes` utility and drop `import Postbox` from `MapResourceToAvatarSizes`
**Files:**
- Modify: `submodules/MapResourceToAvatarSizes/Sources/MapResourceToAvatarSizes.swift`
- Modify: `submodules/MapResourceToAvatarSizes/BUILD`
- Modify: all 27 call sites of the old `mapResourceToAvatarSizes(postbox:resource:representations:)`.
**Preconditions:** Tasks 25 have landed, so every `mapResourceToAvatarSizes:` closure at call sites now receives an `EngineMediaResource` (because the facade closures were retyped). At this point the inner `mapResourceToAvatarSizes(postbox: …, resource: …._asResource(), …)` unwrap becomes avoidable.
- [ ] **Step 1: Read the current file**
```
submodules/MapResourceToAvatarSizes/Sources/MapResourceToAvatarSizes.swift
```
Confirm the function body uses `postbox.mediaBox.resourceData(resource)` and requires `UIImage` / `generateScaledImage` / `jpegData(compressionQuality:)`.
- [ ] **Step 2: Enumerate call sites**
```bash
grep -rn "mapResourceToAvatarSizes(postbox:" submodules/ | grep -v "submodules/MapResourceToAvatarSizes"
```
Expected: 27 call sites, concentrated in `submodules/TelegramUI/...PeerInfoScreenAvatarSetup.swift` (19), `TelegramCallsUI/...VideoChatScreenParticipantContextMenu.swift` (5), and three other TelegramUI files (1 each).
- [ ] **Step 3: Rewrite the function to use `EngineMediaResource` + `TelegramEngine.Resources.data`**
Replace the body of `submodules/MapResourceToAvatarSizes/Sources/MapResourceToAvatarSizes.swift` with:
```swift
import Foundation
import UIKit
import SwiftSignalKit
import TelegramCore
import Display
public func mapResourceToAvatarSizes(engine: TelegramEngine, resource: EngineMediaResource, representations: [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError> {
return engine.resources.data(id: resource.id)
|> take(1)
|> map { data -> [Int: Data] in
guard data.isComplete, let image = UIImage(contentsOfFile: data.path) else {
return [:]
}
var result: [Int: Data] = [:]
for i in 0 ..< representations.count {
let size: CGSize
if representations[i].dimensions.width == 80 {
size = CGSize(width: 160.0, height: 160.0)
} else {
size = representations[i].dimensions.cgSize
}
if let scaledImage = generateScaledImage(image: image, size: size, scale: 1.0), let scaledData = scaledImage.jpegData(compressionQuality: 0.8) {
result[i] = scaledData
}
}
return result
}
}
```
Notes:
- Signature: `(engine: TelegramEngine, resource: EngineMediaResource, representations: [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>`.
- `import Postbox` is gone; replaced usage with `engine.resources.data(id:)` which returns `Signal<EngineMediaResource.ResourceData, NoError>`.
- `data.complete``data.isComplete` (field rename on the engine wrapper).
- [ ] **Step 4: Drop the Bazel dep**
Edit `submodules/MapResourceToAvatarSizes/BUILD` and remove `"//submodules/Postbox:Postbox",` from `deps`. Leave the rest untouched.
- [ ] **Step 5: Update every call site** (same commit)
At each of the 27 sites, two changes:
**Pattern D — the call site already lives inside a `mapResourceToAvatarSizes:` closure on a facade function (post-Task-2/3/4, the closure's `resource` parameter is now `EngineMediaResource`):**
```swift
// Before (from an intermediate state between tasks):
mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource._asResource(), representations: representations)
}
// After:
mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: engine, resource: resource, representations: representations)
}
```
The `engine` value is always reachable at the call site — it's either a stored reference used right above the closure or `context.engine` / `accountContext.engine`. Grep shows every current call site has a `postbox = context.account.postbox` (or similar) just above, so `context.engine` / the adjacent engine reference is in scope.
**Pattern E — direct (non-closure) call with a raw `MediaResource` in scope:**
Rare in the current code, but if you find one, wrap with `EngineMediaResource(rawResource)` at the call.
- [ ] **Step 6: Static checks**
```bash
grep -R "^import Postbox" submodules/MapResourceToAvatarSizes/Sources # expect: empty
grep "submodules/Postbox" submodules/MapResourceToAvatarSizes/BUILD # expect: empty
```
- [ ] **Step 7: Full build**
Run the full build. Expected: PASS.
Likely failure modes:
- A call site's surrounding scope doesn't have an `engine` in context. Fix: use `<nearby-accountContext>.engine` or promote `engine` to a nearby `let`.
- A consumer file passed a non-`EngineMediaResource` into the closure because it wasn't updated by Task 2/3/4. Fix forward (update it now) and record the miss.
- [ ] **Step 8: Commit**
```bash
git add submodules/MapResourceToAvatarSizes/ submodules/TelegramUI/ submodules/TelegramCallsUI/
git commit -m "$(cat <<'EOF'
MapResourceToAvatarSizes: migrate to EngineMediaResource and drop Postbox
Change the signature of mapResourceToAvatarSizes from
(postbox: Postbox, resource: MediaResource, ...) to
(engine: TelegramEngine, resource: EngineMediaResource, ...), using
engine.resources.data(id:) internally. All 27 call sites updated in
this commit. `import Postbox` and the Bazel dep are removed.
Behavior-preserving.
Part of the MediaResource -> EngineMediaResource migration (wave 2).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)"
```
---
## Task 7: Migrate `AuthorizationUI` signal type
**Files:**
- Modify: `submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift`
**Starting inventory:** exactly one reference — `Signal<TelegramMediaResource?, NoError>` at line 1162. AuthorizationUI has six files importing Postbox overall; dropping `import Postbox` from the module as a whole is **not** in scope for this task.
- [ ] **Step 1: Read line 1162 ± 20**
Understand:
- What value is put into the signal? Likely some TelegramMediaResource subclass (e.g. `LocalFileMediaResource`).
- Who consumes the signal downstream? After Tasks 25, any facade that ultimately receives this signal's value (via `updateAccountPhoto`, `uploadedPeerVideo`, etc.) expects `EngineMediaResource`.
- [ ] **Step 2: Change the signal type**
```swift
// Before:
avatarVideo = Signal<TelegramMediaResource?, NoError> { subscriber in
// ... produces a TelegramMediaResource ...
subscriber.putNext(someResource)
}
// After:
avatarVideo = Signal<EngineMediaResource?, NoError> { subscriber in
// ... produces a TelegramMediaResource ...
subscriber.putNext(someResource.flatMap { EngineMediaResource($0) }) // or wrap the non-optional path
}
```
The exact wrapping site depends on where the raw resource flows in. The grep + read from Step 1 tells you.
Downstream, any call site that consumed the raw resource and handed it to an engine facade now has an `EngineMediaResource?` which it can pass directly (post-Tasks 25).
- [ ] **Step 3: Full build**
Run the full build. Expected: PASS.
If the downstream expected a `TelegramMediaResource?` (e.g. for direct Postbox access that wasn't part of Tasks 25), revert this task as `Abandoned — downstream expects raw protocol` with a recorded reason.
- [ ] **Step 4: Commit**
```bash
git add submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift
git commit -m "$(cat <<'EOF'
AuthorizationUI: migrate avatar-video signal type to EngineMediaResource
Single type-reference swap. Downstream engine facades already accept
EngineMediaResource after the Phase-1 migrations. Behavior-preserving.
Part of the MediaResource -> EngineMediaResource migration (wave 2).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)"
```
---
## Task 8: Migrate `SaveToCameraRoll` property type — **ABANDONED**
**Status:** Abandoned in wave 2. No code changes from this task.
**Reason:** The planning-time grep that produced the "one reference" inventory only matched `MediaResource`/`TelegramMediaResource` tokens, not the broader set of Postbox usages. Re-inventorying the module at execution time (`grep -nE "\b(postbox|mediaBox|MediaResource)\b|^import Postbox" submodules/SaveToCameraRoll/Sources/SaveToCameraRoll.swift`) shows three public functions with `postbox: Postbox` in their signatures (`fetchMediaData`, `saveToCameraRoll`, `copyToPasteboard`) plus multiple `postbox.mediaBox.*` calls in their bodies. Per spec rule 2, `Postbox` is an umbrella type that cannot be typealiased, so those public-API signatures cannot be de-Postboxed without editing every caller; and the internal `postbox.mediaBox.*` calls require engine-side wrappers (closer to Task 6's shape) rather than a simple type swap. Scope is a full module-migration wave, not a single type swap — parked for a future wave.
**Original task body (retained for audit trail, do not implement):**
**Files:**
- Modify: `submodules/SaveToCameraRoll/Sources/SaveToCameraRoll.swift`
- Possibly modify: `submodules/SaveToCameraRoll/BUILD`
**Starting inventory:** one reference — `var resource: MediaResource?` at line 19.
- [ ] **Step 1: Read + full grep**
```bash
grep -nE "\b(MediaResource|TelegramMediaResource|postbox|mediaBox|transaction|PostboxView|combinedView)\b|^import Postbox" submodules/SaveToCameraRoll/Sources/SaveToCameraRoll.swift
```
Capture every hit.
- [ ] **Step 2: Abandon-check**
If the grep shows Postbox usages other than the single `MediaResource?` property and an `import Postbox` line, abandon this task with a recorded reason. Do not substitute.
If it shows only the property + import, proceed.
- [ ] **Step 3: Swap the property type + boundary wrap/unwrap**
Change `var resource: MediaResource?` to `var resource: EngineMediaResource?`. At each assignment/use:
- Assignment from a raw resource: `self.resource = EngineMediaResource(rawResource)`; `self.resource = nil` unchanged.
- Read that hands to mediaBox/postbox (if any remains): `self.resource?._asResource()`.
- [ ] **Step 4: Drop `import Postbox` if now unused**
If Step 1 showed `import Postbox` as the only remaining Postbox reference:
- Remove the `import Postbox` line.
- Remove `"//submodules/Postbox:Postbox",` from `submodules/SaveToCameraRoll/BUILD`.
Static checks:
```bash
grep -R "^import Postbox" submodules/SaveToCameraRoll/Sources # expect: empty
grep "submodules/Postbox" submodules/SaveToCameraRoll/BUILD # expect: empty
```
Else skip this step.
- [ ] **Step 5: Full build**
Run the full build. Expected: PASS.
- [ ] **Step 6: Commit**
If the import was removed:
```bash
git add submodules/SaveToCameraRoll/
git commit -m "$(cat <<'EOF'
SaveToCameraRoll: migrate resource property to EngineMediaResource and drop Postbox
Swaps the single MediaResource? property for EngineMediaResource?,
wrapping/unwrapping at boundaries. With the only Postbox reference
gone, removes `import Postbox` and the Bazel dep.
Behavior-preserving.
Part of the MediaResource -> EngineMediaResource migration (wave 2).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)"
```
If the import was kept:
```bash
git add submodules/SaveToCameraRoll/Sources/SaveToCameraRoll.swift
git commit -m "$(cat <<'EOF'
SaveToCameraRoll: migrate resource property to EngineMediaResource
Swaps the single MediaResource? property for EngineMediaResource?,
wrapping/unwrapping at boundaries. import Postbox remains because
other identifiers still need it. Behavior-preserving.
Part of the MediaResource -> EngineMediaResource migration (wave 2).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)"
```
---
## Task 9: Wave-2 completion verification
**Files:** No code changes.
- [ ] **Step 1: Commit-log check**
```bash
git log --oneline master..HEAD # or whatever branch this was executed on
```
Expected commits (some may be absent if tasks abandoned):
- `CLAUDE.md: record TelegramCore-no-UIKit rule and EngineMediaResource migration pattern`
- `TelegramCore: migrate peer-photo facade to EngineMediaResource`
- `TelegramCore: migrate account-photo facade to EngineMediaResource`
- `TelegramCore: migrate updateContactPhoto facade to EngineMediaResource`
- `TelegramCore: migrate Auth.uploadedPeerVideo facade to EngineMediaResource`
- `MapResourceToAvatarSizes: migrate to EngineMediaResource and drop Postbox`
- `AuthorizationUI: migrate avatar-video signal type to EngineMediaResource`
- `SaveToCameraRoll: migrate resource property to EngineMediaResource[...]`
- [ ] **Step 2: Public-API leak check**
```bash
grep -nE "^\s*public func .*: MediaResource|public func .*MediaResource, \[" \
submodules/TelegramCore/Sources/TelegramEngine/
```
Expected: no matches in the facade files touched by Tasks 25 (`TelegramEngine/Peers/TelegramEnginePeers.swift`, `TelegramEngine/AccountData/TelegramEngineAccountData.swift`, `TelegramEngine/Contacts/TelegramEngineContacts.swift`, `TelegramEngine/Auth/TelegramEngineAuth.swift`). Other TelegramEngine files may still leak `MediaResource` — those are for future waves.
- [ ] **Step 3: Final full build from clean state**
Run the full build. Expected: PASS (cached, fast).
- [ ] **Step 4: No commit.** Verification only.
---
## Future waves (not in this plan)
Ranked consumer modules by MediaResource/TelegramMediaResource reference count (from `grep -rE "\\b(MediaResource|TelegramMediaResource)\\b"` over `submodules/<M>/Sources/`, excluding TelegramCore/Postbox). Classifications are preliminary and must be re-audited at the start of each future wave.
| Refs | Module | Future-wave notes |
| --- | --- | --- |
| 2 | ChatPresentationInterfaceState | Public struct field `resource: TelegramMediaResource` — needs caller audit. |
| 2 | ItemListStickerPackItem | Enum case leaks `MediaResource` — needs caller audit. |
| 2 | TelegramCallsUI | Signal<TelegramMediaResource, > locals; mostly type-refs. |
| 3 | LegacyMediaPickerUI | `thumbnailResource: TelegramMediaResource?` internal properties — likely safe. |
| 3 | ReactionSelectionNode | `customEffectResource: MediaResource?` in public func — caller audit. |
| 3 | TelegramAnimatedStickerNode | `public init(postbox: Postbox, resource: MediaResource, …)` + `public convenience init(account: Account, …)` — umbrella-type leaks; needs a paired wave. |
| 4 | GalleryUI | `private func setupStatus(resource: MediaResource)` — internal, 4 files. |
| 5 | StickerResources | Multiple public funcs take `postbox: Postbox, resource: MediaResource` / `mediaBox: MediaBox`. |
| 6 | PhotoResources | Similar to StickerResources; also `securePhoto(account: Account, resource: TelegramMediaResource, …)`. |
| 7 | MediaPlayer | `mediaBox: MediaBox, resource: MediaResource` in public init — umbrella leaks. |
| 7 | WebSearchUI | `thumbnailResource: TelegramMediaResource?` in multiple structs/inits. |
| 8 | AccountContext | Protocol surface — audit carefully. |
| 8 | SoftwareVideo | Public init takes `mediaBox: MediaBox` + `resource: MediaResource`. |
| 12 | LocalMediaResources | Contains `VideoLibraryMediaResource: TelegramMediaResource` — blocked for conformance. |
| 14 | LegacyDataImport | Legacy path; audit scope. |
| 25 | PassportUI | Large surface; break into multiple tasks. |
| 36 | TelegramUI | Umbrella module; never as one wave. |
**Blocked-by-conformance modules, explicitly out of all waves:**
- `submodules/ICloudResources/Sources/ICloudResources.swift``ICloudFileResource`
- `submodules/InstantPageUI/Sources/InstantPageExternalMediaResource.swift``InstantPageExternalMediaResource`
- `submodules/LocalMediaResources/Sources/LocalMediaResources.swift``VideoLibraryMediaResource`
- `submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift``YoutubeEmbedStoryboardMediaResource`
These classes must conform to `TelegramMediaResource` to satisfy the PostboxCoding serialization contract. They remain `import Postbox`.
---
## What's explicitly NOT in this plan
- Adding opt-in `EngineMediaResource` overloads alongside raw-`MediaResource` overloads. The facade is changed in place.
- Touching any class conforming to `TelegramMediaResource`.
- Editing `TelegramUI`, `PassportUI`, `LegacyDataImport`, or the other heavy-ref modules in the Future-waves table beyond what the Phase-1 call-site migrations require.
- Importing UIKit/Display into TelegramCore under any circumstance.
- Modifying `_internal_*` functions in TelegramCore — they stay on raw `MediaResource`.
- Any behavior change, performance tweak, or "while we're here" cleanup.

View file

@ -1159,7 +1159,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
let avatarVideo: Signal<UploadedPeerPhotoData?, NoError>?
if let avatarAsset = avatarAsset as? AVAsset {
let engine = strongSelf.engine
avatarVideo = Signal<TelegramMediaResource?, NoError> { subscriber in
avatarVideo = Signal<EngineMediaResource?, NoError> { subscriber in
let entityRenderer: LegacyPaintEntityRenderer? = avatarAdjustments.flatMap { adjustments in
if let paintingData = adjustments.paintingData, paintingData.hasAnimation {
return LegacyPaintEntityRenderer(postbox: nil, adjustments: adjustments)
@ -1178,7 +1178,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
if let data = try? Data(contentsOf: result.fileURL) {
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
engine.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
subscriber.putNext(resource)
subscriber.putNext(EngineMediaResource(resource))
EngineTempBox.shared.dispose(tempFile)
}

View file

@ -11,7 +11,6 @@ swift_library(
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/Display:Display",
],

View file

@ -1,15 +1,14 @@
import Foundation
import UIKit
import SwiftSignalKit
import Postbox
import TelegramCore
import Display
public func mapResourceToAvatarSizes(postbox: Postbox, resource: MediaResource, representations: [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError> {
return postbox.mediaBox.resourceData(resource)
public func mapResourceToAvatarSizes(engine: TelegramEngine, resource: EngineMediaResource, representations: [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError> {
return engine.resources.data(id: resource.id)
|> take(1)
|> map { data -> [Int: Data] in
guard data.complete, let image = UIImage(contentsOfFile: data.path) else {
guard data.isComplete, let image = UIImage(contentsOfFile: data.path) else {
return [:]
}
var result: [Int: Data] = [:]

View file

@ -497,9 +497,8 @@ extension VideoChatScreenComponent.View {
}
let _ = self.currentAvatarMixin.swap(nil)
let postbox = currentCall.accountContext.account.postbox
self.updateAvatarDisposable.set((currentCall.accountContext.engine.peers.updatePeerPhoto(peerId: peerId, photo: nil, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
return mapResourceToAvatarSizes(engine: currentCall.accountContext.engine, resource: resource, representations: representations)
})
|> deliverOnMainQueue).start())
}
@ -557,11 +556,10 @@ extension VideoChatScreenComponent.View {
self.currentUpdatingAvatar = (representation, 0.0)
let postbox = currentCall.accountContext.account.postbox
let signal = peerId.namespace == Namespaces.Peer.CloudUser ? currentCall.accountContext.engine.accountData.updateAccountPhoto(resource: resource, videoResource: nil, videoStartTimestamp: nil, markup: nil, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
}) : currentCall.accountContext.engine.peers.updatePeerPhoto(peerId: peerId, photo: currentCall.accountContext.engine.peers.uploadedPeerPhoto(resource: resource), mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
let signal = peerId.namespace == Namespaces.Peer.CloudUser ? currentCall.accountContext.engine.accountData.updateAccountPhoto(resource: EngineMediaResource(resource), videoResource: nil, videoStartTimestamp: nil, markup: nil, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: currentCall.accountContext.engine, resource: resource, representations: representations)
}) : currentCall.accountContext.engine.peers.updatePeerPhoto(peerId: peerId, photo: currentCall.accountContext.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(resource)), mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: currentCall.accountContext.engine, resource: resource, representations: representations)
})
self.updateAvatarDisposable.set((signal
@ -693,12 +691,12 @@ extension VideoChatScreenComponent.View {
self.updateAvatarDisposable.set((signal
|> mapToSignal { videoResource -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
if peerId.namespace == Namespaces.Peer.CloudUser {
return context.engine.accountData.updateAccountPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: nil, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
return context.engine.accountData.updateAccountPhoto(resource: EngineMediaResource(photoResource), videoResource: EngineMediaResource(videoResource), videoStartTimestamp: videoStartTimestamp, markup: nil, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations)
})
} else {
return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: photoResource), video: context.engine.peers.uploadedPeerVideo(resource: videoResource) |> map(Optional.init), videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(photoResource)), video: context.engine.peers.uploadedPeerVideo(resource: EngineMediaResource(videoResource)) |> map(Optional.init), videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations)
})
}
}

View file

@ -55,8 +55,10 @@ public extension TelegramEngine {
return _internal_registerNotificationToken(account: self.account, token: token, type: type, sandbox: sandbox, otherAccountUserIds: otherAccountUserIds, excludeMutedChats: excludeMutedChats)
}
public func updateAccountPhoto(resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updateAccountPhoto(account: self.account, resource: resource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, fallback: false, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
public func updateAccountPhoto(resource: EngineMediaResource?, videoResource: EngineMediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mapResourceToAvatarSizes: @escaping (EngineMediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updateAccountPhoto(account: self.account, resource: resource?._asResource(), videoResource: videoResource?._asResource(), videoStartTimestamp: videoStartTimestamp, markup: markup, fallback: false, mapResourceToAvatarSizes: { rawResource, representations in
return mapResourceToAvatarSizes(EngineMediaResource(rawResource), representations)
})
}
public func updatePeerPhotoExisting(reference: TelegramMediaImageReference) -> Signal<TelegramMediaImage?, NoError> {
@ -67,8 +69,10 @@ public extension TelegramEngine {
return _internal_removeAccountPhoto(account: self.account, reference: reference, fallback: false)
}
public func updateFallbackPhoto(resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updateAccountPhoto(account: self.account, resource: resource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, fallback: true, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
public func updateFallbackPhoto(resource: EngineMediaResource?, videoResource: EngineMediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mapResourceToAvatarSizes: @escaping (EngineMediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updateAccountPhoto(account: self.account, resource: resource?._asResource(), videoResource: videoResource?._asResource(), videoStartTimestamp: videoStartTimestamp, markup: markup, fallback: true, mapResourceToAvatarSizes: { rawResource, representations in
return mapResourceToAvatarSizes(EngineMediaResource(rawResource), representations)
})
}
public func removeFallbackPhoto(reference: TelegramMediaImageReference?) -> Signal<Void, NoError> {

View file

@ -48,8 +48,8 @@ public extension TelegramEngineUnauthorized {
return _internal_resendTwoStepRecoveryEmail(network: self.account.network)
}
public func uploadedPeerVideo(resource: MediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
return _internal_uploadedPeerVideo(postbox: self.account.postbox, network: self.account.network, messageMediaPreuploadManager: nil, resource: resource)
public func uploadedPeerVideo(resource: EngineMediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
return _internal_uploadedPeerVideo(postbox: self.account.postbox, network: self.account.network, messageMediaPreuploadManager: nil, resource: resource._asResource())
}
public func reportMissingCode(phoneNumber: String, phoneCodeHash: String, mnc: String) -> Signal<Never, ReportMissingCodeError> {

View file

@ -30,8 +30,10 @@ public extension TelegramEngine {
return _internal_updateContactName(account: self.account, peerId: peerId, firstName: firstName, lastName: lastName)
}
public func updateContactPhoto(peerId: PeerId, resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mode: SetCustomPeerPhotoMode, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updateContactPhoto(account: self.account, peerId: peerId, resource: resource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: mode, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
public func updateContactPhoto(peerId: PeerId, resource: EngineMediaResource?, videoResource: EngineMediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mode: SetCustomPeerPhotoMode, mapResourceToAvatarSizes: @escaping (EngineMediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updateContactPhoto(account: self.account, peerId: peerId, resource: resource?._asResource(), videoResource: videoResource?._asResource(), videoStartTimestamp: videoStartTimestamp, markup: markup, mode: mode, mapResourceToAvatarSizes: { rawResource, representations in
return mapResourceToAvatarSizes(EngineMediaResource(rawResource), representations)
})
}
public func updateContactNote(peerId: PeerId, text: String, entities: [MessageTextEntity]) -> Signal<Never, UpdateContactNoteError> {

View file

@ -701,16 +701,18 @@ public extension TelegramEngine {
return _internal_removeRecentlyUsedApp(account: self.account, peerId: peerId)
}
public func uploadedPeerPhoto(resource: MediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
return _internal_uploadedPeerPhoto(postbox: self.account.postbox, network: self.account.network, resource: resource)
public func uploadedPeerPhoto(resource: EngineMediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
return _internal_uploadedPeerPhoto(postbox: self.account.postbox, network: self.account.network, resource: resource._asResource())
}
public func uploadedPeerVideo(resource: MediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
return _internal_uploadedPeerVideo(postbox: self.account.postbox, network: self.account.network, messageMediaPreuploadManager: self.account.messageMediaPreuploadManager, resource: resource)
public func uploadedPeerVideo(resource: EngineMediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
return _internal_uploadedPeerVideo(postbox: self.account.postbox, network: self.account.network, messageMediaPreuploadManager: self.account.messageMediaPreuploadManager, resource: resource._asResource())
}
public func updatePeerPhoto(peerId: PeerId, photo: Signal<UploadedPeerPhotoData, NoError>?, video: Signal<UploadedPeerPhotoData?, NoError>? = nil, videoStartTimestamp: Double? = nil, markup: UploadPeerPhotoMarkup? = nil, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updatePeerPhoto(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, accountPeerId: self.account.peerId, peerId: peerId, photo: photo, video: video, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
public func updatePeerPhoto(peerId: PeerId, photo: Signal<UploadedPeerPhotoData, NoError>?, video: Signal<UploadedPeerPhotoData?, NoError>? = nil, videoStartTimestamp: Double? = nil, markup: UploadPeerPhotoMarkup? = nil, mapResourceToAvatarSizes: @escaping (EngineMediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updatePeerPhoto(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, accountPeerId: self.account.peerId, peerId: peerId, photo: photo, video: video, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { rawResource, representations in
return mapResourceToAvatarSizes(EngineMediaResource(rawResource), representations)
})
}
public func requestUpdateChatListFilter(id: Int32, filter: ChatListFilter?) -> Signal<Never, RequestUpdateChatListFilterError> {

View file

@ -5267,7 +5267,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
return
}
strongSelf.updateAvatarDisposable.set((strongSelf.context.engine.contacts.updateContactPhoto(peerId: strongSelf.peerId, resource: nil, videoResource: nil, videoStartTimestamp: nil, markup: nil, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
mapResourceToAvatarSizes(postbox: strongSelf.context.account.postbox, resource: resource, representations: representations)
mapResourceToAvatarSizes(engine: strongSelf.context.engine, resource: resource, representations: representations)
})
|> deliverOnMainQueue).startStrict(next: { [weak self] _ in
guard let strongSelf = self else {

View file

@ -245,11 +245,10 @@ public extension PeerInfoScreenImpl {
return
}
let postbox = context.account.postbox
let signal: Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError>
signal = context.engine.peers.updatePeerPhoto(peerId: peer.id, photo: context.engine.peers.uploadedPeerPhoto(resource: resource), mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
signal = context.engine.peers.updatePeerPhoto(peerId: peer.id, photo: context.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(resource)), mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations)
})
var dismissStatus: (() -> Void)?
@ -409,8 +408,8 @@ public extension PeerInfoScreenImpl {
let updateAvatarDisposable = MetaDisposable()
updateAvatarDisposable.set((videoResource
|> mapToSignal { videoResource -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: photoResource), video: videoResource.flatMap { context.engine.peers.uploadedPeerVideo(resource: $0) |> map(Optional.init) }, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(photoResource)), video: videoResource.flatMap { context.engine.peers.uploadedPeerVideo(resource: EngineMediaResource($0)) |> map(Optional.init) }, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations)
})
}
|> deliverOnMainQueue).startStandalone(next: { result in
@ -420,7 +419,7 @@ public extension PeerInfoScreenImpl {
case let .progress(value):
uploadStatus?.set(.single(.progress(value)))
}
if case .complete = result {
dismissStatus?()
}
@ -699,11 +698,10 @@ extension PeerInfoScreenImpl {
}
}
}
let postbox = strongSelf.context.account.postbox
let signal: Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError>
if case .custom = mode {
signal = strongSelf.context.engine.contacts.updateContactPhoto(peerId: strongSelf.peerId, resource: nil, videoResource: nil, videoStartTimestamp: nil, markup: nil, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
return mapResourceToAvatarSizes(engine: strongSelf.context.engine, resource: resource, representations: representations)
})
} else if case .fallback = mode {
signal = strongSelf.context.engine.accountData.removeFallbackPhoto(reference: nil)
@ -713,7 +711,7 @@ extension PeerInfoScreenImpl {
}
} else {
signal = strongSelf.context.engine.peers.updatePeerPhoto(peerId: strongSelf.peerId, photo: nil, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
return mapResourceToAvatarSizes(engine: strongSelf.context.engine, resource: resource, representations: representations)
})
}
strongSelf.controllerNode.updateAvatarDisposable.set((signal
@ -794,29 +792,28 @@ extension PeerInfoScreenImpl {
return
}
let postbox = self.context.account.postbox
let signal: Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError>
if self.isSettings || self.isMyProfile {
if case .fallback = mode {
signal = self.context.engine.accountData.updateFallbackPhoto(resource: resource, videoResource: nil, videoStartTimestamp: nil, markup: nil, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
signal = self.context.engine.accountData.updateFallbackPhoto(resource: EngineMediaResource(resource), videoResource: nil, videoStartTimestamp: nil, markup: nil, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: self.context.engine, resource: resource, representations: representations)
})
} else {
signal = self.context.engine.accountData.updateAccountPhoto(resource: resource, videoResource: nil, videoStartTimestamp: nil, markup: nil, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
signal = self.context.engine.accountData.updateAccountPhoto(resource: EngineMediaResource(resource), videoResource: nil, videoStartTimestamp: nil, markup: nil, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: self.context.engine, resource: resource, representations: representations)
})
}
} else if case .custom = mode {
signal = self.context.engine.contacts.updateContactPhoto(peerId: self.peerId, resource: resource, videoResource: nil, videoStartTimestamp: nil, markup: nil, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
signal = self.context.engine.contacts.updateContactPhoto(peerId: self.peerId, resource: EngineMediaResource(resource), videoResource: nil, videoStartTimestamp: nil, markup: nil, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: self.context.engine, resource: resource, representations: representations)
})
} else if case .suggest = mode {
signal = self.context.engine.contacts.updateContactPhoto(peerId: self.peerId, resource: resource, videoResource: nil, videoStartTimestamp: nil, markup: nil, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
signal = self.context.engine.contacts.updateContactPhoto(peerId: self.peerId, resource: EngineMediaResource(resource), videoResource: nil, videoStartTimestamp: nil, markup: nil, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: self.context.engine, resource: resource, representations: representations)
})
} else {
signal = self.context.engine.peers.updatePeerPhoto(peerId: self.peerId, photo: self.context.engine.peers.uploadedPeerPhoto(resource: resource), mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
signal = self.context.engine.peers.updatePeerPhoto(peerId: self.peerId, photo: self.context.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(resource)), mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: self.context.engine, resource: resource, representations: representations)
})
}
@ -1020,25 +1017,25 @@ extension PeerInfoScreenImpl {
|> mapToSignal { videoResource -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
if isSettings || isMyProfile {
if case .fallback = mode {
return context.engine.accountData.updateFallbackPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
return context.engine.accountData.updateFallbackPhoto(resource: EngineMediaResource(photoResource), videoResource: videoResource.flatMap(EngineMediaResource.init), videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations)
})
} else {
return context.engine.accountData.updateAccountPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
return context.engine.accountData.updateAccountPhoto(resource: EngineMediaResource(photoResource), videoResource: videoResource.flatMap(EngineMediaResource.init), videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations)
})
}
} else if case .custom = mode {
return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: EngineMediaResource(photoResource), videoResource: videoResource.flatMap(EngineMediaResource.init), videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations)
})
} else if case .suggest = mode {
return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: EngineMediaResource(photoResource), videoResource: videoResource.flatMap(EngineMediaResource.init), videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations)
})
} else {
return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: photoResource), video: videoResource.flatMap { context.engine.peers.uploadedPeerVideo(resource: $0) |> map(Optional.init) }, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(photoResource)), video: videoResource.flatMap { context.engine.peers.uploadedPeerVideo(resource: EngineMediaResource($0)) |> map(Optional.init) }, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations)
})
}
}
@ -1234,25 +1231,25 @@ extension PeerInfoScreenImpl {
|> mapToSignal { videoResource -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
if isSettings || isMyProfile {
if case .fallback = mode {
return context.engine.accountData.updateFallbackPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
return context.engine.accountData.updateFallbackPhoto(resource: EngineMediaResource(photoResource), videoResource: videoResource.flatMap(EngineMediaResource.init), videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations)
})
} else {
return context.engine.accountData.updateAccountPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
return context.engine.accountData.updateAccountPhoto(resource: EngineMediaResource(photoResource), videoResource: videoResource.flatMap(EngineMediaResource.init), videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations)
})
}
} else if case .custom = mode {
return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: EngineMediaResource(photoResource), videoResource: videoResource.flatMap(EngineMediaResource.init), videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations)
})
} else if case .suggest = mode {
return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: EngineMediaResource(photoResource), videoResource: videoResource.flatMap(EngineMediaResource.init), videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations)
})
} else {
return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: photoResource), video: videoResource.flatMap { context.engine.peers.uploadedPeerVideo(resource: $0) |> map(Optional.init) }, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(photoResource)), video: videoResource.flatMap { context.engine.peers.uploadedPeerVideo(resource: EngineMediaResource($0)) |> map(Optional.init) }, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations)
})
}
}

View file

@ -439,7 +439,7 @@ public func createChannelController(context: AccountContext, mode: CreateChannel
}
if let _ = updatingAvatar {
let _ = context.engine.peers.updatePeerPhoto(peerId: peerId, photo: uploadedAvatar.get(), video: uploadedVideoAvatar?.0.get(), videoStartTimestamp: uploadedVideoAvatar?.1, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations)
return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations)
}).start()
}
@ -503,7 +503,7 @@ public func createChannelController(context: AccountContext, mode: CreateChannel
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)
uploadedAvatar.set(context.engine.peers.uploadedPeerPhoto(resource: resource))
uploadedAvatar.set(context.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(resource)))
uploadedVideoAvatar = nil
updateState { current in
var current = current
@ -605,8 +605,8 @@ public func createChannelController(context: AccountContext, mode: CreateChannel
}
}
uploadedAvatar.set(context.engine.peers.uploadedPeerPhoto(resource: photoResource))
uploadedAvatar.set(context.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(photoResource)))
let promise = Promise<UploadedPeerPhotoData?>()
promise.set(signal
|> `catch` { _ -> Signal<TelegramMediaResource?, NoError> in
@ -614,7 +614,7 @@ public func createChannelController(context: AccountContext, mode: CreateChannel
}
|> mapToSignal { resource -> Signal<UploadedPeerPhotoData?, NoError> in
if let resource = resource {
return context.engine.peers.uploadedPeerVideo(resource: resource) |> map(Optional.init)
return context.engine.peers.uploadedPeerVideo(resource: EngineMediaResource(resource)) |> map(Optional.init)
} else {
return .single(nil)
}

View file

@ -805,7 +805,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
}
if let _ = updatingAvatar {
return context.engine.peers.updatePeerPhoto(peerId: result.peerId, photo: uploadedAvatar.get(), video: uploadedVideoAvatar?.0.get(), videoStartTimestamp: uploadedVideoAvatar?.1, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations)
return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations)
})
|> ignoreValues
|> `catch` { _ -> Signal<Never, CreateGroupError> in
@ -922,7 +922,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)
uploadedAvatar.set(context.engine.peers.uploadedPeerPhoto(resource: resource))
uploadedAvatar.set(context.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(resource)))
uploadedVideoAvatar = nil
updateState { current in
var current = current
@ -1024,8 +1024,8 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
}
}
uploadedAvatar.set(context.engine.peers.uploadedPeerPhoto(resource: photoResource))
uploadedAvatar.set(context.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(photoResource)))
let promise = Promise<UploadedPeerPhotoData?>()
promise.set(signal
|> `catch` { _ -> Signal<TelegramMediaResource?, NoError> in
@ -1033,7 +1033,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
}
|> mapToSignal { resource -> Signal<UploadedPeerPhotoData?, NoError> in
if let resource = resource {
return context.engine.peers.uploadedPeerVideo(resource: resource) |> map(Optional.init)
return context.engine.peers.uploadedPeerVideo(resource: EngineMediaResource(resource)) |> map(Optional.init)
} else {
return .single(nil)
}