import 'dart:async'; import 'package:flutter/material.dart'; import 'package:hive_flutter/adapters.dart'; import 'package:isar_community/isar.dart'; import 'package:mangayomi/main.dart'; import 'package:mangayomi/models/track.dart'; import 'package:mangayomi/models/track_search.dart'; import 'package:mangayomi/modules/tracker_library/tracker_library_card.dart'; import 'package:mangayomi/modules/tracker_library/tracker_library_section.dart'; import 'package:mangayomi/providers/l10n_providers.dart'; import 'package:super_sliver_list/super_sliver_list.dart'; class TrackerSectionScreen extends StatefulWidget { final TrackLibrarySection section; const TrackerSectionScreen({super.key, required this.section}); @override State createState() => _TrackerSectionScreenState(); } class _TrackerSectionScreenState extends State { String _errorMessage = ""; bool _isLoading = true; List _tracks = []; late StreamSubscription> _trackStreamSub; Map _trackIndex = {}; @override void initState() { super.initState(); _fetchData(); _subscribeToTracks(); } void _subscribeToTracks() { _trackStreamSub = isar.tracks .filter() .itemTypeEqualTo(widget.section.itemType) .mangaIdIsNotNull() .watch(fireImmediately: true) .listen((tracks) { if (mounted) { setState(() { _trackIndex = { for (final t in tracks) if (t.mediaId != null) t.mediaId!: t, }; }); } }); } @override void didUpdateWidget(covariant TrackerSectionScreen oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.section.itemType != widget.section.itemType) { _trackStreamSub.cancel(); _subscribeToTracks(); } _fetchData(); } @override void dispose() { _trackStreamSub.cancel(); super.dispose(); } @override Widget build(BuildContext context) { final l10n = l10nLocalizations(context)!; return Scaffold( body: SizedBox( height: 260, child: Column( children: [ ListTile(dense: true, title: Text(widget.section.name)), Flexible( child: _isLoading ? const Center(child: CircularProgressIndicator()) : Builder( builder: (context) { if (_errorMessage.isNotEmpty) { return Center(child: Text(_errorMessage)); } if (_tracks.isNotEmpty) { return SuperListView.builder( extentPrecalculationPolicy: SuperPrecalculationPolicy(), scrollDirection: Axis.horizontal, itemCount: _tracks.length, itemBuilder: (context, index) { final track = _tracks[index]; return TrackerLibraryImageCard( track: track, itemType: widget.section.itemType, libraryTrack: _trackIndex[track.mediaId], ); }, ); } return Center(child: Text(l10n.no_result)); }, ), ), ], ), ), ); } Future _fetchData() async { final box = await Hive.openBox("tracker_library"); final key = "${widget.section.syncId}-${widget.section.itemType.name}-${widget.section.name}"; if (_checkCache(box, key)) return; try { _errorMessage = ""; _tracks = await widget.section.func() ?? []; box.put(key, _tracks); if (mounted) { setState(() => _isLoading = false); } } catch (e) { if (mounted) { setState(() { _errorMessage = e.toString(); _isLoading = false; }); } } } bool _checkCache(Box box, String key) { if (!widget.section.isSearch && box.containsKey(key)) { final temp = box.get(key); if (temp is List) { _errorMessage = ""; _tracks = temp; if (mounted) setState(() => _isLoading = false); return true; } } return false; } } class SuperPrecalculationPolicy extends ExtentPrecalculationPolicy { @override bool shouldPrecalculateExtents(ExtentPrecalculationContext context) { return context.numberOfItems < 100; } }