|
@@ -2,14 +2,25 @@ 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<Post> allPosts;
|
|
|
+ final Future<void> Function() loadMorePosts;
|
|
|
|
|
|
- const PostContainer({super.key, required this.post});
|
|
|
+ const PostContainer({
|
|
|
+ super.key,
|
|
|
+ required this.post,
|
|
|
+ required this.postIndex,
|
|
|
+ required this.allPosts,
|
|
|
+ required this.loadMorePosts,
|
|
|
+ });
|
|
|
|
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
@@ -22,13 +33,143 @@ class PostContainer extends StatelessWidget {
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
children: [
|
|
|
_PostHeader(post: post),
|
|
|
- _PostContentSection(post: post),
|
|
|
+ _buildContent(context, post.attachments),
|
|
|
+ //_PostContentSection(post: post),
|
|
|
_PostActionsFooter(post: post),
|
|
|
],
|
|
|
),
|
|
|
),
|
|
|
);
|
|
|
}
|
|
|
+
|
|
|
+ Widget _buildContent(BuildContext context, List<Attachment> attachments) {
|
|
|
+ final imageAttachments = post.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),
|
|
|
+ ),
|
|
|
+
|
|
|
+ // Attachments Grid
|
|
|
+ if (imageAttachments.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: imageAttachments.length,
|
|
|
+ itemBuilder: (context, index) => Padding(
|
|
|
+ padding: const EdgeInsets.all(4),
|
|
|
+ child:
|
|
|
+ _buildMediaPreview(context, imageAttachments[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<Attachment> 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<MediaCarouselItem> 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];
|
|
|
+ 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.length;
|
|
|
+ }
|
|
|
+ globalIndex += attachmentIndex;
|
|
|
+
|
|
|
+ Navigator.push(
|
|
|
+ context,
|
|
|
+ MaterialPageRoute(
|
|
|
+ builder: (context) => MediaCarouselScreen(
|
|
|
+ initialIndex: globalIndex,
|
|
|
+ mediaItems: mediaItems,
|
|
|
+ loadMorePosts: loadMorePosts,
|
|
|
+ allPosts: allPosts,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
class _PostHeader extends StatelessWidget {
|
|
@@ -123,116 +264,6 @@ void _sharePost(Post post) {
|
|
|
Share.share(link);
|
|
|
}
|
|
|
|
|
|
-class _PostContentSection extends StatelessWidget {
|
|
|
- final Post post;
|
|
|
-
|
|
|
- const _PostContentSection({required this.post});
|
|
|
-
|
|
|
- @override
|
|
|
- Widget build(BuildContext context) {
|
|
|
- final imageAttachments = post.attachments
|
|
|
- .where((a) => _isImageFile(a.link))
|
|
|
- .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),
|
|
|
- ),
|
|
|
-
|
|
|
- // Attachments Grid
|
|
|
- if (imageAttachments.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: imageAttachments.length,
|
|
|
- itemBuilder: (context, index) => Padding(
|
|
|
- padding: const EdgeInsets.all(4),
|
|
|
- child: 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 ?? _parseFileName(attachment.link),
|
|
|
- style: const TextStyle(
|
|
|
- color: Colors.blue,
|
|
|
- decoration: TextDecoration.underline,
|
|
|
- ),
|
|
|
- overflow: TextOverflow.ellipsis,
|
|
|
- ),
|
|
|
- ),
|
|
|
- ],
|
|
|
- ),
|
|
|
- ),
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- int _calculateColumnCount(double availableWidth) {
|
|
|
- return (availableWidth / 400).floor().clamp(1, 3);
|
|
|
- }
|
|
|
-
|
|
|
- String _parseFileName(String url) {
|
|
|
- try {
|
|
|
- return Uri.parse(url).pathSegments.last;
|
|
|
- } catch (e) {
|
|
|
- return 'Download';
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- bool _isImageFile(String url) {
|
|
|
- try {
|
|
|
- final uri = Uri.parse(url);
|
|
|
- final path = uri.path.toLowerCase();
|
|
|
-
|
|
|
- final cleanPath = path.split('?').first.split('#').first;
|
|
|
- const imageExtensions = {
|
|
|
- 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg',
|
|
|
- 'tiff', 'heic', 'heif'
|
|
|
- };
|
|
|
- return imageExtensions.any((ext) => cleanPath.endsWith('.$ext'));
|
|
|
- } catch (e) {
|
|
|
- return false; // Invalid URL format
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
Future<bool> _handleUrlLaunch(BuildContext context, String url) async {
|
|
|
try {
|
|
|
await launchUrl(
|
|
@@ -245,4 +276,4 @@ Future<bool> _handleUrlLaunch(BuildContext context, String url) async {
|
|
|
);
|
|
|
}
|
|
|
return true;
|
|
|
-}
|
|
|
+}
|