import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import '../../../../api/marianumcloud/talk/chat/get_chat_response.dart'; import '../../../../model/endpoint_data.dart'; import '../../../../session/session_manager.dart'; import '../../../../widget/media_gallery/gallery_item.dart'; import '../../files/data/file_type_icon.dart'; /// File attachment in a chat bubble. Only what the media gallery pages through /// renders as a plain picture; other previews (documents, audio covers) carry /// a name strip so they don't pass for photos. class ChatFileAttachment extends StatelessWidget { final RichObjectString file; const ChatFileAttachment({required this.file, super.key}); @override Widget build(BuildContext context) { final kind = galleryMediaKindOf(file.name, mimeType: file.mimetype); return CachedNetworkImage( imageUrl: '${EndpointData().nextcloud().origin()}/index.php/core/preview?fileId=${file.id}&x=130&y=-1&a=1', httpHeaders: SessionManager().requireNextcloud().authHeaders, alignment: Alignment.center, fadeInDuration: Duration.zero, fadeOutDuration: Duration.zero, errorListener: (_) {}, placeholder: (context, url) => const Padding( padding: EdgeInsets.all(15), child: SizedBox(width: 50, child: LinearProgressIndicator()), ), imageBuilder: switch (kind) { GalleryMediaKind.image => null, GalleryMediaKind.video => (context, image) => Stack( alignment: Alignment.center, children: [ Image(image: image), const _PlayBadge(), ], ), null => (context, image) => Stack( children: [ Image(image: image), Positioned( left: 0, right: 0, bottom: 0, child: _NameStrip(name: file.name), ), ], ), }, errorWidget: (context, url, error) => kind == GalleryMediaKind.video ? const _VideoPlaceholder() : _FileRow(name: file.name), ); } } class _PlayBadge extends StatelessWidget { const _PlayBadge(); @override Widget build(BuildContext context) => const DecoratedBox( decoration: BoxDecoration(color: Colors.black54, shape: BoxShape.circle), child: Padding( padding: EdgeInsets.all(6), child: Icon(Icons.play_arrow, color: Colors.white, size: 28), ), ); } /// Same footprint as a typical preview, so videos without a server thumbnail /// still read as media. class _VideoPlaceholder extends StatelessWidget { const _VideoPlaceholder(); @override Widget build(BuildContext context) => Container( width: 130, height: 90, decoration: BoxDecoration( color: Colors.black87, borderRadius: BorderRadius.circular(4), ), alignment: Alignment.center, child: const Stack( alignment: Alignment.center, children: [ Icon(Icons.movie_outlined, color: Colors.white38, size: 56), _PlayBadge(), ], ), ); } class _NameStrip extends StatelessWidget { final String name; const _NameStrip({required this.name}); @override Widget build(BuildContext context) => ColoredBox( color: Colors.black54, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), child: Row( children: [ Icon(iconForFileName(name), size: 14, color: Colors.white), const SizedBox(width: 4), Expanded( child: Text( name, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(color: Colors.white, fontSize: 11), ), ), ], ), ), ); } class _FileRow extends StatelessWidget { final String name; const _FileRow({required this.name}); @override Widget build(BuildContext context) => Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ const Icon(Icons.file_open_outlined, size: 35), const SizedBox(width: 10), Flexible( child: Text( name, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(fontWeight: FontWeight.bold), ), ), const SizedBox(width: 10), ], ); }