import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:share_plus/share_plus.dart'; import 'package:f0ckapp/models/MediaItem.dart'; import 'package:f0ckapp/widgets/VideoWidget.dart'; import 'package:f0ckapp/utils/SmartRefreshIndicator.dart'; import 'package:f0ckapp/utils/PageTransformer.dart'; import 'package:f0ckapp/providers/MediaProvider.dart'; class DetailView extends ConsumerStatefulWidget { final int initialItemId; const DetailView({super.key, required this.initialItemId}); @override ConsumerState createState() => _DetailViewState(); } class _DetailViewState extends ConsumerState { late PageController _pageController; bool isLoading = false; int _currentIndex = 0; @override void initState() { super.initState(); final mediaState = ref.read(mediaProvider); final initialIndex = mediaState.mediaItems.indexWhere( (item) => item.id == widget.initialItemId, ); _pageController = PageController(initialPage: initialIndex); _currentIndex = initialIndex; _pageController.addListener(() { setState(() => _currentIndex = _pageController.page?.round() ?? 0); }); _preloadAdjacentMedia(initialIndex); } void _preloadAdjacentMedia(int index) async { final mediaState = ref.read(mediaProvider); if (index + 1 < mediaState.mediaItems.length) { final nextUrl = mediaState.mediaItems[index + 1].mediaUrl; if (await DefaultCacheManager().getFileFromCache(nextUrl) == null) { await DefaultCacheManager().downloadFile(nextUrl); } } if (index - 1 >= 0) { final prevUrl = mediaState.mediaItems[index - 1].mediaUrl; if (await DefaultCacheManager().getFileFromCache(prevUrl) == null) { await DefaultCacheManager().downloadFile(prevUrl); } } } Future _loadMoreMedia() async { if (isLoading) return; setState(() => isLoading = true); try { await ref.read(mediaProvider.notifier).loadMedia(); } catch (e) { _showError("Fehler beim Laden der Medien: $e"); } finally { setState(() => isLoading = false); } } void _showError(String message) { if (!mounted) return; ScaffoldMessenger.of(context) ..removeCurrentSnackBar() ..showSnackBar(SnackBar(content: Text(message))); } @override void dispose() { _pageController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final mediaState = ref.watch(mediaProvider); final mediaNotifier = ref.read(mediaProvider.notifier); if (mediaState.mediaItems.isEmpty) { return Scaffold( appBar: AppBar(), body: const Center(child: CircularProgressIndicator()), ); } return Scaffold( appBar: AppBar( centerTitle: true, title: Text('f0ck #${mediaState.mediaItems[_currentIndex].id}'), actions: [ IconButton( icon: Icon(Icons.download), onPressed: () { // wip }, ), PopupMenuButton( onSelected: (value) async { final item = mediaState.mediaItems[_currentIndex]; switch (value) { case 'media': File file = await DefaultCacheManager().getSingleFile( item.mediaUrl, ); Uint8List bytes = await file.readAsBytes(); final params = ShareParams( files: [XFile.fromData(bytes, mimeType: item.mime)], ); await SharePlus.instance.share(params); break; case 'direct_link': await SharePlus.instance.share( ShareParams(text: item.mediaUrl), ); break; case 'post_link': await SharePlus.instance.share( ShareParams(text: item.postUrl), ); break; } }, itemBuilder: (context) => [ PopupMenuItem( value: 'media', child: ListTile( leading: const Icon(Icons.image), title: const Text('Als Datei'), ), ), PopupMenuItem( value: 'direct_link', child: ListTile( leading: const Icon(Icons.link), title: const Text('Link zur Datei'), ), ), PopupMenuItem( value: 'post_link', child: ListTile( leading: const Icon(Icons.article), title: const Text('Link zum f0ck'), ), ), ], icon: const Icon(Icons.share), ), ], ), body: Stack( children: [ PageTransformer( controller: _pageController, pages: mediaState.mediaItems.map((item) { int itemIndex = mediaState.mediaItems.indexOf(item); return SafeArea( child: SmartRefreshIndicator( onRefresh: _loadMoreMedia, child: _buildMediaItem(item, _currentIndex == itemIndex), ), ); }).toList(), ), ], ), persistentFooterButtons: mediaState.tag != null ? [ Center( child: InputChip( label: Text(mediaState.tag!), onDeleted: () { mediaNotifier.setTag(null); Navigator.pop(context); }, ), ), ] : null, ); } Widget _buildMediaItem(MediaItem item, bool isActive) { final mediaNotifier = ref.read(mediaProvider.notifier); return SingleChildScrollView( child: Column( children: [ if (item.mime.startsWith('image')) CachedNetworkImage( imageUrl: item.mediaUrl, fit: BoxFit.contain, placeholder: (context, url) => const CircularProgressIndicator(), errorWidget: (context, url, error) => const Icon(Icons.error), ) else VideoWidget(details: item, isActive: isActive), const SizedBox(height: 10, width: double.infinity), Wrap( alignment: WrapAlignment.center, spacing: 5.0, children: item.tags.map((tag) { return ActionChip( onPressed: () { if (tag.tag == 'sfw' || tag.tag == 'nsfw') return; setState(() { mediaNotifier.setTag(tag.tag); Navigator.pop(context, true); }); }, label: Text(tag.tag), backgroundColor: switch (tag.id) { 1 => Colors.green, 2 => Colors.red, _ => const Color(0xFF090909), }, labelStyle: const TextStyle(color: Colors.white), ); }).toList(), ), const SizedBox(height: 20), ], ), ); } }