import 'package:flutter/material.dart'; import 'package:photo_view/photo_view.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:veloe_kemono_party_flutter/pages/widgets/video_player_widget'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:gallery_saver_plus/gallery_saver.dart'; import 'package:path/path.dart' as path; import '../models/post.dart'; import 'package:veloe_kemono_party_flutter/main.dart'; import 'dart:io'; import 'package:flutter/services.dart'; class MediaCarouselItem { final String url; final String type; // 'image' or 'video' final int postIndex; final int attachmentIndex; MediaCarouselItem({ required this.url, required this.type, required this.postIndex, required this.attachmentIndex, }); } class MediaCarouselScreen extends StatefulWidget { final int initialIndex; final List mediaItems; final Future Function() loadMorePosts; final List allPosts; const MediaCarouselScreen({ super.key, required this.initialIndex, required this.mediaItems, required this.loadMorePosts, required this.allPosts, }); @override State createState() => _MediaCarouselScreenState(); } class _MediaCarouselScreenState extends State { late PageController _pageController; late int _currentIndex; late List _mediaItems; bool _isSaving = false; @override void initState() { super.initState(); _currentIndex = widget.initialIndex; _mediaItems = widget.mediaItems; _pageController = PageController(initialPage: widget.initialIndex); } @override Widget build(BuildContext context) { return Theme( // 👇 Local theme override ONLY for this screen data: ThemeData.dark().copyWith( appBarTheme: const AppBarTheme( backgroundColor: Colors.black, iconTheme: IconThemeData(color: Colors.white), titleTextStyle: TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600 ), ), scaffoldBackgroundColor: Colors.black, ), child: Scaffold( backgroundColor: Colors.black, body: Stack( children: [ PageView.builder( controller: _pageController, itemCount: _mediaItems.length, onPageChanged: (index) { setState(() { _currentIndex = index; }); // Check if we are near the end and load more if (index >= _mediaItems.length - 3) { _loadMoreIfNeeded(); } }, itemBuilder: (context, index) { final item = _mediaItems[index]; if (item.type == 'image') { ThemeData theme = Theme.of(context); CachedNetworkImageProvider provider = CachedNetworkImageProvider(item.url,cacheManager : imageCacheManager); return Theme( data: theme.copyWith( appBarTheme: AppBarTheme( systemOverlayStyle: SystemUiOverlayStyle( statusBarIconBrightness: Brightness.light, statusBarColor: Colors.black26, ), ), ), child:PhotoView( imageProvider: provider, backgroundDecoration: const BoxDecoration(color: Colors.black), )); } else { return VideoPlayerWidget(videoUrl: item.url); } }, ), Positioned( top: 20, left: 20, child: IconButton( icon: const Icon(Icons.close, color: Colors.white), onPressed: () => Navigator.pop(context), ), ), Positioned( top: 20, right: 20, child: PopupMenuButton( onSelected: (value) => _handleMenuSelection(value), itemBuilder: (context) => [ PopupMenuItem( value: 'save', child: Row( children: [ Icon(Icons.save_alt, color: Theme.of(context).primaryColor), const SizedBox(width: 10), const Text('Save to Gallery'), ], ), ), // Add more options here if needed ], icon: const Icon(Icons.more_vert, color: Colors.white), ), ), // Saving indicator if (_isSaving) const Center( child: CircularProgressIndicator(color: Colors.white), ), ], ), ) ); } Future _loadMoreIfNeeded() async { if (_currentIndex >= _mediaItems.length - 3) { await widget.loadMorePosts(); setState(() { _mediaItems = _compileMediaItems(widget.allPosts); }); } } List _compileMediaItems(List posts) { List items = []; for (int i = 0; i < posts.length; i++) { final post = posts[i]; for (int j = 0; j < post.attachments.length; j++) { final attachment = post.attachments[j]; if (attachment.isImage || attachment.isVideo) items.add( MediaCarouselItem( url: attachment.link, type: attachment.isImage ? "image" : "video", postIndex: i, attachmentIndex: j, ), ); } } return items; } void _handleMenuSelection(String value) async { if (value == 'save') { await _saveCurrentMedia(); } } Future _saveCurrentMedia() async { setState(() => _isSaving = true); try { final item = _mediaItems[_currentIndex]; final status = await Permission.storage.request(); if (status.isPermanentlyDenied) { openAppSettings(); } if (!status.isGranted) { _showSnackBar('Storage permission denied'); return; } final cachedFile = await imageCacheManager.getFileFromCache(item.url); if (cachedFile is FileInfo) { //if (Platform.isAndroid) { if (item.type == "image"){ await GallerySaver.saveImage(cachedFile.file.path); } else { await GallerySaver.saveVideo(cachedFile.file.path); } //} _showSnackBar('Media saved to gallery'); } else _showSnackBar('Cache media first!'); } catch (e) { debugPrint('Save error: $e'); _showSnackBar('Failed to save media'); } finally { setState(() => _isSaving = false); } } Future getGalleryDirectory(String type) async { final externalDir = await getExternalStorageDirectory(); final galleryPath = type == 'image' ? path.join(externalDir!.path, 'Pictures', 'Kemono Party') : path.join(externalDir!.path, 'Movies', 'Kemono Party'); final dir = Directory(galleryPath); if (!await dir.exists()) { await dir.create(recursive: true); } return dir; } void _showSnackBar(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), duration: const Duration(seconds: 2), ), ); } }