mangayomi-mirror/lib/modules/library/widgets/library_body.dart
Moustapha Kodjo Amadou 1256e608c7 Refactor
2026-03-02 11:49:19 +01:00

213 lines
7.1 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/modules/library/providers/isar_providers.dart';
import 'package:mangayomi/modules/library/providers/library_filter_provider.dart';
import 'package:mangayomi/modules/library/providers/library_state_provider.dart';
import 'package:mangayomi/modules/library/widgets/library_gridview_widget.dart';
import 'package:mangayomi/modules/library/widgets/library_listview_widget.dart';
import 'package:mangayomi/modules/widgets/error_text.dart';
import 'package:mangayomi/modules/widgets/progress_center.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/services/library_updater.dart';
/// Displays the library body content for a given category (or uncategorized).
///
/// Uses [filteredLibraryMangaProvider] for cached, optimized filtering
/// instead of calling _filterAndSortManga inline (which was O(N*M) due to
/// per-chapter Isar queries).
class LibraryBody extends ConsumerWidget {
final ItemType itemType;
final int? categoryId;
final bool withoutCategories;
final int downloadFilterType;
final int unreadFilterType;
final int startedFilterType;
final int bookmarkedFilterType;
final bool reverse;
final bool downloadedChapter;
final bool continueReaderBtn;
final bool localSource;
final bool language;
final DisplayType displayType;
final Settings settings;
final bool downloadedOnly;
final String searchQuery;
final bool ignoreFiltersOnSearch;
const LibraryBody({
super.key,
required this.itemType,
this.categoryId,
this.withoutCategories = false,
required this.downloadFilterType,
required this.unreadFilterType,
required this.startedFilterType,
required this.bookmarkedFilterType,
required this.reverse,
required this.downloadedChapter,
required this.continueReaderBtn,
required this.localSource,
required this.language,
required this.displayType,
required this.settings,
required this.downloadedOnly,
required this.searchQuery,
required this.ignoreFiltersOnSearch,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = l10nLocalizations(context)!;
final sortType = ref
.watch(
sortLibraryMangaStateProvider(itemType: itemType, settings: settings),
)
.index;
final mangaIdsList = ref.watch(mangasListStateProvider);
// Choose the right data stream based on whether this is a category tab
final mangaStream = withoutCategories
? ref.watch(
getAllMangaWithoutCategoriesStreamProvider(itemType: itemType),
)
: ref.watch(
getAllMangaStreamProvider(
categoryId: categoryId,
itemType: itemType,
),
);
return mangaStream.when(
data: (data) {
// Use the cached filtering provider instead of inline filtering
final entries = ref.watch(
filteredLibraryMangaProvider(
data: data,
downloadFilterType: downloadFilterType,
unreadFilterType: unreadFilterType,
startedFilterType: startedFilterType,
bookmarkedFilterType: bookmarkedFilterType,
sortType: sortType ?? 0,
downloadedOnly: downloadedOnly,
searchQuery: searchQuery,
ignoreFiltersOnSearch: ignoreFiltersOnSearch,
),
);
if (entries.isEmpty) {
return Center(child: Text(l10n.empty_library));
}
final entriesManga = reverse ? entries.reversed.toList() : entries;
return RefreshIndicator(
onRefresh: () async {
await updateLibrary(
ref: ref,
context: context,
mangaList: data,
itemType: itemType,
);
},
child: displayType == DisplayType.list
? LibraryListViewWidget(
entriesManga: entriesManga,
continueReaderBtn: continueReaderBtn,
downloadedChapter: downloadedChapter,
language: language,
mangaIdsList: mangaIdsList,
localSource: localSource,
)
: LibraryGridViewWidget(
entriesManga: entriesManga,
isCoverOnlyGrid: !(displayType == DisplayType.compactGrid),
isComfortableGrid: displayType == DisplayType.comfortableGrid,
continueReaderBtn: continueReaderBtn,
downloadedChapter: downloadedChapter,
language: language,
mangaIdsList: mangaIdsList,
localSource: localSource,
itemType: itemType,
),
);
},
error: (error, _) => ErrorText(error),
loading: () => const ProgressCenter(),
);
}
}
/// Badge showing the number of items in a category tab.
///
/// Uses the cached filtering provider for consistent results without
/// re-running the filter logic.
class CategoryBadge extends ConsumerWidget {
final ItemType itemType;
final int categoryId;
final int downloadFilterType;
final int unreadFilterType;
final int startedFilterType;
final int bookmarkedFilterType;
final Settings settings;
final bool downloadedOnly;
final String searchQuery;
final bool ignoreFiltersOnSearch;
const CategoryBadge({
super.key,
required this.itemType,
required this.categoryId,
required this.downloadFilterType,
required this.unreadFilterType,
required this.startedFilterType,
required this.bookmarkedFilterType,
required this.settings,
required this.downloadedOnly,
required this.searchQuery,
required this.ignoreFiltersOnSearch,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final mangas = ref.watch(
getAllMangaStreamProvider(categoryId: categoryId, itemType: itemType),
);
final sortType = ref
.watch(
sortLibraryMangaStateProvider(itemType: itemType, settings: settings),
)
.index;
return mangas.when(
data: (data) {
final filtered = ref.watch(
filteredLibraryMangaProvider(
data: data,
downloadFilterType: downloadFilterType,
unreadFilterType: unreadFilterType,
startedFilterType: startedFilterType,
bookmarkedFilterType: bookmarkedFilterType,
sortType: sortType ?? 0,
downloadedOnly: downloadedOnly,
searchQuery: searchQuery,
ignoreFiltersOnSearch: ignoreFiltersOnSearch,
),
);
return CircleAvatar(
backgroundColor: Theme.of(context).focusColor,
radius: 8,
child: Text(
filtered.length.toString(),
style: TextStyle(
fontSize: 10,
color: Theme.of(context).textTheme.bodySmall!.color,
),
),
);
},
error: (error, _) => ErrorText(error),
loading: () => const ProgressCenter(),
);
}
}