import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:get/get.dart'; import 'package:share_plus/share_plus.dart'; import 'package:f0ckapp/models/mediaitem_model.dart'; import 'package:f0ckapp/services/api_service.dart'; import 'package:f0ckapp/widgets/end_drawer.dart'; import 'package:f0ckapp/screens/fullscreen_screen.dart'; import 'package:f0ckapp/widgets/video_widget.dart'; class DetailView extends StatefulWidget { final int initialItemId; const DetailView({super.key, required this.initialItemId}); @override State createState() => _DetailViewState(); } class _DetailViewState extends State { final ApiService apiService = Get.find(); PageController? _pageController; Future? _loadingFuture; int _currentPage = 0; @override void initState() { super.initState(); if (!_mediaItemExists(widget.initialItemId)) { _loadingFuture = _fetchAndPreloadMedia(widget.initialItemId); } else { _initializePageController(); } } bool _mediaItemExists(int id) { return apiService.mediaItems.any((media) => media.id == id); } Future _fetchAndPreloadMedia(int targetId) async { try { WidgetsBinding.instance.addPostFrameCallback((_) async { await apiService.setTag(null); }); await apiService.fetchMedia(id: targetId + 50, reset: false); _initializePageController(); } catch (e) { _showMsg("Medien konnten nicht geladen werden"); } } void _initializePageController() { _currentPage = apiService.mediaItems.indexWhere( (media) => media.id == widget.initialItemId, ); if (_currentPage < 0) { _currentPage = 0; } _pageController = PageController(initialPage: _currentPage) ..addListener(() { setState(() { _currentPage = _pageController!.page!.round(); }); }); setState(() {}); } @override void dispose() { _pageController?.dispose(); super.dispose(); } MediaItem? _findMediaItem() { try { return apiService.mediaItems.firstWhere( (media) => media.id == widget.initialItemId, ); } catch (e) { return null; } } Future _downloadMedia(MediaItem item) async { final File file = await DefaultCacheManager().getSingleFile(item.mediaUrl); final MethodChannel methodChannel = const MethodChannel('MediaShit'); bool? success = await methodChannel.invokeMethod('saveFile', { 'filePath': file.path, 'fileName': item.dest, }); success == true ? _showMsg('${item.dest} wurde in Downloads/fApp neigespeichert.') : _showMsg('${item.dest} konnte nicht heruntergeladen werden.'); } void _showMsg(String message) { if (!mounted) return; ScaffoldMessenger.of(context) ..removeCurrentSnackBar() ..showSnackBar(SnackBar(content: Text(message))); } @override Widget build(BuildContext context) { if (_loadingFuture != null) { return FutureBuilder( future: _loadingFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Scaffold( appBar: AppBar(title: const Text("Detail")), body: const Center(child: CircularProgressIndicator()), ); } else { MediaItem? item = _findMediaItem(); if (item == null) { return Scaffold( appBar: AppBar(title: const Text("Detail")), body: const Center(child: Text("f0ck nicht gefunden")), ); } return _buildDetail(); } }, ); } MediaItem? existingItem = _findMediaItem(); if (existingItem == null) { return Scaffold( appBar: AppBar(title: const Text("Detail")), body: const Center(child: Text("f0ck nicht gefunden")), ); } return _buildDetail(); } Widget _buildDetail() { final MediaItem currentItem = apiService.mediaItems[_currentPage]; return Scaffold( endDrawer: const EndDrawer(), endDrawerEnableOpenDragGesture: false, persistentFooterButtons: apiService.tag.value != null ? [ Center( child: InputChip( label: Text(apiService.tag.value!), onDeleted: () { apiService.setTag(null); Get.offAllNamed('/'); }, ), ), ] : null, body: CustomScrollView( slivers: [ SliverAppBar( floating: true, pinned: true, snap: true, centerTitle: true, title: Text('f0ck #${currentItem.id.toString()}'), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => Get.back(), ), actions: [ IconButton( icon: const Icon(Icons.fullscreen), onPressed: () { Get.to( FullScreenMediaView(item: currentItem), fullscreenDialog: true, ); }, ), IconButton( icon: const Icon(Icons.download), onPressed: () async { await _downloadMedia(currentItem); }, ), PopupMenuButton( onSelected: (value) async { switch (value) { case 'media': File file = await DefaultCacheManager().getSingleFile( currentItem.mediaUrl, ); Uint8List bytes = await file.readAsBytes(); final params = ShareParams( files: [ XFile.fromData(bytes, mimeType: currentItem.mime), ], ); await SharePlus.instance.share(params); break; case 'direct_link': await SharePlus.instance.share( ShareParams(text: currentItem.mediaUrl), ); break; case 'post_link': await SharePlus.instance.share( ShareParams(text: currentItem.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), ), Builder( builder: (context) => IconButton( icon: const Icon(Icons.menu), onPressed: () { Scaffold.of(context).openEndDrawer(); }, ), ), ], ), SliverFillRemaining( child: PageView.builder( controller: _pageController, itemCount: apiService.mediaItems.length, itemBuilder: (context, index) { final MediaItem pageItem = apiService.mediaItems[index]; return AnimatedBuilder( animation: _pageController!, builder: (context, child) { double value = 0; if (_pageController!.position.haveDimensions) { value = (_pageController!.page! - index).abs(); } double factor = Curves.easeOut.transform( 1 - value.clamp(0.0, 1.0), ); double scale = 0.8 + factor * 0.2; return Transform.scale(scale: scale, child: child); }, child: SafeArea( top: false, child: SingleChildScrollView( child: Column( children: [ if (pageItem.mime.startsWith('image')) CachedNetworkImage( imageUrl: pageItem.mediaUrl, fit: BoxFit.contain, placeholder: (context, url) => const Center( child: CircularProgressIndicator(), ), errorWidget: (context, url, error) => const Center(child: Icon(Icons.error)), ) else VideoWidget( details: pageItem, isActive: index == _currentPage, ), const SizedBox(height: 10, width: double.infinity), Wrap( alignment: WrapAlignment.center, spacing: 5.0, children: pageItem.tags.map((tag) { return ActionChip( onPressed: () { if (tag.tag == 'sfw' || tag.tag == 'nsfw') { return; } apiService.setTag(tag.tag); Get.offAllNamed('/'); }, 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), ], ), ), ), ); }, ), ), ], ), ); } }