Files
Qlyra/lib/models/poll.dart
T
klockky e3635b5d29 feat: полное отображение типов сообщений + предпросмотр ссылок и оптимизация фото
Модели (lib/models/):
- attachment.dart — новые типы ShareAttachment (OG-карточка ссылки) и
  CallAttachment (звонок); FILE получил вложенное превью и поле token;
  LOCATION — поле zoom; единый декодер previewData (сырой WebP → data-URI)
  вынесен в decodeAttachPreview
- poll.dart — корректный парсинг голосов ({userId,timestamp}) и признак
  своего голоса (options & 1); helper withStateMap

Лента сообщений (message_bubble.dart):
- SHARE — карточка предпросмотра ссылки (картинка, домен, заголовок,
  описание), тап открывает URL
- CALL — отображение звонков: аудио/видео/групповые/пропущенные,
  длительность, направление; симметричная иконка
- LOCATION — карточка геопозиции, тап открывает карту
- FILE — превью-картинка над строкой файла
- кликабельные ссылки в тексте и подписях (LinkText)
- фикс лишней ширины бабблов гео/share/опроса (IntrinsicWidth + stretch)
- оптимизация рендера фото: убран fade-in, уменьшен размер декода
  плиток фото-сетки

Опросы:
- интерактивное голосование через opcode 304 (polls.dart, poll_view.dart):
  одиночный/множественный выбор, отметка своего ответа, проценты

Список чатов (chats.dart):
- превью типа вложения для сообщений без текста («Фото», «Опрос: …»,
  «Звонок», «Геопозиция», «Файл: …», «Ссылка: …» и т.д.)

Настройки разработчика:
- тумблер «Предпросмотр ссылок» (app_link_preview.dart) — отключает
  SHARE-карточки, оставляя текст со ссылкой; применяется на лету

Прочее:
- зависимость url_launcher ^6.3.1 (open_external_url / open_location_on_map)
- bump версии 0.5.0+11 → 0.5.0+12
2026-06-11 17:14:36 +03:00

116 lines
2.9 KiB
Dart

class PollAnswer {
final int answerId;
final String text;
final int voteCount;
final double rate;
final List<int> votes;
final bool mine;
const PollAnswer({
required this.answerId,
required this.text,
this.voteCount = 0,
this.rate = 0,
this.votes = const [],
this.mine = false,
});
}
class Poll {
final int pollId;
final String title;
final int settings;
final int version;
final int total;
final List<PollAnswer> answers;
final List<int> voterPreviewIds;
const Poll({
required this.pollId,
required this.title,
this.settings = 0,
this.version = 0,
this.total = 0,
this.answers = const [],
this.voterPreviewIds = const [],
});
bool get isMultiple => settings & 0x1 != 0;
bool get hasMyVote => answers.any((a) => a.mine);
bool votedBy(int userId) =>
answers.any((a) => a.mine || a.votes.contains(userId));
static List<int> _parseVoterIds(dynamic votes) {
if (votes is! List) return const [];
final ids = <int>[];
for (final v in votes) {
if (v is int) {
ids.add(v);
} else if (v is Map && v['userId'] is int) {
ids.add(v['userId'] as int);
}
}
return ids;
}
Poll withStateMap(Map<dynamic, dynamic> stateMap) {
return Poll.fromServerMap({
'pollId': pollId,
'title': title,
'settings': settings,
'version': version,
'answers': [
for (final a in answers) {'answerId': a.answerId, 'text': a.text},
],
'state': stateMap,
});
}
factory Poll.fromServerMap(Map<dynamic, dynamic> map) {
final state = map['state'];
final stateMap = state is Map ? state : const {};
final resultsById = <int, Map>{};
final result = stateMap['result'];
if (result is List) {
for (final r in result) {
if (r is Map && r['answerId'] is int) {
resultsById[r['answerId'] as int] = r;
}
}
}
final answers = <PollAnswer>[];
final rawAnswers = map['answers'];
if (rawAnswers is List) {
for (final a in rawAnswers) {
if (a is! Map) continue;
final id = a['answerId'] as int? ?? 0;
final res = resultsById[id];
answers.add(PollAnswer(
answerId: id,
text: a['text']?.toString() ?? '',
voteCount: (res?['voteCount'] as num?)?.toInt() ?? 0,
rate: (res?['rate'] as num?)?.toDouble() ?? 0,
votes: _parseVoterIds(res?['votes']),
mine: ((res?['options'] as num?)?.toInt() ?? 0) & 0x1 != 0,
));
}
}
return Poll(
pollId: map['pollId'] as int? ?? 0,
title: map['title']?.toString() ?? '',
settings: map['settings'] as int? ?? 0,
version: map['version'] as int? ?? 0,
total: (stateMap['total'] as num?)?.toInt() ?? 0,
answers: answers,
voterPreviewIds:
(stateMap['voterPreviewIds'] as List?)?.whereType<int>().toList() ??
const [],
);
}
}