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.21+21'; 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"]; @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, ); 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, ); 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) { await Navigator.push( context, MaterialPageRoute( builder: (context) => DetailView( initialItemId: item.id, mediaItems: mediaItems, type: _selectedType, mode: _selectedMode, random: _random, ), ), ); } } 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: 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) => CircularProgressIndicator(), 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 ), ), ], ), ); }, ), ), ); } @override void dispose() { _scrollController.dispose(); _debounceTimer?.cancel(); super.dispose(); } }