From 6235e736b9b1a6d44cea51954eae3453579e3f97 Mon Sep 17 00:00:00 2001
From: LittleSheep <littlesheep.code@hotmail.com>
Date: Tue, 4 Mar 2025 23:56:39 +0800
Subject: [PATCH] :zap: Sticker cache

---
 lib/providers/sn_sticker.dart | 70 ++++++++++++++++++++++++++++++++---
 lib/screens/stickers.dart     |  5 ++-
 2 files changed, 69 insertions(+), 6 deletions(-)

diff --git a/lib/providers/sn_sticker.dart b/lib/providers/sn_sticker.dart
index 4a4cc78..df015dd 100644
--- a/lib/providers/sn_sticker.dart
+++ b/lib/providers/sn_sticker.dart
@@ -1,11 +1,17 @@
+import 'dart:convert';
+
+import 'package:drift/drift.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
+import 'package:surface/database/database.dart';
 import 'package:surface/logger.dart';
+import 'package:surface/providers/database.dart';
 import 'package:surface/providers/sn_network.dart';
 import 'package:surface/types/attachment.dart';
 
 class SnStickerProvider {
   late final SnNetworkProvider _sn;
+  late final DatabaseProvider _dt;
   final Map<String, SnSticker?> _cache = {};
 
   final Map<int, List<SnSticker>> stickersByPack = {};
@@ -15,6 +21,7 @@ class SnStickerProvider {
 
   SnStickerProvider(BuildContext context) {
     _sn = context.read<SnNetworkProvider>();
+    _dt = context.read<DatabaseProvider>();
   }
 
   bool hasNotSticker(String alias) {
@@ -31,22 +38,32 @@ class SnStickerProvider {
     }
   }
 
-  void putSticker(Iterable<SnSticker> sticker) {
-    for (final ele in sticker) {
+  void putSticker(Iterable<SnSticker> stickers) {
+    for (final ele in stickers) {
       _cacheSticker(ele);
     }
+    _saveStickerToLocal(stickers);
+    _saveStickerPackToLocal(stickers.map((ele) => ele.pack).toSet());
   }
 
   Future<SnSticker?> lookupSticker(String alias) async {
+    // In-memory cache
     if (_cache.containsKey(alias)) {
       return _cache[alias];
     }
-
+    // On-disk cache
+    final localStickers = await (_dt.db.snLocalSticker.select()
+          ..where((e) => e.fullAlias.equals(alias)))
+        .getSingleOrNull();
+    if (localStickers != null) {
+      _cache[alias] = localStickers.content;
+      return localStickers.content;
+    }
+    // Remote server
     try {
       final resp = await _sn.client.get('/cgi/uc/stickers/lookup/$alias');
       final sticker = SnSticker.fromJson(resp.data);
-      _cacheSticker(sticker);
-
+      putSticker([sticker]);
       return sticker;
     } catch (err) {
       _cache[alias] = null;
@@ -57,6 +74,18 @@ class SnStickerProvider {
   }
 
   Future<void> listSticker() async {
+    final localPacks = await _dt.db.snLocalStickerPack.select().get();
+    final localStickers = await _dt.db.snLocalSticker.select().get();
+    final local = localStickers.map((ele) {
+      return ele.content.copyWith(
+        pack: localPacks
+            .firstWhere((pk) => pk.content.id == ele.content.packId)
+            .content,
+      );
+    });
+    for (final sticker in local) {
+      _cacheSticker(sticker);
+    }
     try {
       final resp = await _sn.client.get('/cgi/uc/stickers');
       final data = resp.data;
@@ -69,4 +98,35 @@ class SnStickerProvider {
       rethrow;
     }
   }
+
+  Future<void> _saveStickerToLocal(Iterable<SnSticker> stickers) async {
+    await _dt.db.snLocalSticker.insertAll(
+      stickers.map(
+        (ele) => SnLocalStickerCompanion.insert(
+          id: Value(ele.id),
+          alias: ele.alias,
+          fullAlias: '${ele.pack.prefix}${ele.alias}',
+          content: ele,
+          createdAt: Value(ele.createdAt),
+        ),
+      ),
+      onConflict: DoNothing(),
+    );
+  }
+
+  Future<void> _saveStickerPackToLocal(Iterable<SnStickerPack> packs) async {
+    final queries = packs
+        .map(
+          (ele) => _dt.db.snLocalStickerPack.insertOne(
+              SnLocalStickerPackCompanion.insert(
+                id: Value(ele.id),
+                content: ele,
+                createdAt: Value(ele.createdAt),
+              ),
+              onConflict: DoUpdate((_) => SnLocalStickerPackCompanion.custom(
+                  content: Constant(jsonEncode(ele.toJson()))))),
+        )
+        .toList();
+    await Future.wait(queries);
+  }
 }
diff --git a/lib/screens/stickers.dart b/lib/screens/stickers.dart
index c0a3ae8..475d684 100644
--- a/lib/screens/stickers.dart
+++ b/lib/screens/stickers.dart
@@ -284,7 +284,10 @@ class _StickerPackAddPopupState extends State<_StickerPackAddPopup> {
       );
       if (!mounted) return;
       context.showSnackbar('stickersAdded'.tr());
-      if (_pack?.stickers != null) stickers.putSticker(_pack!.stickers!);
+      if (_pack?.stickers != null) {
+        stickers.putSticker(
+            _pack!.stickers!.map((ele) => ele.copyWith(pack: _pack!)));
+      }
       Navigator.pop(context, true);
     } catch (err) {
       if (!mounted) return;