123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- 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';
- 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<MediaCarouselItem> mediaItems;
- final Future<void> Function() loadMorePosts;
- final List<Post> allPosts;
- const MediaCarouselScreen({
- super.key,
- required this.initialIndex,
- required this.mediaItems,
- required this.loadMorePosts,
- required this.allPosts,
- });
- @override
- State<MediaCarouselScreen> createState() => _MediaCarouselScreenState();
- }
- class _MediaCarouselScreenState extends State<MediaCarouselScreen> {
- late PageController _pageController;
- late int _currentIndex;
- late List<MediaCarouselItem> _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 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') {
- return PhotoView(
- imageProvider: NetworkImage(item.url),
- 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<String>(
- 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<void> _loadMoreIfNeeded() async {
- if (_currentIndex >= _mediaItems.length - 3) {
- await widget.loadMorePosts();
- setState(() {
- _mediaItems = _compileMediaItems(widget.allPosts);
- });
- }
- }
- List<MediaCarouselItem> _compileMediaItems(List<Post> posts) {
- List<MediaCarouselItem> 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<void> _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<Directory> 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),
- ),
- );
- }
- }
|