Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Watch: pre-cache favorite and frequently used stickers
Stickers were downloaded on-demand only when visible and cancelled when
scrolled off-screen, causing them to re-download every time the picker
opened. This adds:

- StickerPickerStore.prefetchStickers() that pre-downloads all favorite
  and recent sticker render files at priority 4 (highest)
- Pinned file IDs protected from cancellation by cancelFileDownload()
- TGBridgeStickersSignals.prefetchRecentStickersWithLimit: that triggers
  the full image download pipeline for recent stickers through TGFileCache
- Re-enabled the in-memory caching layer for recent stickers in the legacy
  WatchKit extension
- TGStickersController now calls prefetch after loading the sticker list
  • Loading branch information
Atlas20204 committed Jul 24, 2026
commit d0760c41f31c95ed870403f6a5bd28defd4caf74
2 changes: 2 additions & 0 deletions Telegram/Watch/Bridge/TGBridgeStickersSignals.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@

+ (NSURL *)stickerPacksURL;

+ (void)prefetchRecentStickersWithLimit:(NSUInteger)limit;

@end
38 changes: 26 additions & 12 deletions Telegram/Watch/Bridge/TGBridgeStickersSignals.m
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#import "TGBridgeStickerPack.h"
#import "TGBridgeClient.h"
#import "TGBridgeMediaSignals.h"
#import "TGWatchCommon.h"

@implementation TGBridgeStickersSignals

Expand All @@ -16,18 +18,17 @@ + (SSignal *)cachedRecentStickers

+ (SSignal *)recentStickersWithLimit:(NSUInteger)limit
{
return [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeRecentStickersSubscription alloc] initWithLimit:limit]];
// return [[self cachedRecentStickers] mapToSignal:^SSignal *(NSArray *stickers) {
// SSignal *remote = [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeRecentStickersSubscription alloc] initWithLimit:limit]];
// remote = [remote onNext:^(NSArray *stickers) {
// cachedStickers = stickers;
// }];
// if (stickers != nil) {
// return [[SSignal single:stickers] then:remote];
// } else {
// return remote;
// }
// }];
return [[self cachedRecentStickers] mapToSignal:^SSignal *(NSArray *stickers) {
SSignal *remote = [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeRecentStickersSubscription alloc] initWithLimit:limit]];
remote = [remote onNext:^(NSArray *stickers) {
cachedStickers = stickers;
}];
if (stickers != nil) {
return [[SSignal single:stickers] then:remote];
} else {
return remote;
}
}];
}

+ (SSignal *)stickerPacks
Expand All @@ -47,4 +48,17 @@ + (NSURL *)stickerPacksURL
return stickerPacksURL;
}

+ (void)prefetchRecentStickersWithLimit:(NSUInteger)limit
{
[[[self recentStickersWithLimit:limit] deliverOn:[SQueue mainQueue]] startWithNext:^(NSArray *stickers)
{
for (TGBridgeDocumentMediaAttachment *sticker in stickers)
{
CGSize stickerSize = TGWatchStickerSizeForScreen(TGWatchScreenType());
NSString *imageUrl = [NSString stringWithFormat:@"sticker_%lld_%dx%d_0", sticker.documentId, (int)stickerSize.width, (int)stickerSize.height];
[[[TGBridgeMediaSignals stickerWithDocumentId:sticker.documentId packId:sticker.stickerPackId accessHash:sticker.stickerPackAccessHash type:TGMediaStickerImageTypeNormal] deliverOn:[SQueue mainQueue]] startWithNext:^(id next) {}];
}
}];
}

@end
1 change: 1 addition & 0 deletions Telegram/Watch/Extension/TGStickersController.m
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ - (void)reloadData
updateInteface(initial, currentStickerModels, true);
}
}]];
[TGBridgeStickersSignals prefetchRecentStickersWithLimit:24];
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ struct PickerSticker: Identifiable, Equatable, Hashable {
let emoji: String
let width: Int
let height: Int

var renderFileId: Int? {
switch render {
case .raster(let id, _): return id
case .lottie(let id): return id
case .none: return nil
}
}
}

/// How to produce a static grid-tile image for a sticker.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ final class StickerPickerStore {
private let logger = Logger(subsystem: "org.telegram.TelegramWatch", category: "stickerpicker")
private var files: [Int: File] = [:]
private var trackedFileIds: Set<Int> = []
private var pinnedFileIds: Set<Int> = []
private var setStickers: [Int64: [PickerSticker]] = [:]

init(loader: StickerPickerLoader) {
Expand Down Expand Up @@ -52,6 +53,7 @@ final class StickerPickerStore {
}

func cancelFileDownload(fileId: Int) {
guard !pinnedFileIds.contains(fileId) else { return }
trackedFileIds.remove(fileId)
logger.info("cancelFileDownload fileId=\(fileId, privacy: .public)")
Task { [logger, loader] in
Expand All @@ -63,6 +65,33 @@ final class StickerPickerStore {
}
}

/// Pre-downloads favorite and recent stickers with high priority so they are
/// cached on disk before the user scrolls to them. Pinned file IDs are
/// protected from cancellation by `cancelFileDownload`.
func prefetchStickers() {
let all = favorites + recents
var renderIds = Set<Int>()
for sticker in all {
if let id = sticker.renderFileId {
renderIds.insert(id)
}
}
guard !renderIds.isEmpty else { return }
let newIds = renderIds.subtracting(trackedFileIds)
pinnedFileIds.formUnion(newIds)
trackedFileIds.formUnion(newIds)
logger.info("prefetchStickers: pinning \(newIds.count, privacy: .public) sticker render files")
for id in newIds {
Task { [logger, loader] in
do {
_ = try await loader.downloadFile(fileId: id, priority: 4)
} catch {
logger.warning("prefetch fileId=\(id, privacy: .public) failed: \(error.localizedDescription, privacy: .public)")
}
}
}
}

/// Fetches favorites, recents, and installed sets concurrently. Each source
/// is independent — one failing source still renders the others. `.failed`
/// only when all three fail. Re-runs on every sheet open (keeps recents/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ struct StickerPickerView: View {
.task {
client.setActiveStickerPicker(store)
await store.load()
store.prefetchStickers()
}
.onDisappear { client.setActiveStickerPicker(nil) }
}
Expand Down