Files
Qlyra/lib/backend/modules/animoji.dart
T

195 lines
5.3 KiB
Dart

import 'package:shared_preferences/shared_preferences.dart';
import '../api.dart';
import '../../core/protocol/opcode_map.dart';
import '../../core/utils/emoji_keyword_index.dart';
import '../../core/utils/logger.dart';
import '../../models/animoji.dart';
class AnimojiModule {
final Api _api;
AnimojiModule(this._api);
static const List<String> fallbackReactions = [
'👍',
'❤️',
'🔥',
'🤣',
'😭',
'😍',
];
static const String _recentsKey = 'komet_recent_animoji';
static const int _maxRecents = 24;
final Map<int, Animoji> _byId = {};
final Map<String, Animoji> _byEmoji = {};
List<int> _orderedIds = [];
List<int> _recentIds = [];
bool _recentsLoaded = false;
Future<void>? _loading;
bool get isLoaded => _orderedIds.isNotEmpty;
List<Animoji> get animojis =>
_orderedIds.map((id) => _byId[id]).whereType<Animoji>().toList();
List<Animoji> get recentAnimojis =>
_recentIds.map((id) => _byId[id]).whereType<Animoji>().toList();
List<String> get emojis => animojis.map((a) => a.emoji).toList();
Future<void> ensureRecentsLoaded() async {
if (_recentsLoaded) return;
_recentsLoaded = true;
try {
final prefs = await SharedPreferences.getInstance();
final raw = prefs.getStringList(_recentsKey) ?? const [];
_recentIds = raw.map(int.tryParse).whereType<int>().toList();
} catch (_) {}
}
Future<void> noteUsed(Animoji animoji) async {
await ensureRecentsLoaded();
_remember(animoji);
_recentIds.remove(animoji.id);
_recentIds.insert(0, animoji.id);
if (_recentIds.length > _maxRecents) {
_recentIds = _recentIds.sublist(0, _maxRecents);
}
try {
final prefs = await SharedPreferences.getInstance();
await prefs.setStringList(
_recentsKey,
_recentIds.map((e) => e.toString()).toList(),
);
} catch (_) {}
}
List<Animoji> get quickAnimojis {
final list = animojis;
return list.length <= 6 ? list : list.sublist(0, 6);
}
Animoji? cached(int id) => _byId[id];
Animoji? findByEmoji(String emoji) =>
_byEmoji[EmojiKeywordIndex.normalize(emoji)];
Future<Animoji?> fetchById(int id) async {
final known = _byId[id];
if (known != null) return known;
final map = await _api.sendRequestMap(Opcode.assetsGetByIds, {
'type': 'ANIMOJI',
'ids': [id],
});
final list = map?['animojis'];
if (list is! List) return null;
for (final e in list) {
if (e is! Map) continue;
final animoji = Animoji.fromMap(e);
if (animoji != null) _remember(animoji);
}
return _byId[id];
}
Future<void> ensureLoaded() {
return _loading ??= _load().catchError((Object e) {
_loading = null;
throw e;
});
}
Future<void> _load() async {
final setIds = <int>[];
final fallbackIds = <int>[];
final sync = await _api.sendRequestMap(Opcode.assetsUpdate, {
'type': 'ANIMOJI_SET',
'sync': 0,
});
if (sync != null) {
final sections = sync['sections'];
if (sections is List) {
for (final s in sections) {
if (s is Map) _appendIntList(setIds, s['animojiSetIds']);
}
}
final updates = sync['animojiUpdates'];
if (updates is Map) {
for (final key in updates.keys) {
final id = key is int ? key : int.tryParse(key.toString());
if (id != null) fallbackIds.add(id);
}
}
}
final orderedIds = <int>[];
if (setIds.isNotEmpty) {
final setMap = await _api.sendRequestMap(Opcode.assetsGetByIds, {
'type': 'ANIMOJI_SET',
'ids': setIds,
});
if (setMap != null) {
final sets = setMap['animojiSets'];
if (sets is List) {
for (final set in sets) {
if (set is! Map) continue;
_appendIntList(orderedIds, set['animojis']);
_appendIntList(orderedIds, set['animojiIds']);
}
}
}
}
final ids = _dedup(orderedIds.isNotEmpty ? orderedIds : fallbackIds);
if (ids.isEmpty) return;
for (final batch in _chunk(ids, 100)) {
final map = await _api.sendRequestMap(Opcode.assetsGetByIds, {
'type': 'ANIMOJI',
'ids': batch,
});
if (map == null) continue;
final list = map['animojis'];
if (list is! List) continue;
for (final e in list) {
if (e is! Map) continue;
final animoji = Animoji.fromMap(e);
if (animoji != null) _remember(animoji);
}
}
_orderedIds = ids.where(_byId.containsKey).toList();
logger.i('Анимодзи: ${_orderedIds.length} доступно для реакций');
}
void _remember(Animoji animoji) {
_byId[animoji.id] = animoji;
_byEmoji[EmojiKeywordIndex.normalize(animoji.emoji)] = animoji;
}
List<int> _dedup(List<int> ids) {
final seen = <int>{};
final result = <int>[];
for (final id in ids) {
if (seen.add(id)) result.add(id);
}
return result;
}
void _appendIntList(List<int> target, dynamic raw) {
if (raw is! List) return;
for (final e in raw) {
if (e is int) target.add(e);
}
}
Iterable<List<T>> _chunk<T>(List<T> list, int size) sync* {
for (var i = 0; i < list.length; i += size) {
yield list.sublist(i, i + size > list.length ? list.length : i + size);
}
}
}