diff --git a/assets/images/menu.webp b/assets/images/menu.webp new file mode 100644 index 0000000..ff6b76a Binary files /dev/null and b/assets/images/menu.webp differ diff --git a/lib/main.dart b/lib/main.dart index 89ff36b..f73b914 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,12 +1,25 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:f0ckapp/providers/MediaProvider.dart'; +import 'package:f0ckapp/providers/ThemeProvider.dart'; import 'package:f0ckapp/screens/MediaGrid.dart'; +import 'package:provider/provider.dart'; + void main() async { WidgetsFlutterBinding.ensureInitialized(); await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); - runApp(const F0ckApp()); + + runApp( + MultiProvider( + providers: [ + ChangeNotifierProvider(create: (context) => ThemeProvider()), + ChangeNotifierProvider(create: (context) => MediaProvider()) + ], + child: F0ckApp() + ) + ); } class F0ckApp extends StatelessWidget { @@ -14,11 +27,11 @@ class F0ckApp extends StatelessWidget { @override Widget build(BuildContext context) { + final themeProvider = Provider.of(context); + return MaterialApp( debugShowCheckedModeBanner: false, - theme: ThemeData( - scaffoldBackgroundColor: const Color.fromARGB(255, 23, 23, 23), - ), + theme: themeProvider.themeData, home: Scaffold( body: MediaGrid(), ), diff --git a/lib/providers/MediaProvider.dart b/lib/providers/MediaProvider.dart new file mode 100644 index 0000000..0a7acf7 --- /dev/null +++ b/lib/providers/MediaProvider.dart @@ -0,0 +1,88 @@ +import 'package:f0ckapp/services/Api.dart'; +import 'package:flutter/material.dart'; +import 'package:f0ckapp/models/MediaItem.dart'; + +class MediaProvider extends ChangeNotifier { + int _typeid = 0; + int _mode = 0; + bool _random = false; + String? _tag; + int _crossAxisCount = 0; + final List _mediaItems = []; + bool _isLoading = false; + + List types = ["alles", "image", "video", "audio"]; + List modes = ["sfw", "nsfw", "untagged", "all"]; + + String get type => types[_typeid]; + int get typeid => _typeid; + int get mode => _mode; + bool get random => _random; + String? get tag => _tag; + int get crossAxisCount => _crossAxisCount; + List get mediaItems => _mediaItems; + bool get isLoading => _isLoading; + + void setType(String type) { + _typeid = types.indexOf(type); + loadMedia(reload: true); + } + + void setMode(int mode) { + _mode = mode; + loadMedia(reload: true); + } + + void toggleRandom() { + _random = !_random; + loadMedia(reload: true); + } + + void setTag(String? tag) { + _tag = tag; + loadMedia(reload: true); + } + + void setCrossAxisCount(int crossAxisCount) { + _crossAxisCount = crossAxisCount; + notifyListeners(); + } + + void setMediaItems(List mediaItems) { + _mediaItems.clear(); + addMediaItems(mediaItems); + notifyListeners(); + } + + void addMediaItems(List newItems) { + _mediaItems.addAll(newItems); + notifyListeners(); + } + + Future loadMedia({bool reload = false}) async { + if (_isLoading) return; + _isLoading = true; + notifyListeners(); + + try { + final newMedia = await fetchMedia( + older: reload + ? null + : _mediaItems.isNotEmpty + ? _mediaItems.last.id + : null, + type: type, + mode: mode, + random: random, + tag: tag, + ); + + reload ? setMediaItems(newMedia) : addMediaItems(newMedia); + } catch (e) { + debugPrint('Fehler beim Laden der Medien: $e'); + } finally { + _isLoading = false; + notifyListeners(); + } + } +} diff --git a/lib/providers/ThemeProvider.dart b/lib/providers/ThemeProvider.dart new file mode 100644 index 0000000..2576fb4 --- /dev/null +++ b/lib/providers/ThemeProvider.dart @@ -0,0 +1,132 @@ +import 'package:flutter/material.dart'; + +final ThemeData f0ckTheme = ThemeData( + brightness: Brightness.dark, + primaryColor: Color(0xFF9FFF00), + scaffoldBackgroundColor: Color(0xFF000000), + colorScheme: ColorScheme.dark( + primary: Color(0xFF9FFF00), + secondary: Color(0xFF262626), + surface: Color(0xFF232323), + onPrimary: Color(0xFF000000), + onSecondary: Color(0xFFFFFFFF), + onSurface: Color(0xFFFFFFFF), + ), + appBarTheme: AppBarTheme( + backgroundColor: Color(0xFF2B2B2B), + foregroundColor: Color(0xFF9FFF00), + elevation: 2, + ), + textTheme: TextTheme( + bodyLarge: TextStyle(fontFamily: 'VCR', color: Color(0xFFFFFFFF)), + bodyMedium: TextStyle(fontFamily: 'monospace', color: Color(0xFFFFFFFF)), + ), + buttonTheme: ButtonThemeData( + buttonColor: Color(0xFF9FFF00), + textTheme: ButtonTextTheme.primary, + ), + scrollbarTheme: ScrollbarThemeData( + thumbColor: WidgetStateProperty.all(Color(0xFF2B2B2B)), + trackColor: WidgetStateProperty.all(Color(0xFF424242)), + ), +); + +final ThemeData paperTheme = ThemeData( + brightness: Brightness.light, + primaryColor: Color(0xFF000000), + scaffoldBackgroundColor: Color(0xFFFFFFFF), + colorScheme: ColorScheme.light( + primary: Color(0xFF000000), + secondary: Color(0xFF262626), + surface: Color(0xFFFFFFFF), + onPrimary: Color(0xFFFFFFFF), + onSecondary: Color(0xFFFFFFFF), + onSurface: Color(0xFF000000), + ), + appBarTheme: AppBarTheme( + backgroundColor: Color(0xFFFFFFFF), + foregroundColor: Color(0xFF000000), + elevation: 0, + ), + textTheme: TextTheme( + bodyLarge: TextStyle(fontFamily: 'VCR', color: Color(0xFF000000)), + bodyMedium: TextStyle(fontFamily: 'monospace', color: Color(0xFF000000)), + ), + buttonTheme: ButtonThemeData( + buttonColor: Color(0xFF000000), + textTheme: ButtonTextTheme.primary, + ), +); + +final ThemeData f0ck95Theme = ThemeData( + brightness: Brightness.light, + primaryColor: Color(0xFFC0C0C0), + scaffoldBackgroundColor: Color(0xFF008080), + colorScheme: ColorScheme.light( + primary: Color(0xFFC0C0C0), + secondary: Color(0xFF808080), + surface: Color(0xFFC0C0C0), + onPrimary: Color(0xFF000000), + onSecondary: Color(0xFFFFFFFF), + ), + appBarTheme: AppBarTheme( + backgroundColor: Color(0xFFC0C0C0), + foregroundColor: Color(0xFF000000), + elevation: 2, + ), + textTheme: TextTheme( + bodyLarge: TextStyle(fontFamily: 'VCR', color: Color(0xFF000000)), + bodyMedium: TextStyle(fontFamily: 'monospace', color: Color(0xFF000000)), + ), + buttonTheme: ButtonThemeData( + buttonColor: Color(0xFF000000), + textTheme: ButtonTextTheme.primary, + ), + scrollbarTheme: ScrollbarThemeData( + thumbColor: WidgetStateProperty.all(Color(0xFF2B2B2B)), + trackColor: WidgetStateProperty.all(Color(0xFF424242)), + ), +); + +final ThemeData f0ck95dTheme = ThemeData( + brightness: Brightness.dark, + primaryColor: Color(0xFFFFFFFF), + scaffoldBackgroundColor: Color(0xFF0E0F0F), + colorScheme: ColorScheme.dark( + primary: Color(0xFFFFFFFF), + secondary: Color(0xFFC0C0C0), + surface: Color(0xFF333131), + onPrimary: Color(0xFF000000), + onSecondary: Color(0xFFFFFFFF), + ), + appBarTheme: AppBarTheme( + backgroundColor: Color(0xFF0B0A0A), + foregroundColor: Color(0xFFFFFFFF), + elevation: 2, + ), + textTheme: TextTheme( + bodyLarge: TextStyle(fontFamily: 'VCR', color: Color(0xFFFFFFFF)), + bodyMedium: TextStyle(fontFamily: 'monospace', color: Color(0xFFFFFFFF)), + ), + buttonTheme: ButtonThemeData( + buttonColor: Color(0xFFFFFFFF), + textTheme: ButtonTextTheme.primary, + ), + scrollbarTheme: ScrollbarThemeData( + thumbColor: WidgetStateProperty.all(Color(0xFF2B2B2B)), + trackColor: WidgetStateProperty.all(Color(0xFF424242)), + ), +); + + + +class ThemeProvider extends ChangeNotifier { + ThemeData _themeData = f0ck95dTheme; + + ThemeData get themeData => _themeData; + + /*void toggleTheme() { + _themeData = _themeData == lightTheme ? darkTheme : lightTheme; + notifyListeners(); + }*/ +} diff --git a/lib/screens/DetailView.dart b/lib/screens/DetailView.dart index 25ef09c..06872f1 100644 --- a/lib/screens/DetailView.dart +++ b/lib/screens/DetailView.dart @@ -1,29 +1,18 @@ import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:provider/provider.dart'; import 'package:f0ckapp/models/MediaItem.dart'; import 'package:f0ckapp/services/Api.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'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; class DetailView extends StatefulWidget { final int initialItemId; - final List mediaItems; - final String type; - final int mode; - final bool random; - final String? tagname; - const DetailView({ - super.key, - required this.initialItemId, - required this.mediaItems, - required this.type, - required this.mode, - required this.random, - required this.tagname, - }); + const DetailView({super.key, required this.initialItemId}); @override State createState() => _DetailViewState(); @@ -31,57 +20,34 @@ class DetailView extends StatefulWidget { class _DetailViewState extends State { late PageController _pageController; - late List mediaItems; - String? _tagname; - int currentItemId = 0; bool isLoading = false; @override void initState() { super.initState(); - mediaItems = widget.mediaItems; - _tagname = widget.tagname; - final initialIndex = mediaItems.indexWhere( + final provider = Provider.of(context, listen: false); + + final initialIndex = provider.mediaItems.indexWhere( (item) => item.id == widget.initialItemId, ); _pageController = PageController(initialPage: initialIndex); - currentItemId = mediaItems[initialIndex].id; - - int? lastLoadedIndex; - _pageController.addListener(() async { - final newIndex = _pageController.page?.round(); - if (newIndex != null && - newIndex < mediaItems.length && - newIndex != lastLoadedIndex) { - setState(() => currentItemId = mediaItems[newIndex].id); - lastLoadedIndex = newIndex; - - _preloadAdjacentMedia(newIndex); - } - - if (_pageController.position.pixels >= - _pageController.position.maxScrollExtent - 100) { - _loadMoreMedia(); - } - }); _preloadAdjacentMedia(initialIndex); } void _preloadAdjacentMedia(int index) async { - if (index + 1 < mediaItems.length) { - final nextUrl = mediaItems[index + 1].mediaUrl; + final provider = Provider.of(context, listen: false); + if (index + 1 < provider.mediaItems.length) { + final nextUrl = provider.mediaItems[index + 1].mediaUrl; if (await DefaultCacheManager().getFileFromCache(nextUrl) == null) { await DefaultCacheManager().downloadFile(nextUrl); - print('preload ${mediaItems[index + 1].id}'); } } if (index - 1 >= 0) { - final prevUrl = mediaItems[index - 1].mediaUrl; + final prevUrl = provider.mediaItems[index - 1].mediaUrl; if (await DefaultCacheManager().getFileFromCache(prevUrl) == null) { await DefaultCacheManager().downloadFile(prevUrl); - print('preload ${mediaItems[index - 1].id}'); } } } @@ -90,116 +56,84 @@ class _DetailViewState extends State { if (isLoading) return; setState(() => isLoading = true); + final provider = Provider.of(context, listen: false); + try { final newMedia = await fetchMedia( - older: mediaItems.last.id.toString(), - type: widget.type, - mode: widget.mode, - random: widget.random, - tag: _tagname, + older: provider.mediaItems.last.id, + type: provider.type, + mode: provider.mode, + random: provider.random, + tag: provider.tag, ); if (mounted && newMedia.isNotEmpty) { - setState(() { - mediaItems.addAll(newMedia); - isLoading = false; - }); + setState(() => provider.mediaItems.addAll(newMedia)); } } catch (e) { - _showError("Ein unerwarteter Fehler ist aufgetreten: $e"); - } - } - - Future _refreshMediaItem() async { - try { - final updatedItem = await fetchMediaDetail(currentItemId); - if (mounted) { - final index = mediaItems.indexWhere((item) => item.id == currentItemId); - if (index != -1) { - setState(() => mediaItems[index] = updatedItem); - } - } - } catch (e) { - _showError("Fehler beim Aktualisieren des Items: $e"); + _showError("Fehler beim Laden der Medien: $e"); + } finally { + setState(() => isLoading = false); } } void _showError(String message) { if (!mounted) return; - - final messenger = ScaffoldMessenger.of(context); - messenger.hideCurrentSnackBar(); - messenger.showSnackBar(SnackBar(content: Text(message))); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(message))); } @override Widget build(BuildContext context) { + final provider = Provider.of(context); + return Scaffold( - backgroundColor: const Color(0xFF171717), appBar: AppBar( - backgroundColor: const Color(0xFF2B2B2B), - foregroundColor: Colors.white, - title: Text('f0ck #$currentItemId (${widget.type})'), centerTitle: true, + title: Text('f0ck #${widget.initialItemId} (${provider.type})'), ), body: Stack( children: [ PageTransformer( controller: _pageController, - pages: mediaItems.map((item) { - final isActive = item.id == currentItemId; - return Scaffold( - body: SafeArea( - child: SmartRefreshIndicator( - onRefresh: _refreshMediaItem, - child: _buildMediaItem(item, isActive), - ), + pages: provider.mediaItems.map((item) { + return SafeArea( + child: SmartRefreshIndicator( + onRefresh: _loadMoreMedia, + child: _buildMediaItem(item), ), ); }).toList(), ), - if (_tagname != null) - Positioned( - bottom: 60, - 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: $_tagname', - style: const TextStyle(color: Colors.white), - ), - IconButton( - icon: const Icon(Icons.close, color: Colors.white), - onPressed: () { - setState(() { - _tagname = '___empty___'; - Navigator.pop(context, _tagname); - }); - }, - ), - ], - ), - ), - ), ], ), + persistentFooterButtons: provider.tag != null + ? [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('tag: '), + InputChip( + label: Text(provider.tag!), + backgroundColor: const Color(0xFF090909), + labelStyle: const TextStyle(color: Colors.white), + onDeleted: () { + provider.setTag(null); + Navigator.pop(context); + }, + ), + ], + ), + ] + : null, ); } - Widget _buildMediaItem(MediaItem item, bool isActive) { + Widget _buildMediaItem(MediaItem item) { + final provider = Provider.of(context); + return SingleChildScrollView( child: Column( - mainAxisAlignment: MainAxisAlignment.center, children: [ if (item.mime.startsWith('image')) CachedNetworkImage( @@ -209,7 +143,7 @@ class _DetailViewState extends State { errorWidget: (context, url, error) => Icon(Icons.error), ) else - VideoWidget(details: item, isActive: isActive), + VideoWidget(details: item, isActive: true), const SizedBox(height: 20), Text( item.mime, @@ -221,26 +155,20 @@ class _DetailViewState extends State { spacing: 5.0, children: item.tags.map((tag) { return ActionChip( - label: Text( - tag.tag, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), + onPressed: () { + if (tag.tag == 'sfw' || tag.tag == 'nsfw') return; + setState(() { + provider.setTag(tag.tag); + Navigator.pop(context); + }); + }, + label: Text(tag.tag), backgroundColor: switch (tag.id) { 1 => Colors.green, 2 => Colors.red, _ => const Color(0xFF090909), }, - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - onPressed: () async { - if (tag.tag == 'sfw' || tag.tag == 'nsfw') return; - setState(() => Navigator.pop(context, tag.tag)); - }, + labelStyle: const TextStyle(color: Colors.white), ); }).toList(), ), diff --git a/lib/screens/MediaGrid.dart b/lib/screens/MediaGrid.dart index 1e67478..a259941 100644 --- a/lib/screens/MediaGrid.dart +++ b/lib/screens/MediaGrid.dart @@ -1,9 +1,9 @@ -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:cached_network_image/cached_network_image.dart'; +import 'package:provider/provider.dart'; import 'package:f0ckapp/screens/DetailView.dart'; -import 'dart:async'; +import 'package:f0ckapp/services/Api.dart'; +import 'package:f0ckapp/providers/MediaProvider.dart'; class MediaGrid extends StatefulWidget { const MediaGrid({super.key}); @@ -14,256 +14,175 @@ class MediaGrid extends StatefulWidget { 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(); + + final provider = Provider.of(context, listen: false); + + Future.microtask(() { + provider.loadMedia(); + }); + _scrollController.addListener(() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 100) { - _debounceLoadMedia(); + provider.loadMedia(); } }); } - 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) { + final provider = Provider.of(context); + final GlobalKey scaffoldKey = GlobalKey(); + return Scaffold( + key: scaffoldKey, 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, + //centerTitle: true, + title: Text('f0ck v1.0.26+26'), + actions: [ + DropdownButton( + // mode + value: provider.modes[provider.mode], + isDense: true, + icon: SizedBox.shrink(), + items: provider.modes.map((String value) { + return DropdownMenuItem(value: value, child: Text(value)); + }).toList(), + onChanged: (String? newValue) { + if (newValue != null) { + provider.setMode(provider.modes.indexOf(newValue)); + } + }, + ), + IconButton( + icon: const Icon(Icons.menu), + onPressed: () { + scaffoldKey.currentState?.openEndDrawer(); + }, + ), + ], + ), + endDrawer: Drawer( + child: ListView( + padding: EdgeInsets.zero, children: [ - Text('f0ck v$_version'), - Checkbox( - value: _random, - onChanged: (bool? value) { - setState(() { - _random = !_random; - _refreshMedia(); - }); + DrawerHeader( + padding: EdgeInsets.all(0), + child: Image.asset('assets/images/menu.webp', fit: BoxFit.cover), + ), + ListTile( + title: Text( + 'All', + style: TextStyle( + fontWeight: provider.type == 'alles' + ? FontWeight.bold + : FontWeight.normal, + color: provider.type == 'alles' ? Colors.blue : Colors.white, + ), + ), + onTap: () { + provider.setType('all'); + }, + ), + ListTile( + title: Text( + 'Images', + style: TextStyle( + fontWeight: provider.type == 'image' + ? FontWeight.bold + : FontWeight.normal, + color: provider.type == 'image' ? Colors.blue : Colors.white, + ), + ), + onTap: () { + provider.setType('image'); + }, + ), + ListTile( + title: Text( + 'Videos', + style: TextStyle( + fontWeight: provider.type == 'video' + ? FontWeight.bold + : FontWeight.normal, + color: provider.type == 'video' ? Colors.blue : Colors.white, + ), + ), + onTap: () { + provider.setType('video'); + }, + ), + ListTile( + title: Text( + 'Audio', + style: TextStyle( + fontWeight: provider.type == 'audio' + ? FontWeight.bold + : FontWeight.normal, + color: provider.type == 'audio' ? Colors.blue : Colors.white, + ), + ), + onTap: () { + provider.setType('audio'); }, ), ], ), ), - 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(); - }); - } - }, + persistentFooterButtons: provider.tag != null + ? [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('tag: '), + InputChip( + label: Text(provider.tag!), + backgroundColor: const Color(0xFF090909), + labelStyle: const TextStyle(color: Colors.white), + onDeleted: () { + provider.setTag(null); + }, + ), + ], ), - 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( + ] + : null, + body: RefreshIndicator( + onRefresh: () async { + await provider.loadMedia(reload: true); + }, + child: Consumer( + builder: (context, mediaProvider, child) { + return GridView.builder( controller: _scrollController, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: _calculateCrossAxisCount(context), + crossAxisCount: mediaProvider.crossAxisCount == 0 + ? (MediaQuery.of(context).size.width / 110) + .clamp(3, 5) + .toInt() + : mediaProvider.crossAxisCount, crossAxisSpacing: 5.0, mainAxisSpacing: 5.0, ), - itemCount: mediaItems.length + (isLoading ? 1 : 0), + itemCount: + provider.mediaItems.length + (provider.isLoading ? 1 : 0), itemBuilder: (context, index) { - if (index >= mediaItems.length) { + if (index >= provider.mediaItems.length) { return const Center(child: CircularProgressIndicator()); } - final item = mediaItems[index]; + final item = provider.mediaItems[index]; return InkWell( - onTap: () => _navigateToDetail(item), + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DetailView(initialItemId: item.id), + ), + ), child: Stack( fit: StackFit.expand, children: [ @@ -289,67 +208,10 @@ class _MediaGridState extends State { ), ); }, - ), - ), - 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(); - } } diff --git a/lib/services/Api.dart b/lib/services/Api.dart index 9c88cfa..66c6eed 100644 --- a/lib/services/Api.dart +++ b/lib/services/Api.dart @@ -4,7 +4,7 @@ import 'package:http/http.dart' as http; import 'package:f0ckapp/models/MediaItem.dart'; Future> fetchMedia({ - String? older, + int? older, String? type, int? mode, bool? random, @@ -16,7 +16,7 @@ Future> fetchMedia({ 'mode': (mode ?? 0).toString(), 'random': (random! ? 1 : 0).toString(), if (tag != null) 'tag': tag, - if (older != null) 'older': older, + if (older != null) 'older': older.toString(), }, ); diff --git a/pubspec.lock b/pubspec.lock index 565f08c..d3322be 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -256,6 +256,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" octo_image: dependency: transitive description: @@ -336,6 +344,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" + url: "https://pub.dev" + source: hosted + version: "6.1.5" rxdart: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4ff2c76..62f6432 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.25+25 +version: 1.0.26+26 environment: sdk: ^3.9.0-100.2.beta @@ -37,6 +37,7 @@ dependencies: cupertino_icons: ^1.0.8 cached_network_image: ^3.4.1 cached_video_player_plus: ^3.0.3 + provider: ^6.1.5 dev_dependencies: flutter_test: