import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:provider/provider.dart'; import 'package:simplecloudnotifier/api/api_client.dart'; import 'package:simplecloudnotifier/models/channel.dart'; import 'package:simplecloudnotifier/state/app_bar_state.dart'; import 'package:simplecloudnotifier/state/application_log.dart'; import 'package:simplecloudnotifier/state/app_auth.dart'; import 'package:simplecloudnotifier/pages/channel_list/channel_list_item.dart'; import 'package:simplecloudnotifier/utils/navi.dart'; class ChannelRootPage extends StatefulWidget { const ChannelRootPage({super.key, required this.isVisiblePage}); final bool isVisiblePage; @override State createState() => _ChannelRootPageState(); } class _ChannelRootPageState extends State with RouteAware { final PagingController _pagingController = PagingController.fromValue(PagingState(nextPageKey: null, itemList: [], error: null), firstPageKey: 0); bool _isInitialized = false; bool _reloadEnqueued = false; @override void initState() { super.initState(); _pagingController.addPageRequestListener(_fetchPage); if (widget.isVisiblePage && !_isInitialized) _realInitState(); } @override void didChangeDependencies() { super.didChangeDependencies(); Navi.modalRouteObserver.subscribe(this, ModalRoute.of(context)!); } @override void dispose() { ApplicationLog.debug('ChannelRootPage::dispose'); _pagingController.dispose(); Navi.modalRouteObserver.unsubscribe(this); super.dispose(); } @override void didUpdateWidget(ChannelRootPage oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.isVisiblePage != widget.isVisiblePage && widget.isVisiblePage) { if (!_isInitialized) { _realInitState(); } else { _backgroundRefresh(); } } } @override void didPush() { // ... } @override void didPopNext() { if (_reloadEnqueued) { ApplicationLog.debug('[ChannelList::RouteObserver] --> didPopNext (will background-refresh) (_reloadEnqueued == true)'); () async { _reloadEnqueued = false; AppBarState().setLoadingIndeterminate(true); await Future.delayed(const Duration(milliseconds: 500), () {}); // prevents flutter bug where the whole process crashes ?!? await _backgroundRefresh(); }(); } } void _realInitState() { ApplicationLog.debug('ChannelRootPage::_realInitState'); _pagingController.refresh(); _isInitialized = true; } Future _fetchPage(int pageKey) async { final acc = Provider.of(context, listen: false); ApplicationLog.debug('Start ChannelList::_pagingController::_fetchPage [ ${pageKey} ]'); if (!acc.isAuth()) { _pagingController.error = 'Not logged in'; return; } try { final items = (await APIClient.getChannelList(acc, ChannelSelector.all)).toList(); items.sort((a, b) => -1 * (a.channel.timestampLastSent ?? '').compareTo(b.channel.timestampLastSent ?? '')); _pagingController.value = PagingState(nextPageKey: null, itemList: items, error: null); } catch (exc, trace) { _pagingController.error = exc.toString(); ApplicationLog.error('Failed to list channels: ' + exc.toString(), trace: trace); } } Future _backgroundRefresh() async { final acc = Provider.of(context, listen: false); ApplicationLog.debug('Start background refresh of channel list'); if (!acc.isAuth()) { _pagingController.error = 'Not logged in'; return; } try { await Future.delayed(const Duration(seconds: 0), () {}); // this is annoyingly important - otherwise we call setLoadingIndeterminate directly in initStat() and get an exception.... AppBarState().setLoadingIndeterminate(true); final items = (await APIClient.getChannelList(acc, ChannelSelector.all)).toList(); items.sort((a, b) => -1 * (a.channel.timestampLastSent ?? '').compareTo(b.channel.timestampLastSent ?? '')); setState(() { _pagingController.value = PagingState(nextPageKey: null, itemList: items, error: null); }); } catch (exc, trace) { setState(() { _pagingController.error = exc.toString(); }); ApplicationLog.error('Failed to list channels: ' + exc.toString(), trace: trace); } finally { AppBarState().setLoadingIndeterminate(false); } } @override Widget build(BuildContext context) { return Scaffold( body: RefreshIndicator( onRefresh: () => Future.sync( () => _pagingController.refresh(), ), child: PagedListView( pagingController: _pagingController, builderDelegate: PagedChildBuilderDelegate( itemBuilder: (context, item, index) => ChannelListItem( channel: item.channel, subscription: item.subscription, mode: ChannelListItemMode.Messages, onChannelListReloadTrigger: _enqueueReload, onSubscriptionChanged: (channelID, subscription) { setState(() { final idx = _pagingController.itemList?.indexWhere((p) => p.channel.channelID == channelID); if (idx != null && idx >= 0) _pagingController.itemList![idx] = ChannelWithSubscription(channel: _pagingController.itemList![idx].channel, subscription: subscription); }); }, ), ), ), ), floatingActionButton: FloatingActionButton( heroTag: 'fab_channel_list_qr', onPressed: () { //TODO scan qr code to subscribe channel }, child: const Icon(FontAwesomeIcons.qrcode), ), ); } void _enqueueReload() { _reloadEnqueued = true; } }