import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pullex/pullex.dart'; import 'package:f0ckapp/models/item.dart'; import 'package:f0ckapp/widgets/tagfooter.dart'; import 'package:f0ckapp/utils/customsearchdelegate.dart'; import 'package:f0ckapp/widgets/end_drawer.dart'; import 'package:f0ckapp/widgets/filter_bar.dart'; import 'package:f0ckapp/widgets/media_tile.dart'; import 'package:f0ckapp/controller/settingscontroller.dart'; import 'package:f0ckapp/controller/mediacontroller.dart'; class MediaGrid extends StatefulWidget { const MediaGrid({super.key}); @override State createState() => _MediaGrid(); } class _MediaGrid extends State { final ScrollController _scrollController = ScrollController(); final MediaController _mediaController = Get.put(MediaController()); final SettingsController _settingsController = Get.put(SettingsController()); final PullexRefreshController _refreshController = PullexRefreshController( initialRefresh: false, ); late final _MediaGridAppBar _appBar; late final _MediaGridBody _body; Worker? _filterWorker; @override void initState() { super.initState(); _mediaController.fetchInitial(); _filterWorker = everAll( [ _mediaController.typeIndex, _mediaController.modeIndex, _mediaController.tag, _mediaController.random, ], (_) { WidgetsBinding.instance.addPostFrameCallback((_) { _refreshController.requestRefresh(); }); }, ); _appBar = _MediaGridAppBar(mediaController: _mediaController); _body = _MediaGridBody( refreshController: _refreshController, mediaController: _mediaController, settingsController: _settingsController, scrollController: _scrollController, ); } @override void dispose() { _filterWorker?.dispose(); _scrollController.dispose(); _refreshController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Obx(() { if (_mediaController.loading.value && _mediaController.items.isEmpty) { return Scaffold( appBar: _appBar, body: const Center(child: CircularProgressIndicator()), ); } if (_mediaController.errorMessage.value != null && _mediaController.items.isEmpty) { return Scaffold( appBar: _appBar, body: Center( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error_outline, color: Colors.red, size: 60), const SizedBox(height: 16), Text( '${_mediaController.errorMessage.value}', textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 16), ElevatedButton( onPressed: () => _mediaController.fetchInitial(), child: const Text('Erneut versuchen'), ), ], ), ), ), ); } return Scaffold( endDrawer: const EndDrawer(), endDrawerEnableOpenDragGesture: _settingsController.drawerSwipeEnabled.value, bottomNavigationBar: FilterBar(), appBar: _appBar, body: _body, persistentFooterButtons: _mediaController.tag.value != null ? [TagFooter()] : null, ); }); } } class _MediaGridAppBar extends StatelessWidget implements PreferredSizeWidget { const _MediaGridAppBar({required this.mediaController}); final MediaController mediaController; @override Widget build(BuildContext context) { return AppBar( title: InkWell( onTap: () { if (mediaController.tag.value != null) { mediaController.setTag(null); } }, child: Row( mainAxisSize: MainAxisSize.min, children: [ Image.asset('assets/images/f0ck_small.webp', fit: BoxFit.fitHeight), const SizedBox(width: 10), const Text('fApp', style: TextStyle(fontSize: 24)), ], ), ), actions: [ IconButton( icon: const Icon(Icons.search), onPressed: () async { await showSearch( context: context, delegate: CustomSearchDelegate(), ); }, ), Obx( () => IconButton( icon: Icon( mediaController.isRandomEnabled ? Icons.shuffle_on_outlined : Icons.shuffle, ), onPressed: () { mediaController.toggleRandom(); }, ), ), IconButton( icon: const Icon(Icons.menu), onPressed: () => Scaffold.of(context).openEndDrawer(), ), ], ); } @override Size get preferredSize => const Size.fromHeight(kToolbarHeight); } class _MediaGridBody extends StatelessWidget { const _MediaGridBody({ required this.refreshController, required this.mediaController, required this.settingsController, required this.scrollController, }); final PullexRefreshController refreshController; final MediaController mediaController; final SettingsController settingsController; final ScrollController scrollController; @override Widget build(BuildContext context) { if (mediaController.items.isEmpty && !mediaController.loading.value) { return const Center( child: Text( 'Keine f0cks gefunden.\n\nVersuch mal andere Filter.', textAlign: TextAlign.center, ), ); } return NotificationListener( onNotification: (scrollInfo) { if (!mediaController.loading.value && !mediaController.atEnd.value && scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent - 600) { mediaController.handleLoading(); } return true; }, child: PullexRefresh( controller: refreshController, onRefresh: () async { try { await mediaController.handleRefresh(); } finally { refreshController.refreshCompleted(); } }, header: const WaterDropHeader(), child: Obx( () => GridView.builder( addAutomaticKeepAlives: false, controller: scrollController, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.all(4), itemCount: mediaController.items.length, gridDelegate: settingsController.crossAxisCount.value == 0 ? const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 150, crossAxisSpacing: 5, mainAxisSpacing: 5, childAspectRatio: 1, ) : SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: settingsController.crossAxisCount.value, crossAxisSpacing: 5, mainAxisSpacing: 5, childAspectRatio: 1, ), itemBuilder: (context, index) { final MediaItem item = mediaController.items[index]; return Hero( tag: 'media_${item.id}', child: Material( type: MaterialType.transparency, child: GestureDetector( key: ValueKey(item.id), onTap: () => Get.toNamed('/${item.id}'), child: MediaTile(item: item), ), ), ); }, ), ), ), ); } }