feat: реакции, так еще ебать и анимированные!!!!!! правда лагает пизда я это потом пофиксю мб как в тг сделаю
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import '../api.dart';
|
||||
import '../../core/protocol/opcode_map.dart';
|
||||
import '../../core/utils/logger.dart';
|
||||
import '../../models/animoji.dart';
|
||||
|
||||
class AnimojiModule {
|
||||
final Api _api;
|
||||
|
||||
AnimojiModule(this._api);
|
||||
|
||||
static const List<String> fallbackReactions = [
|
||||
'👍',
|
||||
'❤️',
|
||||
'🔥',
|
||||
'🤣',
|
||||
'😭',
|
||||
'😍',
|
||||
];
|
||||
|
||||
final Map<int, Animoji> _byId = {};
|
||||
List<int> _orderedIds = [];
|
||||
Future<void>? _loading;
|
||||
|
||||
bool get isLoaded => _orderedIds.isNotEmpty;
|
||||
|
||||
List<Animoji> get animojis =>
|
||||
_orderedIds.map((id) => _byId[id]).whereType<Animoji>().toList();
|
||||
|
||||
List<String> get emojis => animojis.map((a) => a.emoji).toList();
|
||||
|
||||
List<Animoji> get quickAnimojis {
|
||||
final list = animojis;
|
||||
return list.length <= 6 ? list : list.sublist(0, 6);
|
||||
}
|
||||
|
||||
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) _byId[animoji.id] = animoji;
|
||||
}
|
||||
}
|
||||
|
||||
_orderedIds = ids.where(_byId.containsKey).toList();
|
||||
logger.i('Анимодзи: ${_orderedIds.length} доступно для реакций');
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import '../../core/config/komet_settings.dart';
|
||||
import '../../core/protocol/opcode_map.dart';
|
||||
import '../../core/protocol/packet.dart';
|
||||
import '../../core/storage/app_database.dart';
|
||||
import '../../core/storage/token_storage.dart';
|
||||
import '../../core/utils/logger.dart';
|
||||
import '../../core/utils/text_format.dart';
|
||||
import '../../models/attachment.dart';
|
||||
@@ -393,6 +394,7 @@ class CachedMessage {
|
||||
bool? deleted,
|
||||
List<MessageAttachment>? attachments,
|
||||
List<Map<String, dynamic>>? editHistory,
|
||||
Map<String, dynamic>? payload,
|
||||
}) => CachedMessage(
|
||||
id: id,
|
||||
accountId: accountId,
|
||||
@@ -401,7 +403,7 @@ class CachedMessage {
|
||||
text: text,
|
||||
time: time,
|
||||
status: status ?? this.status,
|
||||
payload: payload,
|
||||
payload: payload ?? this.payload,
|
||||
attachments: attachments ?? this.attachments,
|
||||
isControl: isControl,
|
||||
deleted: deleted ?? this.deleted,
|
||||
@@ -1055,6 +1057,107 @@ class MessagesModule {
|
||||
return _api.sendRequestOk(Opcode.msgDelete, payload);
|
||||
}
|
||||
|
||||
Future<({bool ok, Map<String, dynamic>? info})> setReaction(
|
||||
int chatId,
|
||||
String messageId,
|
||||
String emoji,
|
||||
) async {
|
||||
final id = int.tryParse(messageId);
|
||||
if (id == null) return (ok: false, info: null);
|
||||
final response = await _api.sendRequest(Opcode.msgReaction, {
|
||||
'chatId': chatId,
|
||||
'messageId': id,
|
||||
'reaction': {'reactionType': 'EMOJI', 'id': emoji},
|
||||
});
|
||||
return _applyReactionResponse(chatId, messageId, response);
|
||||
}
|
||||
|
||||
Future<({bool ok, Map<String, dynamic>? info})> cancelReaction(
|
||||
int chatId,
|
||||
String messageId,
|
||||
) async {
|
||||
final id = int.tryParse(messageId);
|
||||
if (id == null) return (ok: false, info: null);
|
||||
final response = await _api.sendRequest(Opcode.msgCancelReaction, {
|
||||
'chatId': chatId,
|
||||
'messageId': id,
|
||||
});
|
||||
return _applyReactionResponse(chatId, messageId, response);
|
||||
}
|
||||
|
||||
Future<({bool ok, Map<String, dynamic>? info})> _applyReactionResponse(
|
||||
int chatId,
|
||||
String messageId,
|
||||
Packet response,
|
||||
) async {
|
||||
if (!response.isOk) return (ok: false, info: null);
|
||||
final payload = response.payload;
|
||||
final info = payload is Map
|
||||
? _normalizeReactionInfo(payload['reactionInfo'])
|
||||
: null;
|
||||
try {
|
||||
await _persistReaction(chatId, messageId, info);
|
||||
} catch (_) {}
|
||||
return (ok: true, info: info);
|
||||
}
|
||||
|
||||
static Map<String, dynamic>? _normalizeReactionInfo(dynamic raw) {
|
||||
if (raw is! Map) return null;
|
||||
final rawCounters = raw['counters'];
|
||||
if (rawCounters is! List) return null;
|
||||
final counters = <Map<String, dynamic>>[];
|
||||
for (final c in rawCounters) {
|
||||
if (c is! Map) continue;
|
||||
final reaction = c['reaction']?.toString();
|
||||
if (reaction == null || reaction.isEmpty) continue;
|
||||
final count = c['count'];
|
||||
counters.add({'reaction': reaction, 'count': count is int ? count : 0});
|
||||
}
|
||||
if (counters.isEmpty) return null;
|
||||
final your = raw['yourReaction']?.toString();
|
||||
final total = raw['totalCount'];
|
||||
return {
|
||||
'counters': counters,
|
||||
if (your != null && your.isNotEmpty) 'yourReaction': your,
|
||||
'totalCount': total is int
|
||||
? total
|
||||
: counters.fold<int>(0, (a, b) => a + (b['count'] as int)),
|
||||
};
|
||||
}
|
||||
|
||||
Future<void> _persistReaction(
|
||||
int chatId,
|
||||
String messageId,
|
||||
Map<String, dynamic>? info,
|
||||
) async {
|
||||
final accountId = await TokenStorage.getActiveAccountId();
|
||||
if (accountId == null) return;
|
||||
final existing = await AppDatabase.loadMessage(accountId, chatId, messageId);
|
||||
if (existing == null) return;
|
||||
|
||||
Map<String, dynamic> payloadMap;
|
||||
final raw = existing['payload'];
|
||||
if (raw is String && raw.isNotEmpty) {
|
||||
try {
|
||||
payloadMap = Map<String, dynamic>.from(jsonDecode(raw) as Map);
|
||||
} catch (_) {
|
||||
payloadMap = <String, dynamic>{};
|
||||
}
|
||||
} else {
|
||||
payloadMap = <String, dynamic>{};
|
||||
}
|
||||
|
||||
if (info == null) {
|
||||
payloadMap.remove('reactionInfo');
|
||||
} else {
|
||||
payloadMap['reactionInfo'] = info;
|
||||
}
|
||||
|
||||
final newRow = Map<String, dynamic>.from(existing);
|
||||
newRow['payload'] = jsonEncode(payloadMap);
|
||||
await AppDatabase.saveMessages([newRow]);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> sendButtonCallback({
|
||||
required int chatId,
|
||||
required String messageId,
|
||||
|
||||
Reference in New Issue
Block a user