mangayomi/lib/views/library/library_screen.dart
2023-05-07 21:35:21 +01:00

902 lines
42 KiB
Dart

import 'dart:developer';
import 'package:draggable_menu/draggable_menu.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/providers/hive_provider.dart';
import 'package:mangayomi/utils/media_query.dart';
import 'package:mangayomi/views/library/providers/isar_providers.dart';
import 'package:mangayomi/views/library/providers/library_state_provider.dart';
import 'package:mangayomi/views/library/search_text_form_field.dart';
import 'package:mangayomi/views/library/widgets/library_gridview_widget.dart';
import 'package:mangayomi/views/library/widgets/library_listview_widget.dart';
import 'package:mangayomi/views/manga/detail/widgets/chapter_filter_list_tile_widget.dart';
import 'package:mangayomi/views/more/settings/categoties/providers/isar_providers.dart';
import 'package:mangayomi/views/widgets/error_text.dart';
import 'package:mangayomi/views/widgets/progress_center.dart';
class LibraryScreen extends ConsumerStatefulWidget {
const LibraryScreen({super.key});
@override
ConsumerState<LibraryScreen> createState() => _LibraryScreenState();
}
class _LibraryScreenState extends ConsumerState<LibraryScreen>
with TickerProviderStateMixin {
bool isSearch = false;
final List<Manga> _entries = [];
final _textEditingController = TextEditingController();
late TabController tabBarController;
int tabIndex = 0;
@override
Widget build(BuildContext context) {
final categories = ref.watch(getMangaCategorieStreamProvider);
final withoutCategories =
ref.watch(getAllMangaWithoutCategoriesStreamProvider);
final showCategoryTabs = ref.watch(libraryShowCategoryTabsStateProvider);
final mangaAll = ref.watch(getAllMangaStreamProvider(categoryId: null));
return mangaAll.when(
data: (man) {
return withoutCategories.when(
data: (withoutCategory) {
return categories.when(
data: (data) {
if (data.isNotEmpty && showCategoryTabs) {
final entr = data;
tabBarController = TabController(
length: withoutCategory.isNotEmpty
? entr.length + 1
: entr.length,
vsync: this);
tabBarController.animateTo(tabIndex);
tabBarController.addListener(() {
tabIndex = tabBarController.index;
});
return Consumer(builder: (context, ref, child) {
final reverse = ref.watch(libraryReverseListStateProvider);
final continueReaderBtn = ref
.watch(libraryShowContinueReadingButtonStateProvider);
final showNumbersOfItems =
ref.watch(libraryShowNumbersOfItemsStateProvider);
final downloadedChapter =
ref.watch(libraryDownloadedChaptersStateProvider);
final language = ref.watch(libraryLanguageStateProvider);
final displayType = ref
.read(libraryDisplayTypeStateProvider.notifier)
.getLibraryDisplayTypeValue(
ref.watch(libraryDisplayTypeStateProvider));
final isNotFiltering = ref.watch(
mangasFilterResultStateProvider(mangaList: _entries));
final downloadFilterType = ref.watch(
mangaFilterDownloadedStateProvider(
mangaList: _entries));
final unreadFilterType = ref.watch(
mangaFilterUnreadStateProvider(mangaList: _entries));
final startedFilterType = ref.watch(
mangaFilterStartedStateProvider(mangaList: _entries));
final bookmarkedFilterType = ref.watch(
mangaFilterBookmarkedStateProvider(
mangaList: _entries));
final numberOfItemsList = _filterMangas(
data: man,
downloadFilterType: downloadFilterType,
unreadFilterType: unreadFilterType,
startedFilterType: startedFilterType,
bookmarkedFilterType: bookmarkedFilterType);
final withoutCateogoryNumberOfItemsList = _filterMangas(
data: withoutCategory,
downloadFilterType: downloadFilterType,
unreadFilterType: unreadFilterType,
startedFilterType: startedFilterType,
bookmarkedFilterType: bookmarkedFilterType);
return DefaultTabController(
length: entr.length,
child: Scaffold(
appBar: _appBar(isNotFiltering, showNumbersOfItems,
numberOfItemsList.length),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TabBar(
isScrollable: true,
controller: tabBarController,
tabs: [
if (withoutCategory.isNotEmpty)
for (var i = 0; i < entr.length + 1; i++)
Row(
children: [
Tab(
text: i == 0
? "Default"
: entr[i - 1].name),
const SizedBox(
width: 4,
),
if (showNumbersOfItems)
i == 0
? CircleAvatar(
backgroundColor:
Theme.of(context)
.focusColor,
radius: 8,
child: Text(
withoutCateogoryNumberOfItemsList
.length
.toString(),
style: TextStyle(
fontSize: 10,
color:
Theme.of(context)
.textTheme
.bodySmall!
.color),
),
)
: _categoriNumberOfItems(
downloadFilterType:
downloadFilterType,
unreadFilterType:
unreadFilterType,
startedFilterType:
startedFilterType,
bookmarkedFilterType:
bookmarkedFilterType,
reverse: reverse,
downloadedChapter:
downloadedChapter,
continueReaderBtn:
continueReaderBtn,
categoryId:
entr[i - 1].id!),
],
),
if (withoutCategory.isEmpty)
for (var i = 0; i < entr.length; i++)
Row(
children: [
Tab(text: entr[i].name),
if (showNumbersOfItems)
_categoriNumberOfItems(
downloadFilterType:
downloadFilterType,
unreadFilterType:
unreadFilterType,
startedFilterType:
startedFilterType,
bookmarkedFilterType:
bookmarkedFilterType,
reverse: reverse,
downloadedChapter:
downloadedChapter,
continueReaderBtn:
continueReaderBtn,
categoryId: entr[i].id!),
],
)
]),
Flexible(
child: TabBarView(
controller: tabBarController,
children: [
if (withoutCategory.isNotEmpty)
for (var i = 0; i < entr.length + 1; i++)
i == 0
? _bodyWithoutCategories(
withouCategories: true,
downloadFilterType:
downloadFilterType,
unreadFilterType:
unreadFilterType,
startedFilterType:
startedFilterType,
bookmarkedFilterType:
bookmarkedFilterType,
reverse: reverse,
downloadedChapter:
downloadedChapter,
continueReaderBtn:
continueReaderBtn,
language: language,
displayType: displayType,
ref: ref)
: _bodyWithCatories(
categoryId: entr[i - 1].id!,
downloadFilterType:
downloadFilterType,
unreadFilterType:
unreadFilterType,
startedFilterType:
startedFilterType,
bookmarkedFilterType:
bookmarkedFilterType,
reverse: reverse,
downloadedChapter:
downloadedChapter,
continueReaderBtn:
continueReaderBtn,
language: language,
displayType: displayType,
ref: ref),
if (withoutCategory.isEmpty)
for (var i = 0; i < entr.length; i++)
_bodyWithCatories(
categoryId: entr[i].id!,
downloadFilterType:
downloadFilterType,
unreadFilterType: unreadFilterType,
startedFilterType: startedFilterType,
bookmarkedFilterType:
bookmarkedFilterType,
reverse: reverse,
downloadedChapter: downloadedChapter,
continueReaderBtn: continueReaderBtn,
language: language,
displayType: displayType,
ref: ref)
]))
],
),
),
);
});
}
return Consumer(builder: (context, ref, child) {
final reverse = ref.watch(libraryReverseListStateProvider);
final continueReaderBtn =
ref.watch(libraryShowContinueReadingButtonStateProvider);
final showNumbersOfItems =
ref.watch(libraryShowNumbersOfItemsStateProvider);
final downloadedChapter =
ref.watch(libraryDownloadedChaptersStateProvider);
final language = ref.watch(libraryLanguageStateProvider);
final displayType = ref
.read(libraryDisplayTypeStateProvider.notifier)
.getLibraryDisplayTypeValue(
ref.watch(libraryDisplayTypeStateProvider));
final isNotFiltering = ref.watch(
mangasFilterResultStateProvider(mangaList: _entries));
final downloadFilterType = ref.watch(
mangaFilterDownloadedStateProvider(mangaList: _entries));
final unreadFilterType = ref.watch(
mangaFilterUnreadStateProvider(mangaList: _entries));
final startedFilterType = ref.watch(
mangaFilterStartedStateProvider(mangaList: _entries));
final bookmarkedFilterType = ref.watch(
mangaFilterBookmarkedStateProvider(mangaList: _entries));
final numberOfItemsList = _filterMangas(
data: man,
downloadFilterType: downloadFilterType,
unreadFilterType: unreadFilterType,
startedFilterType: startedFilterType,
bookmarkedFilterType: bookmarkedFilterType);
return Scaffold(
appBar: _appBar(isNotFiltering, showNumbersOfItems,
numberOfItemsList.length),
body: _bodyWithoutCategories(
downloadFilterType: downloadFilterType,
unreadFilterType: unreadFilterType,
startedFilterType: startedFilterType,
bookmarkedFilterType: bookmarkedFilterType,
reverse: reverse,
downloadedChapter: downloadedChapter,
continueReaderBtn: continueReaderBtn,
language: language,
displayType: displayType,
ref: ref));
});
},
error: (Object error, StackTrace stackTrace) {
return ErrorText(error);
},
loading: () {
return const ProgressCenter();
},
);
},
error: (Object error, StackTrace stackTrace) {
return ErrorText(error);
},
loading: () {
return const ProgressCenter();
},
);
},
error: (Object error, StackTrace stackTrace) {
return ErrorText(error);
},
loading: () {
return const ProgressCenter();
},
);
}
Widget _categoriNumberOfItems(
{required int downloadFilterType,
required int unreadFilterType,
required int startedFilterType,
required int bookmarkedFilterType,
required bool reverse,
required bool downloadedChapter,
required bool continueReaderBtn,
required int categoryId}) {
final mangas = ref.watch(getAllMangaStreamProvider(categoryId: categoryId));
return mangas.when(
data: (data) {
final categoriNumberOfItemsList = _filterMangas(
data: data,
downloadFilterType: downloadFilterType,
unreadFilterType: unreadFilterType,
startedFilterType: startedFilterType,
bookmarkedFilterType: bookmarkedFilterType);
return CircleAvatar(
backgroundColor: Theme.of(context).focusColor,
radius: 8,
child: Text(
categoriNumberOfItemsList.length.toString(),
style: TextStyle(
fontSize: 10,
color: Theme.of(context).textTheme.bodySmall!.color),
),
);
},
error: (Object error, StackTrace stackTrace) {
return ErrorText(error);
},
loading: () {
return const ProgressCenter();
},
);
}
Widget _bodyWithCatories(
{required int categoryId,
required int downloadFilterType,
required int unreadFilterType,
required int startedFilterType,
required int bookmarkedFilterType,
required bool reverse,
required bool downloadedChapter,
required bool continueReaderBtn,
required bool language,
required WidgetRef ref,
required DisplayType displayType}) {
final mangas = ref.watch(getAllMangaStreamProvider(categoryId: categoryId));
return Scaffold(
body: mangas.when(
data: (data) {
final entries = _filterMangas(
data: data,
downloadFilterType: downloadFilterType,
unreadFilterType: unreadFilterType,
startedFilterType: startedFilterType,
bookmarkedFilterType: bookmarkedFilterType);
if (entries.isNotEmpty) {
final entriesManga = reverse ? entries.reversed.toList() : entries;
return displayType == DisplayType.list
? LibraryListViewWidget(
entriesManga: entriesManga,
continueReaderBtn: continueReaderBtn,
downloadedChapter: downloadedChapter,
language: language,
)
: LibraryGridViewWidget(
entriesManga: entriesManga,
isCoverOnlyGrid:
displayType == DisplayType.compactGrid ? false : true,
isComfortableGrid:
displayType == DisplayType.comfortableGrid ? true : false,
continueReaderBtn: continueReaderBtn,
downloadedChapter: downloadedChapter,
language: language,
);
}
return const Center(child: Text("Empty Library"));
},
error: (Object error, StackTrace stackTrace) {
return ErrorText(error);
},
loading: () {
return const ProgressCenter();
},
));
}
Widget _bodyWithoutCategories(
{required int downloadFilterType,
required int unreadFilterType,
required int startedFilterType,
required int bookmarkedFilterType,
required bool reverse,
required bool downloadedChapter,
required bool continueReaderBtn,
required bool language,
required DisplayType displayType,
required WidgetRef ref,
bool withouCategories = false}) {
final manga = withouCategories
? ref.watch(getAllMangaWithoutCategoriesStreamProvider)
: ref.watch(getAllMangaStreamProvider(categoryId: null));
return manga.when(
data: (data) {
final entries = _filterMangas(
data: data,
downloadFilterType: downloadFilterType,
unreadFilterType: unreadFilterType,
startedFilterType: startedFilterType,
bookmarkedFilterType: bookmarkedFilterType);
if (entries.isNotEmpty) {
final entriesManga = reverse ? entries.reversed.toList() : entries;
return displayType == DisplayType.list
? LibraryListViewWidget(
entriesManga: entriesManga,
continueReaderBtn: continueReaderBtn,
downloadedChapter: downloadedChapter,
language: language,
)
: LibraryGridViewWidget(
entriesManga: entriesManga,
isCoverOnlyGrid:
displayType == DisplayType.compactGrid ? false : true,
isComfortableGrid:
displayType == DisplayType.comfortableGrid ? true : false,
continueReaderBtn: continueReaderBtn,
downloadedChapter: downloadedChapter,
language: language,
);
}
return const Center(child: Text("Empty Library"));
},
error: (Object error, StackTrace stackTrace) {
return ErrorText(error);
},
loading: () {
return const ProgressCenter();
},
);
}
List<Manga> _filterMangas(
{required List<Manga> data,
required int downloadFilterType,
required int unreadFilterType,
required int startedFilterType,
required int bookmarkedFilterType}) {
return data
.where((element) {
List list = [];
if (downloadFilterType == 1) {
for (var chap in element.chapters) {
final modelChapDownload = ref
.watch(hiveBoxMangaDownloadsProvider)
.get("${chap.mangaId}/${chap.id}", defaultValue: null);
if (modelChapDownload != null &&
modelChapDownload.isDownload == true) {
list.add(true);
}
}
return list.length == element.chapters.length;
} else if (downloadFilterType == 2) {
for (var chap in element.chapters) {
final modelChapDownload = ref
.watch(hiveBoxMangaDownloadsProvider)
.get("${chap.mangaId}/${chap.id}", defaultValue: null);
if (!(modelChapDownload != null &&
modelChapDownload.isDownload == true)) {
list.add(true);
}
}
return list.length == element.chapters.length;
}
return true;
})
.where((element) {
List list = [];
if (unreadFilterType == 1 || startedFilterType == 1) {
for (var chap in element.chapters) {
if (!chap.isRead!) {
list.add(true);
}
}
return list.isNotEmpty;
} else if (unreadFilterType == 2 || startedFilterType == 2) {
List list = [];
for (var chap in element.chapters) {
if (chap.isRead!) {
list.add(true);
}
}
return list.length == element.chapters.length;
}
return true;
})
.where((element) {
List list = [];
if (bookmarkedFilterType == 1) {
for (var chap in element.chapters) {
if (chap.isBookmarked!) {
list.add(true);
}
}
return list.isNotEmpty;
} else if (bookmarkedFilterType == 2) {
List list = [];
for (var chap in element.chapters) {
if (!chap.isBookmarked!) {
list.add(true);
}
}
return list.length == element.chapters.length;
}
return true;
})
.where((element) => _textEditingController.text.isNotEmpty
? element.name!
.toLowerCase()
.contains(_textEditingController.text.toLowerCase())
: true)
.toList();
}
_showDraggableMenu() {
late TabController tabBarController;
tabBarController = TabController(length: 3, vsync: this);
DraggableMenu.open(
context,
DraggableMenu(
barItem: Container(),
uiType: DraggableMenuUiType.softModern,
expandable: true,
expandedHeight: mediaHeight(context, 0.8),
maxHeight: mediaHeight(context, 0.5),
minimizeBeforeFastDrag: true,
child: DefaultTabController(
length: 3,
child: Scaffold(
body: Column(
children: [
TabBar(
controller: tabBarController,
tabs: const [
Tab(text: "Filter"),
Tab(text: "Sort"),
Tab(text: "Display"),
],
),
Flexible(
child:
TabBarView(controller: tabBarController, children: [
Consumer(builder: (context, ref, chil) {
return Column(
children: [
ListTileChapterFilter(
label: "Downloaded",
type: ref.watch(
mangaFilterDownloadedStateProvider(
mangaList: _entries)),
onTap: () {
ref
.read(
mangaFilterDownloadedStateProvider(
mangaList: _entries)
.notifier)
.update();
}),
ListTileChapterFilter(
label: "Unread",
type: ref.watch(
mangaFilterUnreadStateProvider(
mangaList: _entries)),
onTap: () {
ref
.read(mangaFilterUnreadStateProvider(
mangaList: _entries)
.notifier)
.update();
}),
ListTileChapterFilter(
label: "Started",
type: ref.watch(
mangaFilterStartedStateProvider(
mangaList: _entries)),
onTap: () {
ref
.read(mangaFilterStartedStateProvider(
mangaList: _entries)
.notifier)
.update();
}),
ListTileChapterFilter(
label: "Bookmarked",
type: ref.watch(
mangaFilterBookmarkedStateProvider(
mangaList: _entries)),
onTap: () {
setState(() {
ref
.read(
mangaFilterBookmarkedStateProvider(
mangaList: _entries)
.notifier)
.update();
});
}),
],
);
}),
Consumer(builder: (context, ref, chil) {
final reverse =
ref.watch(libraryReverseListStateProvider);
return Column(
children: [
ListTile(
onTap: () {
ref
.read(libraryReverseListStateProvider
.notifier)
.set(!reverse);
},
dense: true,
leading: Icon(reverse
? Icons.arrow_downward_sharp
: Icons.arrow_upward_sharp),
title: const Text("Alphabetically"),
),
],
);
}),
Consumer(builder: (context, ref, chil) {
final display =
ref.watch(libraryDisplayTypeStateProvider);
final displayV = ref
.read(libraryDisplayTypeStateProvider.notifier);
final showCategoryTabs =
ref.watch(libraryShowCategoryTabsStateProvider);
final continueReaderBtn = ref.watch(
libraryShowContinueReadingButtonStateProvider);
final showNumbersOfItems = ref
.watch(libraryShowNumbersOfItemsStateProvider);
final downloadedChapter = ref
.watch(libraryDownloadedChaptersStateProvider);
final language =
ref.watch(libraryLanguageStateProvider);
return SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(
left: 20, top: 10),
child: Row(
children: const [
Text("Display mode"),
],
),
),
Column(
children: DisplayType.values
.map(
(e) => RadioListTile<DisplayType>(
title: Text(
displayV
.getLibraryDisplayTypeName(
e.name),
style: TextStyle(
color: Theme.of(context)
.textTheme
.bodyLarge!
.color,
fontSize: 14),
),
value: e,
groupValue: displayV
.getLibraryDisplayTypeValue(
display),
selected: true,
onChanged: (value) {
displayV.setLibraryDisplayType(
value!);
},
),
)
.toList()),
Padding(
padding: const EdgeInsets.only(
left: 20, top: 10),
child: Row(
children: const [
Text("Badges"),
],
),
),
Padding(
padding:
const EdgeInsets.only(left: 10, top: 5),
child: Column(
children: [
ListTileChapterFilter(
label: "Downloaded chapters",
type: downloadedChapter ? 1 : 0,
onTap: () {
ref
.read(
libraryDownloadedChaptersStateProvider
.notifier)
.set(!downloadedChapter);
}),
ListTileChapterFilter(
label: "Language",
type: language ? 1 : 0,
onTap: () {
ref
.read(
libraryLanguageStateProvider
.notifier)
.set(!language);
}),
],
),
),
Padding(
padding: const EdgeInsets.only(
left: 20, top: 10),
child: Row(
children: const [
Text("Tabs"),
],
),
),
Padding(
padding:
const EdgeInsets.only(left: 10, top: 5),
child: Column(
children: [
ListTileChapterFilter(
label: "Show category tabs",
type: showCategoryTabs ? 1 : 0,
onTap: () {
ref
.read(
libraryShowCategoryTabsStateProvider
.notifier)
.set(!showCategoryTabs);
}),
ListTileChapterFilter(
label: "Show numbers of items",
type: showNumbersOfItems ? 1 : 0,
onTap: () {
ref
.read(
libraryShowNumbersOfItemsStateProvider
.notifier)
.set(!showNumbersOfItems);
}),
],
),
),
Padding(
padding: const EdgeInsets.only(
left: 20, top: 10),
child: Row(
children: const [
Text("Others"),
],
),
),
Padding(
padding:
const EdgeInsets.only(left: 10, top: 5),
child: Column(
children: [
ListTileChapterFilter(
label:
"Show continue reading button",
type: continueReaderBtn ? 1 : 0,
onTap: () {
ref
.read(
libraryShowContinueReadingButtonStateProvider
.notifier)
.set(!continueReaderBtn);
}),
],
),
)
],
),
);
}),
]),
),
],
),
))));
}
AppBar _appBar(
bool isNotFiltering, bool showNumbersOfItems, int numberOfItems) {
return AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
title: isSearch
? null
: Row(
children: [
Text(
'Library',
style: TextStyle(color: Theme.of(context).hintColor),
),
const SizedBox(
width: 10,
),
if (showNumbersOfItems)
Padding(
padding: const EdgeInsets.only(bottom: 3),
child: CircleAvatar(
backgroundColor: Theme.of(context).focusColor,
radius: 10,
child: Text(
numberOfItems.toString(),
style: TextStyle(
fontSize: 12,
color:
Theme.of(context).textTheme.bodySmall!.color),
),
),
),
],
),
actions: [
isSearch
? SeachFormTextField(
onChanged: (value) {
setState(() {});
},
onPressed: () {
setState(() {
isSearch = false;
});
_textEditingController.clear();
},
controller: _textEditingController,
onSuffixPressed: () {
_textEditingController.clear();
setState(() {});
},
)
: IconButton(
splashRadius: 20,
onPressed: () {
setState(() {
isSearch = true;
});
_textEditingController.clear();
},
icon: const Icon(
Icons.search,
)),
IconButton(
splashRadius: 20,
onPressed: () {
_showDraggableMenu();
},
icon: Icon(
Icons.filter_list_sharp,
color: isNotFiltering ? null : Colors.yellow,
)),
PopupMenuButton(
itemBuilder: (context) {
return [
const PopupMenuItem<int>(
value: 0, child: Text("Open random entry")),
];
},
onSelected: (value) {}),
],
);
}
}