This commit is contained in:
		@@ -14,6 +14,7 @@ class MediaController extends GetxController {
 | 
			
		||||
  RxBool loading = false.obs;
 | 
			
		||||
  RxBool atEnd = false.obs;
 | 
			
		||||
  RxBool atStart = false.obs;
 | 
			
		||||
  Rxn<String> errorMessage = Rxn<String>();
 | 
			
		||||
 | 
			
		||||
  RxInt typeIndex = 0.obs;
 | 
			
		||||
  RxInt modeIndex = 0.obs;
 | 
			
		||||
@@ -22,12 +23,10 @@ class MediaController extends GetxController {
 | 
			
		||||
 | 
			
		||||
  void setTypeIndex(int idx) {
 | 
			
		||||
    typeIndex.value = idx;
 | 
			
		||||
    fetchInitial();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setModeIndex(int idx) {
 | 
			
		||||
    modeIndex.value = idx;
 | 
			
		||||
    fetchInitial();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setTag(String? newTag, {bool reload = true}) {
 | 
			
		||||
@@ -37,6 +36,10 @@ class MediaController extends GetxController {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void toggleRandom() {
 | 
			
		||||
    random.value = random.value == 0 ? 1 : 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<List<Favorite>?> toggleFavorite(
 | 
			
		||||
    MediaItem item,
 | 
			
		||||
    bool isFavorite,
 | 
			
		||||
@@ -51,6 +54,7 @@ class MediaController extends GetxController {
 | 
			
		||||
  Future<Feed?> _fetchItems({int? older, int? newer}) async {
 | 
			
		||||
    if (loading.value) return null;
 | 
			
		||||
    loading.value = true;
 | 
			
		||||
    errorMessage.value = null;
 | 
			
		||||
    try {
 | 
			
		||||
      return await _api.fetchItems(
 | 
			
		||||
        older: older,
 | 
			
		||||
@@ -61,11 +65,10 @@ class MediaController extends GetxController {
 | 
			
		||||
        tag: tag.value,
 | 
			
		||||
      );
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      Get.snackbar(
 | 
			
		||||
        'Fehler beim Laden',
 | 
			
		||||
        'Die Daten konnten nicht abgerufen werden. Wo Internet?',
 | 
			
		||||
        snackPosition: SnackPosition.BOTTOM,
 | 
			
		||||
      );
 | 
			
		||||
      final String errorText =
 | 
			
		||||
          'Die Daten konnten nicht abgerufen werden. Wo Internet?';
 | 
			
		||||
      errorMessage.value = errorText;
 | 
			
		||||
      Get.snackbar('Fehler beim Laden', errorText);
 | 
			
		||||
      return null;
 | 
			
		||||
    } finally {
 | 
			
		||||
      loading.value = false;
 | 
			
		||||
@@ -73,7 +76,7 @@ class MediaController extends GetxController {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> fetchInitial({int? id}) async {
 | 
			
		||||
    final result = await _fetchItems(older: id);
 | 
			
		||||
    final Feed? result = await _fetchItems(older: id);
 | 
			
		||||
    if (result != null) {
 | 
			
		||||
      items.assignAll(result.items);
 | 
			
		||||
      atEnd.value = result.atEnd;
 | 
			
		||||
@@ -83,7 +86,7 @@ class MediaController extends GetxController {
 | 
			
		||||
 | 
			
		||||
  Future<void> fetchMore() async {
 | 
			
		||||
    if (items.isEmpty || atEnd.value) return;
 | 
			
		||||
    final result = await _fetchItems(older: items.last.id);
 | 
			
		||||
    final Feed? result = await _fetchItems(older: items.last.id);
 | 
			
		||||
    if (result != null) {
 | 
			
		||||
      final Set<int> existingIds = items.map((e) => e.id).toSet();
 | 
			
		||||
      final List<MediaItem> newItems = result.items
 | 
			
		||||
@@ -95,10 +98,9 @@ class MediaController extends GetxController {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<int> fetchNewer() async {
 | 
			
		||||
    if (items.isEmpty || atStart.value) return 0;
 | 
			
		||||
    final oldLength = items.length;
 | 
			
		||||
    final result = await _fetchItems(newer: items.first.id);
 | 
			
		||||
  Future<void> fetchNewer() async {
 | 
			
		||||
    if (items.isEmpty || atStart.value) return;
 | 
			
		||||
    final Feed? result = await _fetchItems(newer: items.first.id);
 | 
			
		||||
    if (result != null) {
 | 
			
		||||
      final Set<int> existingIds = items.map((e) => e.id).toSet();
 | 
			
		||||
      final List<MediaItem> newItems = result.items
 | 
			
		||||
@@ -107,9 +109,8 @@ class MediaController extends GetxController {
 | 
			
		||||
      items.insertAll(0, newItems);
 | 
			
		||||
      items.refresh();
 | 
			
		||||
      atStart.value = result.atStart;
 | 
			
		||||
      return items.length - oldLength;
 | 
			
		||||
    }
 | 
			
		||||
    return 0;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> handleRefresh() async {
 | 
			
		||||
@@ -122,15 +123,11 @@ class MediaController extends GetxController {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> handleLoading() async {
 | 
			
		||||
    if (loading.value) return;
 | 
			
		||||
    if (!loading.value && !atEnd.value) {
 | 
			
		||||
      await fetchMore();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void toggleRandom() {
 | 
			
		||||
    random.value = random.value == 1 ? 0 : 1;
 | 
			
		||||
    fetchInitial();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool get isRandomEnabled => random.value == 1;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -29,11 +29,27 @@ class _MediaGrid extends State<MediaGrid> {
 | 
			
		||||
 | 
			
		||||
  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,
 | 
			
		||||
@@ -45,6 +61,7 @@ class _MediaGrid extends State<MediaGrid> {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    _filterWorker?.dispose();
 | 
			
		||||
    _scrollController.dispose();
 | 
			
		||||
    _refreshController.dispose();
 | 
			
		||||
    super.dispose();
 | 
			
		||||
@@ -52,19 +69,55 @@ class _MediaGrid extends State<MediaGrid> {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Obx(
 | 
			
		||||
      () => Scaffold(
 | 
			
		||||
    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(scrollController: _scrollController),
 | 
			
		||||
        bottomNavigationBar: FilterBar(),
 | 
			
		||||
        appBar: _appBar,
 | 
			
		||||
        body: _body,
 | 
			
		||||
        persistentFooterButtons: _mediaController.tag.value != null
 | 
			
		||||
            ? [TagFooter()]
 | 
			
		||||
            : null,
 | 
			
		||||
      ),
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -78,7 +131,9 @@ class _MediaGridAppBar extends StatelessWidget implements PreferredSizeWidget {
 | 
			
		||||
    return AppBar(
 | 
			
		||||
      title: InkWell(
 | 
			
		||||
        onTap: () {
 | 
			
		||||
          if (mediaController.tag.value != null) {
 | 
			
		||||
            mediaController.setTag(null);
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        child: Row(
 | 
			
		||||
          mainAxisSize: MainAxisSize.min,
 | 
			
		||||
@@ -106,7 +161,9 @@ class _MediaGridAppBar extends StatelessWidget implements PreferredSizeWidget {
 | 
			
		||||
                  ? Icons.shuffle_on_outlined
 | 
			
		||||
                  : Icons.shuffle,
 | 
			
		||||
            ),
 | 
			
		||||
            onPressed: mediaController.toggleRandom,
 | 
			
		||||
            onPressed: () {
 | 
			
		||||
              mediaController.toggleRandom();
 | 
			
		||||
            },
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        IconButton(
 | 
			
		||||
@@ -134,14 +191,28 @@ class _MediaGridBody extends StatelessWidget {
 | 
			
		||||
  final SettingsController settingsController;
 | 
			
		||||
  final ScrollController scrollController;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return PullexRefresh(
 | 
			
		||||
    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<ScrollNotification>(
 | 
			
		||||
      onNotification: (scrollInfo) {
 | 
			
		||||
        if (!mediaController.loading.value &&
 | 
			
		||||
            !mediaController.atEnd.value &&
 | 
			
		||||
            scrollInfo.metrics.pixels >=
 | 
			
		||||
                scrollInfo.metrics.maxScrollExtent - 600) {
 | 
			
		||||
          mediaController.handleLoading();
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
      },
 | 
			
		||||
      child: PullexRefresh(
 | 
			
		||||
        controller: refreshController,
 | 
			
		||||
      enablePullDown: true,
 | 
			
		||||
      enablePullUp: true,
 | 
			
		||||
      header: const WaterDropHeader(),
 | 
			
		||||
        onRefresh: () async {
 | 
			
		||||
          try {
 | 
			
		||||
            await mediaController.handleRefresh();
 | 
			
		||||
@@ -149,19 +220,13 @@ class _MediaGridBody extends StatelessWidget {
 | 
			
		||||
            refreshController.refreshCompleted();
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
      onLoading: () async {
 | 
			
		||||
        try {
 | 
			
		||||
          await mediaController.handleLoading();
 | 
			
		||||
        } finally {
 | 
			
		||||
          refreshController.loadComplete();
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
        header: const WaterDropHeader(),
 | 
			
		||||
        child: Obx(
 | 
			
		||||
          () => GridView.builder(
 | 
			
		||||
            addAutomaticKeepAlives: false,
 | 
			
		||||
            controller: scrollController,
 | 
			
		||||
          physics: const NeverScrollableScrollPhysics(),
 | 
			
		||||
            shrinkWrap: true,
 | 
			
		||||
            physics: const NeverScrollableScrollPhysics(),
 | 
			
		||||
            padding: const EdgeInsets.all(4),
 | 
			
		||||
            itemCount: mediaController.items.length,
 | 
			
		||||
            gridDelegate: settingsController.crossAxisCount.value == 0
 | 
			
		||||
@@ -193,6 +258,7 @@ class _MediaGridBody extends StatelessWidget {
 | 
			
		||||
            },
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,9 +5,7 @@ import 'package:get/get.dart';
 | 
			
		||||
import 'package:f0ckapp/controller/mediacontroller.dart';
 | 
			
		||||
 | 
			
		||||
class FilterBar extends StatelessWidget {
 | 
			
		||||
  final ScrollController scrollController;
 | 
			
		||||
 | 
			
		||||
  const FilterBar({super.key, required this.scrollController});
 | 
			
		||||
  const FilterBar({super.key});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@@ -32,7 +30,6 @@ class FilterBar extends StatelessWidget {
 | 
			
		||||
              onChanged: (String? newValue) {
 | 
			
		||||
                if (newValue != null) {
 | 
			
		||||
                  c.setTypeIndex(mediaTypes.indexOf(newValue));
 | 
			
		||||
                  scrollController.jumpTo(0);
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
            ),
 | 
			
		||||
@@ -51,7 +48,6 @@ class FilterBar extends StatelessWidget {
 | 
			
		||||
              onChanged: (String? newValue) {
 | 
			
		||||
                if (newValue != null) {
 | 
			
		||||
                  c.setModeIndex(mediaModes.indexOf(newValue));
 | 
			
		||||
                  scrollController.jumpTo(0);
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
            ),
 | 
			
		||||
 
 | 
			
		||||
@@ -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.4.7+68
 | 
			
		||||
version: 1.4.8+69
 | 
			
		||||
 | 
			
		||||
environment:
 | 
			
		||||
  sdk: ^3.9.0-100.2.beta
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user