154 lines
4.7 KiB
Dart
154 lines
4.7 KiB
Dart
import 'package:cached_network_image/cached_network_image.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:material_symbols_icons/symbols.dart';
|
|
|
|
import '../../core/media/preview_image.dart';
|
|
import '../../models/attachment.dart';
|
|
|
|
class ReplyPreview {
|
|
const ReplyPreview._({this.icon, this.media, this.round = false});
|
|
|
|
final IconData? icon;
|
|
final MessageAttachment? media;
|
|
final bool round;
|
|
|
|
bool get hasMedia => media != null;
|
|
|
|
static ReplyPreview of({String? text, List<MessageAttachment>? attachments}) {
|
|
final list = attachments;
|
|
if (list == null || list.isEmpty) return const ReplyPreview._();
|
|
final attachment = list.first;
|
|
final icon = _iconFor(attachment.type);
|
|
final captioned = text != null && text.trim().isNotEmpty;
|
|
if (captioned || !_hasThumbnail(attachment)) {
|
|
return ReplyPreview._(icon: icon);
|
|
}
|
|
return ReplyPreview._(
|
|
icon: icon,
|
|
media: attachment,
|
|
round: attachment is VideoAttachment && attachment.isNote,
|
|
);
|
|
}
|
|
|
|
Size box({required double maxSide, double minSide = 72}) {
|
|
final attachment = media;
|
|
if (round) return Size(maxSide, maxSide);
|
|
final width = _sizeOf(attachment, horizontal: true);
|
|
final height = _sizeOf(attachment, horizontal: false);
|
|
if (width == null || height == null || width <= 0 || height <= 0) {
|
|
return Size(maxSide, maxSide);
|
|
}
|
|
final ratio = width / height;
|
|
if (ratio >= 1) {
|
|
return Size(maxSide, (maxSide / ratio).clamp(minSide, maxSide));
|
|
}
|
|
return Size((maxSide * ratio).clamp(minSide, maxSide), maxSide);
|
|
}
|
|
|
|
Widget thumbnail({
|
|
required Size size,
|
|
required ColorScheme cs,
|
|
double radius = 8,
|
|
}) {
|
|
final attachment = media;
|
|
if (attachment == null) return const SizedBox.shrink();
|
|
final url = _networkThumbnail(attachment);
|
|
final local = dataUriImage(attachment, attachment.previewData);
|
|
|
|
Widget fallback() => Container(
|
|
width: size.width,
|
|
height: size.height,
|
|
color: cs.surfaceContainerHighest,
|
|
child: Icon(
|
|
icon ?? Symbols.image,
|
|
size: size.shortestSide * 0.4,
|
|
color: cs.onSurfaceVariant,
|
|
),
|
|
);
|
|
|
|
Widget localImage() => Image(
|
|
image: local!,
|
|
width: size.width,
|
|
height: size.height,
|
|
fit: BoxFit.cover,
|
|
gaplessPlayback: true,
|
|
errorBuilder: (_, _, _) => fallback(),
|
|
);
|
|
|
|
final Widget image;
|
|
if (url != null) {
|
|
image = CachedNetworkImage(
|
|
imageUrl: url,
|
|
width: size.width,
|
|
height: size.height,
|
|
memCacheWidth: (size.width * 3).round(),
|
|
fit: BoxFit.cover,
|
|
fadeInDuration: Duration.zero,
|
|
placeholderFadeInDuration: Duration.zero,
|
|
placeholder: (_, _) => local == null ? fallback() : localImage(),
|
|
errorWidget: (_, _, _) => local == null ? fallback() : localImage(),
|
|
);
|
|
} else if (local != null) {
|
|
image = localImage();
|
|
} else {
|
|
image = fallback();
|
|
}
|
|
|
|
return ClipRRect(
|
|
borderRadius: BorderRadius.circular(
|
|
round ? size.shortestSide / 2 : radius,
|
|
),
|
|
child: image,
|
|
);
|
|
}
|
|
|
|
static bool _hasThumbnail(MessageAttachment attachment) {
|
|
if (attachment is! PhotoAttachment && attachment is! VideoAttachment) {
|
|
return false;
|
|
}
|
|
return _networkThumbnail(attachment) != null ||
|
|
dataUriImage(attachment, attachment.previewData) != null;
|
|
}
|
|
|
|
static String? _networkThumbnail(MessageAttachment attachment) {
|
|
final candidates = <String?>[
|
|
if (attachment is VideoAttachment) attachment.thumbnail,
|
|
attachment.baseUrl,
|
|
];
|
|
for (final url in candidates) {
|
|
if (url == null || url.isEmpty || url.startsWith('data:')) continue;
|
|
return url;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static int? _sizeOf(
|
|
MessageAttachment? attachment, {
|
|
required bool horizontal,
|
|
}) {
|
|
if (attachment is PhotoAttachment) {
|
|
return horizontal ? attachment.width : attachment.height;
|
|
}
|
|
if (attachment is VideoAttachment) {
|
|
return horizontal ? attachment.width : attachment.height;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static IconData? _iconFor(AttachmentType type) => switch (type) {
|
|
AttachmentType.photo => Symbols.image,
|
|
AttachmentType.video => Symbols.videocam,
|
|
AttachmentType.audio => Symbols.mic,
|
|
AttachmentType.file => Symbols.description,
|
|
AttachmentType.sticker => Symbols.emoji_emotions,
|
|
AttachmentType.contact => Symbols.person,
|
|
AttachmentType.location => Symbols.location_on,
|
|
AttachmentType.poll => Symbols.bar_chart,
|
|
AttachmentType.call => Symbols.call,
|
|
AttachmentType.share => Symbols.link,
|
|
AttachmentType.forward => Symbols.forward,
|
|
AttachmentType.unknown => Symbols.attach_file,
|
|
AttachmentType.control || AttachmentType.inlineKeyboard => null,
|
|
};
|
|
}
|