import 'package:flutter/material.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:veloe_kemono_party_flutter/models/attachment.dart'; import 'package:veloe_kemono_party_flutter/pages/media_carousel_screen'; import 'package:veloe_kemono_party_flutter/pages/widgets/video_preview.dart'; import 'smart_image_container.dart'; import 'package:share_plus/share_plus.dart'; import '../../models/post.dart'; class PostContainer extends StatelessWidget { final Post post; final int postIndex; // The index of this post in the list of posts (in PostsScreen) final List allPosts; final Future Function() loadMorePosts; const PostContainer({ super.key, required this.post, required this.postIndex, required this.allPosts, required this.loadMorePosts, }); @override Widget build(BuildContext context) { return Card( margin: const EdgeInsets.all(8), child: Padding( padding: const EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ _PostHeader(post: post), _buildContent(context, post.attachments), //_PostContentSection(post: post), _PostActionsFooter(post: post), ], ), ), ); } Widget _buildContent(BuildContext context, List attachments) { final mediaAttachments = attachments.where((a) => a.isImage || a.isVideo).toList(); return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // Text Content HtmlWidget( post.content, textStyle: Theme.of(context).textTheme.bodyMedium, onTapUrl: (url) => _handleUrlLaunch(context, url), ), if (mediaAttachments.isNotEmpty) LayoutBuilder( builder: (context, constraints) { return Column( children: [ const SizedBox(height: 12), GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: _calculateColumnCount(constraints.maxWidth), ), itemCount: mediaAttachments.length, itemBuilder: (context, index) => Padding( padding: const EdgeInsets.all(4), child: _buildMediaPreview(context, mediaAttachments[index], index, attachments) /*SmartImageContainer( imageUrl: imageAttachments[index].link, //onTap: () => _handleAttachmentTap(index), ),*/ ), ), // Attachment links list ...post.attachments.map( (attachment) => _buildAttachmentLink(context,attachment), ), ], ); }, ), ], ); } Widget _buildAttachmentLink(BuildContext context,Attachment attachment) { return Padding( padding: const EdgeInsets.only(top: 8), child: InkWell( onTap: () => _handleUrlLaunch(context, attachment.link), child: Row( children: [ const Icon(Icons.link, size: 16), const SizedBox(width: 8), Expanded( child: Text( attachment.name, style: const TextStyle( color: Colors.blue, decoration: TextDecoration.underline, ), overflow: TextOverflow.ellipsis, ), ), ], ), ), ); } int _calculateColumnCount(double availableWidth) { return (availableWidth / 400).floor().clamp(1, 3); } Widget _buildMediaPreview(BuildContext context,Attachment attachment, int index, List attachments) { return GestureDetector( onTap: () { _openMediaCarousel(context, index); }, child: attachment.isVideo ? VideoPreview(videoUrl: attachment.link, onTap: () => _openMediaCarousel(context, index),) : (attachment.isImage ? SmartImageContainer(imageUrl: attachment.link) : null), ); } void _openMediaCarousel(BuildContext context, int attachmentIndex) { List mediaItems = []; for (int i = 0; i < allPosts.length; i++) { final post = allPosts[i]; for (int j = 0; j < post.attachments.length; j++) { final attachment = post.attachments[j]; if (attachment.isImage || attachment.isVideo) { mediaItems.add( MediaCarouselItem( url: attachment.link, type: attachment.isVideo ? "video" : "image", postIndex: i, attachmentIndex: j, ), ); } } } int globalIndex = 0; for (int i = 0; i < postIndex; i++) { globalIndex += allPosts[i].attachments.where((x)=>x.isImage || x.isVideo).length; } globalIndex += attachmentIndex; Navigator.push( context, MaterialPageRoute( builder: (context) => MediaCarouselScreen( initialIndex: globalIndex, mediaItems: mediaItems, loadMorePosts: loadMorePosts, allPosts: allPosts, ), ), ); } } class _PostHeader extends StatelessWidget { final Post post; const _PostHeader({required this.post}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 12), child: Row( children: [ /*CircleAvatar( backgroundImage: NetworkImage(post.author), radius: 20, ),*/ const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( post.title, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), ), ], ), ), // Time and menu Text( post.published, style: Theme.of(context).textTheme.bodySmall, ), /*IconButton( icon: const Icon(Icons.more_vert), onPressed: () => _showPostMenu(context), ),*/ ], ), ); } } // New footer component class _PostActionsFooter extends StatelessWidget { final Post post; const _PostActionsFooter({required this.post}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(top: 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // Interactive buttons Row( children: [/* IconButton( icon: Icon( post.isLiked ? Icons.favorite : Icons.favorite_border, color: post.isLiked ? Colors.red : null, ), onPressed: () => _toggleLike(), ), Text(_formatCount(post.likeCount)), const SizedBox(width: 16), IconButton( icon: const Icon(Icons.comment_outlined), onPressed: () => _showComments(), ), Text(_formatCount(post.commentCount)), */], ), // Share button IconButton( icon: const Icon(Icons.share_outlined), onPressed: () => _sharePost(post), ), ], ), ); } } void _sharePost(Post post) { var link = '${post.baseUrl}/${post.service}/user/${post.user}/post/${post.id}'; SharePlus.instance.share(ShareParams(uri: Uri.parse(link))); } Future _handleUrlLaunch(BuildContext context, String url) async { try { await launchUrl( Uri.parse(url), mode: LaunchMode.externalApplication, ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Could not open link: ${e.toString()}')), ); } return true; }