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 _cache = {}; final Map> stickersByPack = {}; List get stickers => _cache.values.where((ele) => ele != null).cast().toList(); SnStickerProvider(BuildContext context) { _sn = context.read(); _dt = context.read(); } bool hasNotSticker(String alias) { return _cache.containsKey(alias) && _cache[alias] == null; } void _cacheSticker(SnSticker sticker) { _cache['${sticker.pack.prefix}:${sticker.alias}'] = sticker; if (stickersByPack[sticker.pack.id] == null) { stickersByPack[sticker.pack.id] = List.empty(growable: true); } if (!stickersByPack[sticker.pack.id]!.contains(sticker)) { stickersByPack[sticker.pack.id]!.add(sticker); } } void putSticker(Iterable stickers) { for (final ele in stickers) { _cacheSticker(ele); } _saveStickerToLocal(stickers); _saveStickerPackToLocal(stickers.map((ele) => ele.pack).toSet()); } Future 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); putSticker([sticker]); return sticker; } catch (err) { _cache[alias] = null; logging.warning('[Sticker] Failed to lookup sticker $alias', err); } return null; } Future 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; final stickers = List.from(data).map((ele) => SnSticker.fromJson(ele)); for (final sticker in stickers) { _cacheSticker(sticker); } } catch (err) { logging.error('[Sticker] Failed to list stickers...', err); rethrow; } } Future _saveStickerToLocal(Iterable 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 _saveStickerPackToLocal(Iterable 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); } }