import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:f0ckapp/services/Api.dart'; import 'package:f0ckapp/models/MediaItem.dart'; import 'package:f0ckapp/screens/DetailView.dart'; import 'dart:async'; class MediaGrid extends StatefulWidget { const MediaGrid({super.key}); @override State createState() => _MediaGridState(); } class _MediaGridState extends State { final ScrollController _scrollController = ScrollController(); final String _version = '1.0.25+25'; List mediaItems = []; bool isLoading = false; Timer? _debounceTimer; Completer? _navigationCompleter; int _crossAxisCount = 0; String _selectedType = 'alles'; int _selectedMode = 0; bool _random = false; final List _modes = ["sfw", "nsfw", "untagged", "all"]; String? _selectedTag; @override void initState() { super.initState(); _loadMedia(); _scrollController.addListener(() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 100) { _debounceLoadMedia(); } }); } void _debounceLoadMedia() { _debounceTimer?.cancel(); _debounceTimer = Timer(const Duration(milliseconds: 500), _loadMedia); } int _calculateCrossAxisCount(BuildContext context) { if (_crossAxisCount != 0) { return _crossAxisCount; } double screenWidth = MediaQuery.of(context).size.width; int columnCount = (screenWidth / 110).clamp(3, 5).toInt(); return columnCount; } Future _loadMedia() async { if (isLoading) return; setState(() => isLoading = true); try { final newMedia = await fetchMedia( older: mediaItems.isNotEmpty ? mediaItems.last.id.toString() : null, type: _selectedType, mode: _selectedMode, random: _random, tag: _selectedTag, ); if (mounted) { setState(() => mediaItems.addAll(newMedia)); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Fehler beim Laden der Medien: $e')), ); } } finally { if (mounted) setState(() => isLoading = false); } } Future _refreshMedia() async { setState(() => isLoading = true); try { final freshMedia = await fetchMedia( older: null, type: _selectedType, mode: _selectedMode, random: _random, tag: _selectedTag, ); if (mounted) { setState(() { mediaItems.clear(); mediaItems.addAll(freshMedia); }); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Fehler beim Aktualisieren: $e')), ); } } finally { if (mounted) setState(() => isLoading = false); } } Future _navigateToDetail(MediaItem item) async { if (_navigationCompleter?.isCompleted == false) return; _navigationCompleter = Completer(); try { if (mounted) { final String? newTag = await Navigator.push( context, MaterialPageRoute( builder: (context) => DetailView( initialItemId: item.id, mediaItems: mediaItems, type: _selectedType, mode: _selectedMode, random: _random, tagname: _selectedTag, ), ), ); if (newTag != null) { setState(() { if (newTag == '___empty___') { _selectedTag = null; } else { _selectedTag = newTag; } _refreshMedia(); }); } } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Fehler beim Laden der Details: $e')), ); } } finally { _navigationCompleter?.complete(); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( centerTitle: true, backgroundColor: const Color.fromARGB(255, 43, 43, 43), foregroundColor: const Color.fromARGB(255, 255, 255, 255), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('f0ck v$_version'), Checkbox( value: _random, onChanged: (bool? value) { setState(() { _random = !_random; _refreshMedia(); }); }, ), ], ), ), bottomNavigationBar: BottomAppBar( color: const Color.fromARGB(255, 43, 43, 43), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.end, children: [ DropdownButton( value: _selectedType, dropdownColor: const Color.fromARGB(255, 43, 43, 43), iconEnabledColor: Colors.white, items: ["alles", "image", "video", "audio"].map((String value) { return DropdownMenuItem( value: value, child: Text(value, style: TextStyle(color: Colors.white)), ); }).toList(), onChanged: (String? newValue) { if (newValue != null) { setState(() { _selectedType = newValue; _refreshMedia(); }); } }, ), DropdownButton( value: _modes[_selectedMode], dropdownColor: const Color.fromARGB(255, 43, 43, 43), iconEnabledColor: Colors.white, items: _modes.map((String value) { return DropdownMenuItem( value: value, child: Text(value, style: TextStyle(color: Colors.white)), ); }).toList(), onChanged: (String? newValue) { if (newValue != null) { setState(() { _selectedMode = _modes.indexOf(newValue); _refreshMedia(); }); } }, ), DropdownButton( value: _crossAxisCount, dropdownColor: const Color.fromARGB(255, 43, 43, 43), iconEnabledColor: Colors.white, items: [0, 3, 4].map((int value) { return DropdownMenuItem( value: value, child: Text( value == 0 ? 'auto' : '$value Spalten', style: TextStyle(color: Colors.white), ), ); }).toList(), onChanged: (int? newValue) { if (newValue != null) { setState(() { _crossAxisCount = newValue; }); } }, ), ], ), ), ), body: Stack( children: [ RefreshIndicator( onRefresh: _refreshMedia, child: GridView.builder( controller: _scrollController, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: _calculateCrossAxisCount(context), crossAxisSpacing: 5.0, mainAxisSpacing: 5.0, ), itemCount: mediaItems.length + (isLoading ? 1 : 0), itemBuilder: (context, index) { if (index >= mediaItems.length) { return const Center(child: CircularProgressIndicator()); } final item = mediaItems[index]; return InkWell( onTap: () => _navigateToDetail(item), child: Stack( fit: StackFit.expand, children: [ CachedNetworkImage( imageUrl: item.thumbnailUrl, fit: BoxFit.cover, placeholder: (context, url) => SizedBox.shrink(), errorWidget: (context, url, error) => Icon(Icons.error), ), Align( alignment: FractionalOffset.bottomRight, child: Icon( Icons.square, color: switch (item.mode) { 1 => Colors.green, 2 => Colors.red, _ => Colors.yellow, }, size: 15.0, ), ), ], ), ); }, ), ), if (_selectedTag != null) Positioned( bottom: 20, left: MediaQuery.of(context).size.width * 0.2, right: MediaQuery.of(context).size.width * 0.2, child: Container( padding: const EdgeInsets.symmetric( vertical: 10, horizontal: 20, ), decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.8), borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Tag: $_selectedTag', style: const TextStyle(color: Colors.white), ), IconButton( icon: const Icon(Icons.close, color: Colors.white), onPressed: () { setState(() { _selectedTag = null; _refreshMedia(); }); _scrollController.animateTo( 0.0, duration: const Duration(milliseconds: 500), curve: Curves.easeOut, ); }, ), ], ), ), ), ], ), /*floatingActionButton: FloatingActionButton( backgroundColor: Colors.black.withValues(alpha: 0.8), child: const Icon(Icons.arrow_upward, color: Colors.white), onPressed: () { _scrollController.animateTo( 0.0, duration: const Duration(milliseconds: 500), curve: Curves.easeOut, ); }, ),*/ ); } @override void dispose() { _scrollController.dispose(); _debounceTimer?.cancel(); super.dispose(); } }