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:go_router/go_router.dart'; import 'package:share_plus/share_plus.dart'; import 'package:f0ckapp/models/mediaitem_model.dart'; import 'package:f0ckapp/widgets/video_widget.dart'; import 'package:f0ckapp/utils/smartrefreshindicator_util.dart'; import 'package:f0ckapp/utils/pagetransformer_util.dart'; import 'package:f0ckapp/providers/media_provider.dart'; class DetailView extends ConsumerStatefulWidget { final int initialItemId; const DetailView({super.key, required this.initialItemId}); @override ConsumerState createState() => _DetailViewState(); } class _DetailViewState extends ConsumerState { PageController? _pageController; bool isLoading = false; int _currentIndex = 0; @override void initState() { super.initState(); } void _preloadAdjacentMedia(int index) async { final mediaState = ref.read(mediaProvider); for (int offset in [-1, 1]) { final adjacentIndex = index + offset; if (adjacentIndex >= 0 && adjacentIndex < mediaState.mediaItems.length) { final url = mediaState.mediaItems[adjacentIndex].mediaUrl; if (await DefaultCacheManager().getFileFromCache(url) == null) { await DefaultCacheManager().downloadFile(url); } } } } 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 int itemIndex = mediaState.mediaItems.indexWhere( (item) => item.id == widget.initialItemId, ); if (itemIndex == -1) { Future.microtask(() { ref .read(mediaProvider.notifier) .loadMedia(id: widget.initialItemId + 50); }); return Scaffold( appBar: AppBar(), body: const Center(child: CircularProgressIndicator()), ); } if (_pageController == null) { _pageController = PageController(initialPage: itemIndex); _currentIndex = itemIndex; _pageController!.addListener(() { setState(() => _currentIndex = _pageController!.page?.round() ?? 0); }); _preloadAdjacentMedia(itemIndex); } return Scaffold( appBar: AppBar( centerTitle: true, title: Text('f0ck #${mediaState.mediaItems[_currentIndex].id}'), automaticallyImplyLeading: false, leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { context.canPop() ? context.pop() : context.go('/', extra: true); }, ), actions: [ IconButton( icon: const Icon(Icons.fullscreen), onPressed: () { _showError('fullscreen ist wip'); }, ), IconButton( icon: const Icon(Icons.download), onPressed: () { _showError('download ist 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: () { ref.read(mediaProvider.notifier).setTag(null); context.go('/', extra: true); }, ), ), ] : 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); context.go('/', extra: 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), ], ), ); } }