2106 lines
94 KiB
Dart
2106 lines
94 KiB
Dart
// ignore_for_file: use_build_context_synchronously
|
|
|
|
import 'dart:io';
|
|
import 'dart:math';
|
|
import 'package:bot_toast/bot_toast.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:isar/isar.dart';
|
|
import 'package:mangayomi/eval/model/m_bridge.dart';
|
|
import 'package:mangayomi/main.dart';
|
|
import 'package:mangayomi/models/category.dart';
|
|
import 'package:mangayomi/models/chapter.dart';
|
|
import 'package:mangayomi/models/download.dart';
|
|
import 'package:mangayomi/models/history.dart';
|
|
import 'package:mangayomi/models/manga.dart';
|
|
import 'package:mangayomi/models/settings.dart';
|
|
import 'package:mangayomi/models/update.dart';
|
|
import 'package:mangayomi/modules/library/providers/add_torrent.dart';
|
|
import 'package:mangayomi/modules/library/providers/local_archive.dart';
|
|
import 'package:mangayomi/modules/manga/detail/providers/update_manga_detail_providers.dart';
|
|
import 'package:mangayomi/modules/more/categories/providers/isar_providers.dart';
|
|
import 'package:mangayomi/modules/widgets/custom_draggable_tabbar.dart';
|
|
import 'package:mangayomi/modules/widgets/manga_image_card_widget.dart';
|
|
import 'package:mangayomi/providers/l10n_providers.dart';
|
|
import 'package:mangayomi/providers/storage_provider.dart';
|
|
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
|
import 'package:mangayomi/modules/library/providers/isar_providers.dart';
|
|
import 'package:mangayomi/modules/library/providers/library_state_provider.dart';
|
|
import 'package:mangayomi/modules/library/widgets/search_text_form_field.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/library/widgets/list_tile_manga_category.dart';
|
|
import 'package:mangayomi/modules/manga/detail/widgets/chapter_filter_list_tile_widget.dart';
|
|
import 'package:mangayomi/modules/manga/detail/widgets/chapter_sort_list_tile_widget.dart';
|
|
import 'package:mangayomi/modules/widgets/error_text.dart';
|
|
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
|
import 'package:mangayomi/utils/global_style.dart';
|
|
|
|
class LibraryScreen extends ConsumerStatefulWidget {
|
|
final ItemType itemType;
|
|
const LibraryScreen({required this.itemType, 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;
|
|
|
|
Future<void> _updateLibrary(List<Manga> mangaList) async {
|
|
botToast(context.l10n.updating_library,
|
|
fontSize: 13, second: 1600, alignY: !context.isTablet ? 0.85 : 1);
|
|
int numbers = 0;
|
|
for (var manga in mangaList) {
|
|
try {
|
|
await ref.read(
|
|
updateMangaDetailProvider(mangaId: manga.id, isInit: false).future);
|
|
} catch (_) {}
|
|
numbers++;
|
|
}
|
|
await Future.doWhile(() async {
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
if (mangaList.length == numbers) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
BotToast.cleanAll();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final settingsStream = ref.watch(getSettingsStreamProvider);
|
|
return settingsStream.when(data: (settingsList) {
|
|
final settings = settingsList.first;
|
|
|
|
final categories =
|
|
ref.watch(getMangaCategorieStreamProvider(itemType: widget.itemType));
|
|
final withoutCategories = ref.watch(
|
|
getAllMangaWithoutCategoriesStreamProvider(
|
|
itemType: widget.itemType));
|
|
final showCategoryTabs = ref.watch(libraryShowCategoryTabsStateProvider(
|
|
itemType: widget.itemType, settings: settings));
|
|
final mangaAll = ref.watch(getAllMangaStreamProvider(
|
|
categoryId: null, itemType: widget.itemType));
|
|
final l10n = l10nLocalizations(context)!;
|
|
return Scaffold(
|
|
body: 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) {
|
|
bool reverse = ref
|
|
.watch(sortLibraryMangaStateProvider(
|
|
itemType: widget.itemType,
|
|
settings: settings))
|
|
.reverse!;
|
|
|
|
final continueReaderBtn = ref.watch(
|
|
libraryShowContinueReadingButtonStateProvider(
|
|
itemType: widget.itemType,
|
|
settings: settings));
|
|
final showNumbersOfItems = ref.watch(
|
|
libraryShowNumbersOfItemsStateProvider(
|
|
itemType: widget.itemType,
|
|
settings: settings));
|
|
final localSource = ref.watch(
|
|
libraryLocalSourceStateProvider(
|
|
itemType: widget.itemType,
|
|
settings: settings));
|
|
final downloadedChapter = ref.watch(
|
|
libraryDownloadedChaptersStateProvider(
|
|
itemType: widget.itemType,
|
|
settings: settings));
|
|
final language = ref.watch(
|
|
libraryLanguageStateProvider(
|
|
itemType: widget.itemType,
|
|
settings: settings));
|
|
final displayType = ref.watch(
|
|
libraryDisplayTypeStateProvider(
|
|
itemType: widget.itemType,
|
|
settings: settings));
|
|
final isNotFiltering = ref.watch(
|
|
mangasFilterResultStateProvider(
|
|
itemType: widget.itemType,
|
|
mangaList: _entries,
|
|
settings: settings));
|
|
final downloadFilterType = ref.watch(
|
|
mangaFilterDownloadedStateProvider(
|
|
itemType: widget.itemType,
|
|
mangaList: _entries,
|
|
settings: settings));
|
|
final unreadFilterType = ref.watch(
|
|
mangaFilterUnreadStateProvider(
|
|
itemType: widget.itemType,
|
|
mangaList: _entries,
|
|
settings: settings));
|
|
final startedFilterType = ref.watch(
|
|
mangaFilterStartedStateProvider(
|
|
itemType: widget.itemType,
|
|
mangaList: _entries,
|
|
settings: settings));
|
|
final bookmarkedFilterType = ref.watch(
|
|
mangaFilterBookmarkedStateProvider(
|
|
itemType: widget.itemType,
|
|
mangaList: _entries,
|
|
settings: settings));
|
|
final sortType = ref
|
|
.watch(sortLibraryMangaStateProvider(
|
|
itemType: widget.itemType,
|
|
settings: settings))
|
|
.index as int;
|
|
final numberOfItemsList = _filterAndSortManga(
|
|
data: man,
|
|
downloadFilterType: downloadFilterType,
|
|
unreadFilterType: unreadFilterType,
|
|
startedFilterType: startedFilterType,
|
|
bookmarkedFilterType: bookmarkedFilterType,
|
|
sortType: sortType);
|
|
final withoutCategoryNumberOfItemsList =
|
|
_filterAndSortManga(
|
|
data: withoutCategory,
|
|
downloadFilterType: downloadFilterType,
|
|
unreadFilterType: unreadFilterType,
|
|
startedFilterType: startedFilterType,
|
|
bookmarkedFilterType: bookmarkedFilterType,
|
|
sortType: sortType);
|
|
|
|
return DefaultTabController(
|
|
length: entr.length,
|
|
child: Scaffold(
|
|
appBar: _appBar(
|
|
isNotFiltering,
|
|
showNumbersOfItems,
|
|
numberOfItemsList.length,
|
|
ref,
|
|
[],
|
|
true,
|
|
withoutCategory.isNotEmpty && _tabIndex == 0
|
|
? null
|
|
: entr[withoutCategory.isNotEmpty
|
|
? _tabIndex - 1
|
|
: _tabIndex]
|
|
.id!,
|
|
settings),
|
|
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
|
|
? l10n.default0
|
|
: entr[i - 1].name),
|
|
const SizedBox(
|
|
width: 4,
|
|
),
|
|
if (showNumbersOfItems)
|
|
i == 0
|
|
? CircleAvatar(
|
|
backgroundColor:
|
|
Theme.of(context)
|
|
.focusColor,
|
|
radius: 8,
|
|
child: Text(
|
|
withoutCategoryNumberOfItemsList
|
|
.length
|
|
.toString(),
|
|
style: TextStyle(
|
|
fontSize: 10,
|
|
color: Theme.of(
|
|
context)
|
|
.textTheme
|
|
.bodySmall!
|
|
.color),
|
|
),
|
|
)
|
|
: _categoriesNumberOfItems(
|
|
downloadFilterType:
|
|
downloadFilterType,
|
|
unreadFilterType:
|
|
unreadFilterType,
|
|
startedFilterType:
|
|
startedFilterType,
|
|
bookmarkedFilterType:
|
|
bookmarkedFilterType,
|
|
reverse: reverse,
|
|
downloadedChapter:
|
|
downloadedChapter,
|
|
continueReaderBtn:
|
|
continueReaderBtn,
|
|
categoryId:
|
|
entr[i - 1].id!,
|
|
settings: settings),
|
|
],
|
|
),
|
|
if (withoutCategory.isEmpty)
|
|
for (var i = 0; i < entr.length; i++)
|
|
Row(
|
|
children: [
|
|
Tab(text: entr[i].name),
|
|
const SizedBox(
|
|
width: 4,
|
|
),
|
|
if (showNumbersOfItems)
|
|
_categoriesNumberOfItems(
|
|
downloadFilterType:
|
|
downloadFilterType,
|
|
unreadFilterType:
|
|
unreadFilterType,
|
|
startedFilterType:
|
|
startedFilterType,
|
|
bookmarkedFilterType:
|
|
bookmarkedFilterType,
|
|
reverse: reverse,
|
|
downloadedChapter:
|
|
downloadedChapter,
|
|
continueReaderBtn:
|
|
continueReaderBtn,
|
|
categoryId: entr[i].id!,
|
|
settings: settings),
|
|
],
|
|
)
|
|
]),
|
|
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,
|
|
localSource: localSource,
|
|
settings: settings)
|
|
: _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,
|
|
localSource: localSource,
|
|
settings: settings),
|
|
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,
|
|
localSource: localSource,
|
|
settings: settings)
|
|
]))
|
|
],
|
|
),
|
|
),
|
|
);
|
|
});
|
|
}
|
|
return Consumer(builder: (context, ref, child) {
|
|
bool reverse = ref
|
|
.watch(sortLibraryMangaStateProvider(
|
|
itemType: widget.itemType, settings: settings))
|
|
.reverse!;
|
|
final continueReaderBtn = ref.watch(
|
|
libraryShowContinueReadingButtonStateProvider(
|
|
itemType: widget.itemType, settings: settings));
|
|
final showNumbersOfItems = ref.watch(
|
|
libraryShowNumbersOfItemsStateProvider(
|
|
itemType: widget.itemType, settings: settings));
|
|
final localSource = ref.watch(
|
|
libraryLocalSourceStateProvider(
|
|
itemType: widget.itemType, settings: settings));
|
|
final downloadedChapter = ref.watch(
|
|
libraryDownloadedChaptersStateProvider(
|
|
itemType: widget.itemType, settings: settings));
|
|
final language = ref.watch(libraryLanguageStateProvider(
|
|
itemType: widget.itemType, settings: settings));
|
|
final displayType = ref.watch(
|
|
libraryDisplayTypeStateProvider(
|
|
itemType: widget.itemType, settings: settings));
|
|
final isNotFiltering = ref.watch(
|
|
mangasFilterResultStateProvider(
|
|
itemType: widget.itemType,
|
|
mangaList: _entries,
|
|
settings: settings));
|
|
final downloadFilterType = ref.watch(
|
|
mangaFilterDownloadedStateProvider(
|
|
itemType: widget.itemType,
|
|
mangaList: _entries,
|
|
settings: settings));
|
|
final unreadFilterType = ref.watch(
|
|
mangaFilterUnreadStateProvider(
|
|
itemType: widget.itemType,
|
|
mangaList: _entries,
|
|
settings: settings));
|
|
final startedFilterType = ref.watch(
|
|
mangaFilterStartedStateProvider(
|
|
itemType: widget.itemType,
|
|
mangaList: _entries,
|
|
settings: settings));
|
|
final bookmarkedFilterType = ref.watch(
|
|
mangaFilterBookmarkedStateProvider(
|
|
itemType: widget.itemType,
|
|
mangaList: _entries,
|
|
settings: settings));
|
|
final sortType = ref
|
|
.watch(sortLibraryMangaStateProvider(
|
|
itemType: widget.itemType, settings: settings))
|
|
.index;
|
|
final numberOfItemsList = _filterAndSortManga(
|
|
data: man,
|
|
downloadFilterType: downloadFilterType,
|
|
unreadFilterType: unreadFilterType,
|
|
startedFilterType: startedFilterType,
|
|
bookmarkedFilterType: bookmarkedFilterType,
|
|
sortType: sortType!);
|
|
return Scaffold(
|
|
appBar: _appBar(
|
|
isNotFiltering,
|
|
showNumbersOfItems,
|
|
numberOfItemsList.length,
|
|
ref,
|
|
numberOfItemsList,
|
|
false,
|
|
null,
|
|
settings),
|
|
body: _bodyWithoutCategories(
|
|
downloadFilterType: downloadFilterType,
|
|
unreadFilterType: unreadFilterType,
|
|
startedFilterType: startedFilterType,
|
|
bookmarkedFilterType: bookmarkedFilterType,
|
|
reverse: reverse,
|
|
downloadedChapter: downloadedChapter,
|
|
continueReaderBtn: continueReaderBtn,
|
|
language: language,
|
|
displayType: displayType,
|
|
ref: ref,
|
|
localSource: localSource,
|
|
settings: settings));
|
|
});
|
|
},
|
|
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();
|
|
},
|
|
),
|
|
bottomNavigationBar: Consumer(builder: (context, ref, child) {
|
|
final isLongPressed = ref.watch(isLongPressedMangaStateProvider);
|
|
final color = Theme.of(context).textTheme.bodyLarge!.color!;
|
|
final mangaIds = ref.watch(mangasListStateProvider);
|
|
return AnimatedContainer(
|
|
curve: Curves.easeIn,
|
|
decoration: BoxDecoration(
|
|
color: context.primaryColor.withValues(alpha: 0.2),
|
|
borderRadius: const BorderRadius.only(
|
|
topLeft: Radius.circular(20),
|
|
topRight: Radius.circular(20))),
|
|
duration: const Duration(milliseconds: 100),
|
|
height: isLongPressed ? 70 : 0,
|
|
width: context.width(1),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: [
|
|
Expanded(
|
|
child: SizedBox(
|
|
height: 70,
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
shadowColor: Colors.transparent,
|
|
elevation: 0,
|
|
backgroundColor: Colors.transparent),
|
|
onPressed: () {
|
|
_openCategory();
|
|
},
|
|
child: Icon(
|
|
Icons.label_outline_rounded,
|
|
color: color,
|
|
)),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: SizedBox(
|
|
height: 70,
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
elevation: 0,
|
|
backgroundColor: Colors.transparent,
|
|
shadowColor: Colors.transparent,
|
|
),
|
|
onPressed: () {
|
|
ref
|
|
.read(mangasSetIsReadStateProvider(
|
|
mangaIds: mangaIds)
|
|
.notifier)
|
|
.set();
|
|
ref.invalidate(
|
|
getAllMangaWithoutCategoriesStreamProvider(
|
|
itemType: widget.itemType));
|
|
ref.invalidate(getAllMangaStreamProvider(
|
|
categoryId: null, itemType: widget.itemType));
|
|
},
|
|
child: Icon(
|
|
Icons.done_all_sharp,
|
|
color: color,
|
|
)),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: SizedBox(
|
|
height: 70,
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
elevation: 0,
|
|
backgroundColor: Colors.transparent,
|
|
shadowColor: Colors.transparent,
|
|
),
|
|
onPressed: () {
|
|
ref
|
|
.read(mangasSetUnReadStateProvider(
|
|
mangaIds: mangaIds)
|
|
.notifier)
|
|
.set();
|
|
ref.invalidate(
|
|
getAllMangaWithoutCategoriesStreamProvider(
|
|
itemType: widget.itemType));
|
|
ref.invalidate(getAllMangaStreamProvider(
|
|
categoryId: null, itemType: widget.itemType));
|
|
},
|
|
child: Icon(
|
|
Icons.remove_done_sharp,
|
|
color: color,
|
|
)),
|
|
),
|
|
),
|
|
// Expanded(
|
|
// child: SizedBox(
|
|
// height: 70,
|
|
// child: ElevatedButton(
|
|
// style: ElevatedButton.styleFrom(
|
|
// elevation: 0,
|
|
// backgroundColor: Colors.transparent,
|
|
// shadowColor: Colors.transparent,
|
|
// ),
|
|
// onPressed: () {},
|
|
// child: Icon(
|
|
// Icons.download_outlined,
|
|
// color: color,
|
|
// )),
|
|
// ),
|
|
// ),
|
|
Expanded(
|
|
child: SizedBox(
|
|
height: 70,
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
elevation: 0,
|
|
backgroundColor: Colors.transparent,
|
|
shadowColor: Colors.transparent,
|
|
),
|
|
onPressed: () {
|
|
_deleteManga();
|
|
},
|
|
child: Icon(
|
|
Icons.delete_outline_outlined,
|
|
color: color,
|
|
)),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}));
|
|
}, error: (error, e) {
|
|
return ErrorText(error);
|
|
}, loading: () {
|
|
return const ProgressCenter();
|
|
});
|
|
}
|
|
|
|
Widget _categoriesNumberOfItems(
|
|
{required int downloadFilterType,
|
|
required int unreadFilterType,
|
|
required int startedFilterType,
|
|
required int bookmarkedFilterType,
|
|
required bool reverse,
|
|
required bool downloadedChapter,
|
|
required bool continueReaderBtn,
|
|
required int categoryId,
|
|
required Settings settings}) {
|
|
final mangas = ref.watch(getAllMangaStreamProvider(
|
|
categoryId: categoryId, itemType: widget.itemType));
|
|
final sortType = ref
|
|
.watch(sortLibraryMangaStateProvider(
|
|
itemType: widget.itemType, settings: settings))
|
|
.index;
|
|
return mangas.when(
|
|
data: (data) {
|
|
final categoriNumberOfItemsList = _filterAndSortManga(
|
|
data: data,
|
|
downloadFilterType: downloadFilterType,
|
|
unreadFilterType: unreadFilterType,
|
|
startedFilterType: startedFilterType,
|
|
bookmarkedFilterType: bookmarkedFilterType,
|
|
sortType: sortType!);
|
|
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 localSource,
|
|
required bool language,
|
|
required WidgetRef ref,
|
|
required DisplayType displayType,
|
|
required Settings settings}) {
|
|
final l10n = l10nLocalizations(context)!;
|
|
final mangas = ref.watch(getAllMangaStreamProvider(
|
|
categoryId: categoryId, itemType: widget.itemType));
|
|
final sortType = ref
|
|
.watch(sortLibraryMangaStateProvider(
|
|
itemType: widget.itemType, settings: settings))
|
|
.index;
|
|
final mangaIdsList = ref.watch(mangasListStateProvider);
|
|
return Scaffold(
|
|
body: mangas.when(
|
|
data: (data) {
|
|
final entries = _filterAndSortManga(
|
|
data: data,
|
|
downloadFilterType: downloadFilterType,
|
|
unreadFilterType: unreadFilterType,
|
|
startedFilterType: startedFilterType,
|
|
bookmarkedFilterType: bookmarkedFilterType,
|
|
sortType: sortType!);
|
|
if (entries.isNotEmpty) {
|
|
final entriesManga = reverse ? entries.reversed.toList() : entries;
|
|
return RefreshIndicator(
|
|
onRefresh: () async {
|
|
await _updateLibrary(data);
|
|
},
|
|
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: widget.itemType,
|
|
),
|
|
);
|
|
}
|
|
return Center(child: Text(l10n.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 localSource,
|
|
required bool language,
|
|
required DisplayType displayType,
|
|
required WidgetRef ref,
|
|
bool withouCategories = false,
|
|
required Settings settings}) {
|
|
final sortType = ref
|
|
.watch(sortLibraryMangaStateProvider(
|
|
itemType: widget.itemType, settings: settings))
|
|
.index;
|
|
final manga = withouCategories
|
|
? ref.watch(getAllMangaWithoutCategoriesStreamProvider(
|
|
itemType: widget.itemType))
|
|
: ref.watch(getAllMangaStreamProvider(
|
|
categoryId: null, itemType: widget.itemType));
|
|
final mangaIdsList = ref.watch(mangasListStateProvider);
|
|
final l10n = l10nLocalizations(context)!;
|
|
return manga.when(
|
|
data: (data) {
|
|
final entries = _filterAndSortManga(
|
|
data: data,
|
|
downloadFilterType: downloadFilterType,
|
|
unreadFilterType: unreadFilterType,
|
|
startedFilterType: startedFilterType,
|
|
bookmarkedFilterType: bookmarkedFilterType,
|
|
sortType: sortType!);
|
|
if (entries.isNotEmpty) {
|
|
final entriesManga = reverse ? entries.reversed.toList() : entries;
|
|
return RefreshIndicator(
|
|
onRefresh: () async {
|
|
await _updateLibrary(data);
|
|
},
|
|
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: widget.itemType,
|
|
),
|
|
);
|
|
}
|
|
return Center(child: Text(l10n.empty_library));
|
|
},
|
|
error: (Object error, StackTrace stackTrace) {
|
|
return ErrorText(error);
|
|
},
|
|
loading: () {
|
|
return const ProgressCenter();
|
|
},
|
|
);
|
|
}
|
|
|
|
List<Manga> _filterAndSortManga(
|
|
{required List<Manga> data,
|
|
required int downloadFilterType,
|
|
required int unreadFilterType,
|
|
required int startedFilterType,
|
|
required int bookmarkedFilterType,
|
|
required int sortType}) {
|
|
List<Manga>? mangas;
|
|
mangas = data
|
|
.where((element) {
|
|
List list = [];
|
|
if (downloadFilterType == 1) {
|
|
for (var chap in element.chapters) {
|
|
final modelChapDownload = isar.downloads
|
|
.filter()
|
|
.idIsNotNull()
|
|
.chapterIdEqualTo(chap.id)
|
|
.findAllSync();
|
|
|
|
if (modelChapDownload.isNotEmpty &&
|
|
modelChapDownload.first.isDownload == true) {
|
|
list.add(true);
|
|
}
|
|
}
|
|
return list.isNotEmpty;
|
|
} else if (downloadFilterType == 2) {
|
|
for (var chap in element.chapters) {
|
|
final modelChapDownload = isar.downloads
|
|
.filter()
|
|
.idIsNotNull()
|
|
.chapterIdEqualTo(chap.id)
|
|
.findAllSync();
|
|
if (!(modelChapDownload.isNotEmpty &&
|
|
modelChapDownload.first.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();
|
|
|
|
if (sortType == 0) {
|
|
mangas.sort(
|
|
(a, b) {
|
|
return a.name!.compareTo(b.name!);
|
|
},
|
|
);
|
|
} else if (sortType == 1) {
|
|
mangas.sort(
|
|
(a, b) {
|
|
return a.lastRead!.compareTo(b.lastRead!);
|
|
},
|
|
);
|
|
} else if (sortType == 2) {
|
|
mangas.sort(
|
|
(a, b) {
|
|
return a.lastUpdate?.compareTo(b.lastUpdate ?? 0) ?? 0;
|
|
},
|
|
);
|
|
} else if (sortType == 3) {
|
|
mangas.sort(
|
|
(a, b) {
|
|
return a.chapters
|
|
.where((element) => !element.isRead!)
|
|
.toList()
|
|
.length
|
|
.compareTo(b.chapters
|
|
.where((element) => !element.isRead!)
|
|
.toList()
|
|
.length);
|
|
},
|
|
);
|
|
} else if (sortType == 4) {
|
|
mangas.sort(
|
|
(a, b) {
|
|
return a.chapters.length.compareTo(b.chapters.length);
|
|
},
|
|
);
|
|
} else if (sortType == 5) {
|
|
mangas.sort(
|
|
(a, b) {
|
|
final aChaps = a.chapters;
|
|
final bChaps = b.chapters;
|
|
return (aChaps.lastOrNull?.dateUpload ?? "")
|
|
.compareTo(bChaps.lastOrNull?.dateUpload ?? "");
|
|
},
|
|
);
|
|
} else if (sortType == 6) {
|
|
mangas.sort(
|
|
(a, b) {
|
|
return a.dateAdded?.compareTo(b.dateAdded ?? 0) ?? 0;
|
|
},
|
|
);
|
|
}
|
|
return mangas;
|
|
}
|
|
|
|
void _openCategory() {
|
|
List<int> categoryIds = [];
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
return Consumer(builder: (context, ref, child) {
|
|
final mangaIdsList = ref.watch(mangasListStateProvider);
|
|
final l10n = l10nLocalizations(context)!;
|
|
final List<Manga> mangasList = [];
|
|
for (var id in mangaIdsList) {
|
|
mangasList.add(isar.mangas.getSync(id)!);
|
|
}
|
|
return StatefulBuilder(
|
|
builder: (context, setState) {
|
|
return StreamBuilder(
|
|
stream: isar.categorys
|
|
.filter()
|
|
.idIsNotNull()
|
|
.and()
|
|
.forItemTypeEqualTo(widget.itemType)
|
|
.watch(fireImmediately: true),
|
|
builder: (context, snapshot) {
|
|
return AlertDialog(
|
|
title: Text(
|
|
l10n.set_categories,
|
|
),
|
|
content: SizedBox(
|
|
width: context.width(0.8),
|
|
child: Builder(builder: (context) {
|
|
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
|
|
final entries = snapshot.data!;
|
|
return ListView.builder(
|
|
shrinkWrap: true,
|
|
itemCount: entries.length,
|
|
itemBuilder: (context, index) {
|
|
return ListTileMangaCategory(
|
|
category: entries[index],
|
|
categoryIds: categoryIds,
|
|
mangasList: mangasList,
|
|
onTap: () {
|
|
setState(() {
|
|
if (categoryIds
|
|
.contains(entries[index].id)) {
|
|
categoryIds.remove(entries[index].id);
|
|
} else {
|
|
categoryIds.add(entries[index].id!);
|
|
}
|
|
});
|
|
},
|
|
res: (res) {
|
|
if (res.isNotEmpty) {
|
|
categoryIds.add(entries[index].id!);
|
|
}
|
|
},
|
|
);
|
|
},
|
|
);
|
|
}
|
|
return Text(l10n.library_no_category_exist);
|
|
}),
|
|
),
|
|
actions: [
|
|
snapshot.hasData && snapshot.data!.isNotEmpty
|
|
? Row(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
TextButton(
|
|
onPressed: () {
|
|
context.push("/categories",
|
|
extra: (true, widget.itemType));
|
|
Navigator.pop(context);
|
|
},
|
|
child: Text(l10n.edit)),
|
|
Row(
|
|
children: [
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
},
|
|
child: Text(l10n.cancel)),
|
|
const SizedBox(
|
|
width: 15,
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
isar.writeTxnSync(() {
|
|
for (var id in mangaIdsList) {
|
|
Manga? manga =
|
|
isar.mangas.getSync(id);
|
|
manga!.categories =
|
|
categoryIds;
|
|
isar.mangas.putSync(manga);
|
|
}
|
|
});
|
|
ref
|
|
.read(mangasListStateProvider
|
|
.notifier)
|
|
.clear();
|
|
ref
|
|
.read(
|
|
isLongPressedMangaStateProvider
|
|
.notifier)
|
|
.update(false);
|
|
if (mounted) {
|
|
Navigator.pop(context);
|
|
}
|
|
},
|
|
child: Text(
|
|
l10n.ok,
|
|
)),
|
|
],
|
|
),
|
|
],
|
|
)
|
|
: Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
TextButton(
|
|
onPressed: () {
|
|
context.push("/categories",
|
|
extra: (true, widget.itemType));
|
|
Navigator.pop(context);
|
|
},
|
|
child: Text(
|
|
l10n.edit_categories,
|
|
)),
|
|
],
|
|
)
|
|
],
|
|
);
|
|
});
|
|
},
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
void _deleteManga() {
|
|
List<int> fromLibList = [];
|
|
List<int> downloadedChapsList = [];
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
return Consumer(builder: (context, ref, child) {
|
|
final mangaIdsList = ref.watch(mangasListStateProvider);
|
|
final l10n = l10nLocalizations(context)!;
|
|
final List<Manga> mangasList = [];
|
|
for (var id in mangaIdsList) {
|
|
mangasList.add(isar.mangas.getSync(id)!);
|
|
}
|
|
return StatefulBuilder(
|
|
builder: (context, setState) {
|
|
return AlertDialog(
|
|
title: Text(
|
|
l10n.remove,
|
|
),
|
|
content: SizedBox(
|
|
height: 100,
|
|
width: context.width(0.8),
|
|
child: Column(
|
|
children: [
|
|
ListTileChapterFilter(
|
|
label: l10n.from_library,
|
|
onTap: () {
|
|
setState(() {
|
|
if (fromLibList == mangaIdsList) {
|
|
fromLibList = [];
|
|
} else {
|
|
fromLibList = mangaIdsList;
|
|
}
|
|
});
|
|
},
|
|
type: fromLibList.isNotEmpty ? 1 : 0,
|
|
),
|
|
ListTileChapterFilter(
|
|
label: widget.itemType != ItemType.anime
|
|
? l10n.downloaded_chapters
|
|
: l10n.downloaded_episodes,
|
|
onTap: () {
|
|
setState(() {
|
|
if (downloadedChapsList == mangaIdsList) {
|
|
downloadedChapsList = [];
|
|
} else {
|
|
downloadedChapsList = mangaIdsList;
|
|
}
|
|
});
|
|
},
|
|
type: downloadedChapsList.isNotEmpty ? 1 : 0,
|
|
),
|
|
],
|
|
)),
|
|
actions: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
},
|
|
child: Text(l10n.cancel)),
|
|
const SizedBox(
|
|
width: 15,
|
|
),
|
|
TextButton(
|
|
onPressed: () async {
|
|
if (fromLibList.isNotEmpty) {
|
|
isar.writeTxnSync(() {
|
|
for (var manga in mangasList) {
|
|
if (manga.isLocalArchive ?? false) {
|
|
final histories = isar.historys
|
|
.filter()
|
|
.mangaIdEqualTo(manga.id)
|
|
.findAllSync();
|
|
for (var history in histories) {
|
|
isar.historys.deleteSync(history.id!);
|
|
}
|
|
|
|
for (var chapter in manga.chapters) {
|
|
isar.updates
|
|
.filter()
|
|
.mangaIdEqualTo(chapter.mangaId)
|
|
.chapterNameEqualTo(chapter.name)
|
|
.deleteAllSync();
|
|
isar.chapters.deleteSync(chapter.id!);
|
|
}
|
|
isar.mangas.deleteSync(manga.id!);
|
|
} else {
|
|
manga.favorite = false;
|
|
isar.mangas.putSync(manga);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if (downloadedChapsList.isNotEmpty) {
|
|
isar.writeTxnSync(() async {
|
|
for (var manga in mangasList) {
|
|
if (manga.isLocalArchive ?? false) {
|
|
for (var chapter in manga.chapters) {
|
|
try {
|
|
final storageProvider =
|
|
StorageProvider();
|
|
final mangaDir = await storageProvider
|
|
.getMangaMainDirectory(chapter);
|
|
final path = await storageProvider
|
|
.getMangaChapterDirectory(
|
|
chapter,
|
|
);
|
|
|
|
try {
|
|
try {
|
|
if (File(
|
|
"${mangaDir!.path}${chapter.name}.cbz")
|
|
.existsSync()) {
|
|
File("${mangaDir.path}${chapter.name}.cbz")
|
|
.deleteSync();
|
|
}
|
|
} catch (_) {}
|
|
try {
|
|
if (File(
|
|
"${mangaDir!.path}${chapter.name}.mp4")
|
|
.existsSync()) {
|
|
File("${mangaDir.path}${chapter.name}.mp4")
|
|
.deleteSync();
|
|
}
|
|
} catch (_) {}
|
|
path!.deleteSync(recursive: true);
|
|
} catch (_) {}
|
|
isar.writeTxnSync(() {
|
|
final download = isar.downloads
|
|
.filter()
|
|
.chapterIdEqualTo(chapter.id!)
|
|
.findAllSync();
|
|
if (download.isNotEmpty) {
|
|
isar.downloads.deleteSync(
|
|
download.first.id!);
|
|
}
|
|
});
|
|
} catch (_) {}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
ref
|
|
.read(mangasListStateProvider.notifier)
|
|
.clear();
|
|
ref
|
|
.read(
|
|
isLongPressedMangaStateProvider.notifier)
|
|
.update(false);
|
|
if (mounted) {
|
|
Navigator.pop(context);
|
|
}
|
|
},
|
|
child: Text(
|
|
l10n.ok,
|
|
)),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
void _showDraggableMenu(Settings settings) {
|
|
final l10n = l10nLocalizations(context)!;
|
|
customDraggableTabBar(tabs: [
|
|
Tab(text: l10n.filter),
|
|
Tab(text: l10n.sort),
|
|
Tab(text: l10n.display),
|
|
], children: [
|
|
Consumer(builder: (context, ref, chil) {
|
|
return Column(
|
|
children: [
|
|
ListTileChapterFilter(
|
|
label: l10n.downloaded,
|
|
type: ref.watch(mangaFilterDownloadedStateProvider(
|
|
itemType: widget.itemType,
|
|
mangaList: _entries,
|
|
settings: settings)),
|
|
onTap: () {
|
|
ref
|
|
.read(mangaFilterDownloadedStateProvider(
|
|
itemType: widget.itemType,
|
|
mangaList: _entries,
|
|
settings: settings)
|
|
.notifier)
|
|
.update();
|
|
}),
|
|
ListTileChapterFilter(
|
|
label: widget.itemType != ItemType.anime
|
|
? l10n.unread
|
|
: l10n.unwatched,
|
|
type: ref.watch(mangaFilterUnreadStateProvider(
|
|
itemType: widget.itemType,
|
|
mangaList: _entries,
|
|
settings: settings)),
|
|
onTap: () {
|
|
ref
|
|
.read(mangaFilterUnreadStateProvider(
|
|
itemType: widget.itemType,
|
|
mangaList: _entries,
|
|
settings: settings)
|
|
.notifier)
|
|
.update();
|
|
}),
|
|
ListTileChapterFilter(
|
|
label: l10n.started,
|
|
type: ref.watch(mangaFilterStartedStateProvider(
|
|
itemType: widget.itemType,
|
|
mangaList: _entries,
|
|
settings: settings)),
|
|
onTap: () {
|
|
ref
|
|
.read(mangaFilterStartedStateProvider(
|
|
itemType: widget.itemType,
|
|
mangaList: _entries,
|
|
settings: settings)
|
|
.notifier)
|
|
.update();
|
|
}),
|
|
ListTileChapterFilter(
|
|
label: l10n.bookmarked,
|
|
type: ref.watch(mangaFilterBookmarkedStateProvider(
|
|
itemType: widget.itemType,
|
|
mangaList: _entries,
|
|
settings: settings)),
|
|
onTap: () {
|
|
setState(() {
|
|
ref
|
|
.read(mangaFilterBookmarkedStateProvider(
|
|
itemType: widget.itemType,
|
|
mangaList: _entries,
|
|
settings: settings)
|
|
.notifier)
|
|
.update();
|
|
});
|
|
}),
|
|
],
|
|
);
|
|
}),
|
|
Consumer(builder: (context, ref, chil) {
|
|
final reverse = ref
|
|
.read(sortLibraryMangaStateProvider(
|
|
itemType: widget.itemType, settings: settings)
|
|
.notifier)
|
|
.isReverse();
|
|
final reverseChapter = ref.watch(sortLibraryMangaStateProvider(
|
|
itemType: widget.itemType, settings: settings));
|
|
return Column(
|
|
children: [
|
|
for (var i = 0; i < 7; i++)
|
|
ListTileChapterSort(
|
|
label: _getSortNameByIndex(i, context),
|
|
reverse: reverse,
|
|
onTap: () {
|
|
ref
|
|
.read(sortLibraryMangaStateProvider(
|
|
itemType: widget.itemType, settings: settings)
|
|
.notifier)
|
|
.set(i);
|
|
},
|
|
showLeading: reverseChapter.index == i,
|
|
),
|
|
],
|
|
);
|
|
}),
|
|
Consumer(builder: (context, ref, chil) {
|
|
final display = ref.watch(libraryDisplayTypeStateProvider(
|
|
itemType: widget.itemType, settings: settings));
|
|
final displayV = ref.read(libraryDisplayTypeStateProvider(
|
|
itemType: widget.itemType, settings: settings)
|
|
.notifier);
|
|
final showCategoryTabs = ref.watch(libraryShowCategoryTabsStateProvider(
|
|
itemType: widget.itemType, settings: settings));
|
|
final continueReaderBtn = ref.watch(
|
|
libraryShowContinueReadingButtonStateProvider(
|
|
itemType: widget.itemType, settings: settings));
|
|
final showNumbersOfItems = ref.watch(
|
|
libraryShowNumbersOfItemsStateProvider(
|
|
itemType: widget.itemType, settings: settings));
|
|
final downloadedChapter = ref.watch(
|
|
libraryDownloadedChaptersStateProvider(
|
|
itemType: widget.itemType, settings: settings));
|
|
final language = ref.watch(libraryLanguageStateProvider(
|
|
itemType: widget.itemType, settings: settings));
|
|
final localSource = ref.watch(libraryLocalSourceStateProvider(
|
|
itemType: widget.itemType, settings: settings));
|
|
return SingleChildScrollView(
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 20, right: 20, top: 10),
|
|
child: Row(
|
|
children: [
|
|
Text(l10n.display_mode),
|
|
],
|
|
),
|
|
),
|
|
Padding(
|
|
padding:
|
|
const EdgeInsets.symmetric(vertical: 5, horizontal: 20),
|
|
child: Wrap(
|
|
children: DisplayType.values.map((e) {
|
|
final selected = e == display;
|
|
return Padding(
|
|
padding: const EdgeInsets.only(right: 5),
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
|
surfaceTintColor: Colors.transparent,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10)),
|
|
side: selected
|
|
? null
|
|
: BorderSide(
|
|
color: context.isLight
|
|
? Colors.black
|
|
: Colors.white,
|
|
width: 0.8),
|
|
shadowColor: Colors.transparent,
|
|
elevation: 0,
|
|
backgroundColor: selected
|
|
? context.primaryColor.withValues(alpha: 0.2)
|
|
: Colors.transparent),
|
|
onPressed: () {
|
|
displayV.setLibraryDisplayType(e);
|
|
},
|
|
child: Text(
|
|
displayV.getLibraryDisplayTypeName(e, context),
|
|
style: TextStyle(
|
|
color:
|
|
Theme.of(context).textTheme.bodyLarge!.color,
|
|
fontSize: 14),
|
|
)),
|
|
);
|
|
}
|
|
|
|
// RadioListTile<
|
|
// DisplayType>(
|
|
// dense: true,
|
|
// title: ,
|
|
// value: e,
|
|
// groupValue: displayV
|
|
// .getLibraryDisplayTypeValue(
|
|
// display),
|
|
// selected: true,
|
|
// onChanged: (value) {
|
|
// displayV
|
|
// .setLibraryDisplayType(
|
|
// value!);
|
|
// },
|
|
// ),
|
|
).toList()),
|
|
),
|
|
Consumer(
|
|
builder: (context, ref, child) {
|
|
final gridSize = ref.watch(libraryGridSizeStateProvider(
|
|
itemType: widget.itemType)) ??
|
|
0;
|
|
return Padding(
|
|
padding: const EdgeInsets.only(left: 8, right: 8, top: 10),
|
|
child: Row(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
child: Column(
|
|
children: [
|
|
Text(context.l10n.grid_size),
|
|
Text(gridSize == 0
|
|
? context.l10n.default0
|
|
: context.l10n.n_per_row(gridSize.toString()))
|
|
],
|
|
),
|
|
),
|
|
Flexible(
|
|
flex: 7,
|
|
child: SliderTheme(
|
|
data: SliderTheme.of(context).copyWith(
|
|
overlayShape: const RoundSliderOverlayShape(
|
|
overlayRadius: 5.0),
|
|
),
|
|
child: Slider(
|
|
min: 0.0,
|
|
max: 7,
|
|
divisions: max(7, 0),
|
|
value: gridSize.toDouble(),
|
|
onChanged: (value) {
|
|
HapticFeedback.vibrate();
|
|
ref
|
|
.read(libraryGridSizeStateProvider(
|
|
itemType: widget.itemType)
|
|
.notifier)
|
|
.set(value.toInt());
|
|
},
|
|
onChangeEnd: (value) {
|
|
ref
|
|
.read(libraryGridSizeStateProvider(
|
|
itemType: widget.itemType)
|
|
.notifier)
|
|
.set(value.toInt(), end: true);
|
|
},
|
|
),
|
|
),
|
|
)
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 20, right: 20, top: 10),
|
|
child: Row(
|
|
children: [
|
|
Text(l10n.badges),
|
|
],
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 5),
|
|
child: Column(
|
|
children: [
|
|
ListTileChapterFilter(
|
|
label: widget.itemType != ItemType.anime
|
|
? l10n.downloaded_chapters
|
|
: l10n.downloaded_episodes,
|
|
type: downloadedChapter ? 1 : 0,
|
|
onTap: () {
|
|
ref
|
|
.read(libraryDownloadedChaptersStateProvider(
|
|
itemType: widget.itemType,
|
|
settings: settings)
|
|
.notifier)
|
|
.set(!downloadedChapter);
|
|
}),
|
|
ListTileChapterFilter(
|
|
label: l10n.language,
|
|
type: language ? 1 : 0,
|
|
onTap: () {
|
|
ref
|
|
.read(libraryLanguageStateProvider(
|
|
itemType: widget.itemType,
|
|
settings: settings)
|
|
.notifier)
|
|
.set(!language);
|
|
}),
|
|
ListTileChapterFilter(
|
|
label: l10n.local_source,
|
|
type: localSource ? 1 : 0,
|
|
onTap: () {
|
|
ref
|
|
.read(libraryLocalSourceStateProvider(
|
|
itemType: widget.itemType,
|
|
settings: settings)
|
|
.notifier)
|
|
.set(!localSource);
|
|
}),
|
|
ListTileChapterFilter(
|
|
label: widget.itemType != ItemType.anime
|
|
? l10n.show_continue_reading_buttons
|
|
: l10n.show_continue_watching_buttons,
|
|
type: continueReaderBtn ? 1 : 0,
|
|
onTap: () {
|
|
ref
|
|
.read(
|
|
libraryShowContinueReadingButtonStateProvider(
|
|
itemType: widget.itemType,
|
|
settings: settings)
|
|
.notifier)
|
|
.set(!continueReaderBtn);
|
|
}),
|
|
],
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 20, right: 20, top: 10),
|
|
child: Row(
|
|
children: [Text(l10n.tabs)],
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 5),
|
|
child: Column(
|
|
children: [
|
|
ListTileChapterFilter(
|
|
label: l10n.show_category_tabs,
|
|
type: showCategoryTabs ? 1 : 0,
|
|
onTap: () {
|
|
ref
|
|
.read(libraryShowCategoryTabsStateProvider(
|
|
itemType: widget.itemType,
|
|
settings: settings)
|
|
.notifier)
|
|
.set(!showCategoryTabs);
|
|
}),
|
|
ListTileChapterFilter(
|
|
label: l10n.show_numbers_of_items,
|
|
type: showNumbersOfItems ? 1 : 0,
|
|
onTap: () {
|
|
ref
|
|
.read(libraryShowNumbersOfItemsStateProvider(
|
|
itemType: widget.itemType,
|
|
settings: settings)
|
|
.notifier)
|
|
.set(!showNumbersOfItems);
|
|
}),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}),
|
|
], context: context, vsync: this);
|
|
}
|
|
|
|
String _getSortNameByIndex(int index, BuildContext context) {
|
|
final l10n = l10nLocalizations(context)!;
|
|
if (index == 0) {
|
|
return l10n.alphabetically;
|
|
} else if (index == 1) {
|
|
return widget.itemType != ItemType.anime
|
|
? l10n.last_read
|
|
: l10n.last_watched;
|
|
} else if (index == 2) {
|
|
return l10n.last_update_check;
|
|
} else if (index == 3) {
|
|
return widget.itemType != ItemType.anime
|
|
? l10n.unread_count
|
|
: l10n.unwatched_count;
|
|
} else if (index == 4) {
|
|
return widget.itemType != ItemType.anime
|
|
? l10n.total_chapters
|
|
: l10n.total_episodes;
|
|
} else if (index == 5) {
|
|
return widget.itemType != ItemType.anime
|
|
? l10n.latest_chapter
|
|
: l10n.latest_episode;
|
|
}
|
|
return l10n.date_added;
|
|
}
|
|
|
|
PreferredSize _appBar(
|
|
bool isNotFiltering,
|
|
bool showNumbersOfItems,
|
|
int numberOfItems,
|
|
WidgetRef ref,
|
|
List<Manga> mangas,
|
|
bool isCategory,
|
|
int? categoryId,
|
|
Settings settings) {
|
|
final isLongPressed = ref.watch(isLongPressedMangaStateProvider);
|
|
final mangaIdsList = ref.watch(mangasListStateProvider);
|
|
final manga = categoryId == null
|
|
? ref.watch(getAllMangaWithoutCategoriesStreamProvider(
|
|
itemType: widget.itemType))
|
|
: ref.watch(getAllMangaStreamProvider(
|
|
categoryId: categoryId, itemType: widget.itemType));
|
|
final l10n = l10nLocalizations(context)!;
|
|
return PreferredSize(
|
|
preferredSize: Size.fromHeight(AppBar().preferredSize.height),
|
|
child: isLongPressed
|
|
? manga.when(
|
|
data: (data) => Container(
|
|
color: Theme.of(context).scaffoldBackgroundColor,
|
|
child: AppBar(
|
|
title: Text(mangaIdsList.length.toString()),
|
|
backgroundColor:
|
|
context.primaryColor.withValues(alpha: 0.2),
|
|
leading: IconButton(
|
|
onPressed: () {
|
|
ref.read(mangasListStateProvider.notifier).clear();
|
|
|
|
ref
|
|
.read(isLongPressedMangaStateProvider.notifier)
|
|
.update(!isLongPressed);
|
|
},
|
|
icon: const Icon(Icons.clear)),
|
|
actions: [
|
|
IconButton(
|
|
onPressed: () {
|
|
for (var manga in data) {
|
|
ref
|
|
.read(mangasListStateProvider.notifier)
|
|
.selectAll(manga);
|
|
}
|
|
},
|
|
icon: const Icon(Icons.select_all)),
|
|
IconButton(
|
|
onPressed: () {
|
|
if (data.length == mangaIdsList.length) {
|
|
for (var manga in data) {
|
|
ref
|
|
.read(mangasListStateProvider.notifier)
|
|
.selectSome(manga);
|
|
}
|
|
ref
|
|
.read(
|
|
isLongPressedMangaStateProvider.notifier)
|
|
.update(false);
|
|
} else {
|
|
for (var manga in data) {
|
|
ref
|
|
.read(mangasListStateProvider.notifier)
|
|
.selectSome(manga);
|
|
}
|
|
}
|
|
},
|
|
icon: const Icon(Icons.flip_to_back_rounded)),
|
|
],
|
|
),
|
|
),
|
|
error: (Object error, StackTrace stackTrace) {
|
|
return ErrorText(error);
|
|
},
|
|
loading: () {
|
|
return const ProgressCenter();
|
|
},
|
|
)
|
|
: AppBar(
|
|
elevation: 0,
|
|
backgroundColor: Colors.transparent,
|
|
title: _isSearch
|
|
? null
|
|
: Row(
|
|
children: [
|
|
Text(
|
|
widget.itemType == ItemType.manga
|
|
? l10n.manga
|
|
: widget.itemType == ItemType.anime
|
|
? l10n.anime
|
|
: l10n.novel,
|
|
style:
|
|
TextStyle(color: Theme.of(context).hintColor),
|
|
),
|
|
const SizedBox(
|
|
width: 10,
|
|
),
|
|
if (showNumbersOfItems)
|
|
Padding(
|
|
padding: const EdgeInsets.only(bottom: 3),
|
|
child: Badge(
|
|
backgroundColor: Theme.of(context).focusColor,
|
|
label: 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(settings);
|
|
},
|
|
icon: Icon(
|
|
Icons.filter_list_sharp,
|
|
color: isNotFiltering ? null : Colors.yellow,
|
|
)),
|
|
PopupMenuButton(
|
|
popUpAnimationStyle: popupAnimationStyle,
|
|
itemBuilder: (context) {
|
|
return [
|
|
PopupMenuItem<int>(
|
|
value: 0,
|
|
child: Text(context.l10n.update_library),
|
|
),
|
|
PopupMenuItem<int>(
|
|
value: 1, child: Text(l10n.open_random_entry)),
|
|
PopupMenuItem<int>(
|
|
value: 2, child: Text(l10n.import)),
|
|
if (widget.itemType == ItemType.anime)
|
|
PopupMenuItem<int>(
|
|
value: 3, child: Text(l10n.torrent_stream)),
|
|
];
|
|
},
|
|
onSelected: (value) {
|
|
if (value == 0) {
|
|
manga.whenData((value) {
|
|
_updateLibrary(value);
|
|
});
|
|
} else if (value == 1) {
|
|
manga.whenData((value) {
|
|
var randomManga = (value..shuffle()).first;
|
|
pushToMangaReaderDetail(
|
|
archiveId: randomManga.isLocalArchive ?? false
|
|
? randomManga.id
|
|
: null,
|
|
context: context,
|
|
lang: randomManga.lang!,
|
|
mangaM: randomManga,
|
|
source: randomManga.source!);
|
|
});
|
|
} else if (value == 2) {
|
|
_importLocal(context, widget.itemType);
|
|
} else if (value == 3 &&
|
|
widget.itemType == ItemType.anime) {
|
|
addTorrent(context);
|
|
}
|
|
}),
|
|
],
|
|
));
|
|
}
|
|
}
|
|
|
|
void _importLocal(BuildContext context, ItemType itemType) {
|
|
final l10n = l10nLocalizations(context)!;
|
|
bool isLoading = false;
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: !isLoading,
|
|
builder: (context) {
|
|
return AlertDialog(
|
|
title: Text(
|
|
l10n.import_local_file,
|
|
),
|
|
content: StatefulBuilder(
|
|
builder: (context, setState) {
|
|
return Consumer(builder: (context, ref, child) {
|
|
return SizedBox(
|
|
height: 100,
|
|
child: Stack(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(3),
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius:
|
|
BorderRadius.circular(10))),
|
|
onPressed: () async {
|
|
setState(() {
|
|
isLoading = true;
|
|
});
|
|
await ref.watch(
|
|
importArchivesFromFileProvider(
|
|
itemType: itemType,
|
|
null,
|
|
init: true)
|
|
.future);
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
Navigator.pop(context);
|
|
},
|
|
child: Column(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.spaceEvenly,
|
|
children: [
|
|
const Icon(Icons.archive_outlined),
|
|
Text(
|
|
"${l10n.import_files} ( ${itemType == ItemType.manga ? ".zip, .cbz" : ".mp4, .mkv, .avi, and more"} )",
|
|
style: TextStyle(
|
|
color: Theme.of(context)
|
|
.textTheme
|
|
.bodySmall!
|
|
.color,
|
|
fontSize: 10))
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
if (isLoading)
|
|
Container(
|
|
width: context.width(1),
|
|
height: context.height(1),
|
|
color: Colors.transparent,
|
|
child: UnconstrainedBox(
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(20),
|
|
color:
|
|
Theme.of(context).scaffoldBackgroundColor,
|
|
),
|
|
height: 50,
|
|
width: 50,
|
|
child: const Center(child: ProgressCenter())),
|
|
),
|
|
)
|
|
],
|
|
),
|
|
);
|
|
});
|
|
},
|
|
),
|
|
actions: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
},
|
|
child: Text(l10n.cancel)),
|
|
const SizedBox(
|
|
width: 15,
|
|
),
|
|
],
|
|
)
|
|
],
|
|
);
|
|
});
|
|
}
|
|
|
|
void addTorrent(BuildContext context, {Manga? manga}) {
|
|
final l10n = l10nLocalizations(context)!;
|
|
String torrentUrl = "";
|
|
bool isLoading = false;
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: !isLoading,
|
|
builder: (context) {
|
|
return AlertDialog(
|
|
title: Text(
|
|
l10n.add_torrent,
|
|
),
|
|
content: StatefulBuilder(
|
|
builder: (context, setState) {
|
|
return Consumer(builder: (context, ref, _) {
|
|
return SizedBox(
|
|
height: 150,
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: TextFormField(
|
|
onChanged: (value) {
|
|
setState(() {
|
|
torrentUrl = value;
|
|
});
|
|
},
|
|
decoration: InputDecoration(
|
|
hintText: l10n.enter_torrent_hint_text,
|
|
labelText: l10n.torrent_url,
|
|
isDense: true,
|
|
filled: true,
|
|
fillColor: Colors.transparent,
|
|
enabledBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: context.secondaryColor)),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: context.secondaryColor)),
|
|
border: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: context.secondaryColor))),
|
|
),
|
|
),
|
|
TextButton(
|
|
onPressed: isLoading
|
|
? null
|
|
: () async {
|
|
setState(() {
|
|
isLoading = true;
|
|
});
|
|
try {
|
|
await ref.watch(
|
|
addTorrentFromUrlOrFromFileProvider(
|
|
manga,
|
|
init: true,
|
|
url: torrentUrl)
|
|
.future);
|
|
} catch (_) {}
|
|
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
Navigator.pop(context);
|
|
},
|
|
child: Text(l10n.add))
|
|
],
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Text(l10n.or),
|
|
),
|
|
Stack(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(3),
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius:
|
|
BorderRadius.circular(10))),
|
|
onPressed: isLoading
|
|
? null
|
|
: () async {
|
|
setState(() {
|
|
isLoading = true;
|
|
});
|
|
try {
|
|
await ref.watch(
|
|
addTorrentFromUrlOrFromFileProvider(
|
|
manga,
|
|
init: true)
|
|
.future);
|
|
} catch (_) {}
|
|
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
Navigator.pop(context);
|
|
},
|
|
child: Column(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.spaceEvenly,
|
|
children: [
|
|
const Icon(Icons.archive_outlined),
|
|
Text("import .torrent file",
|
|
style: TextStyle(
|
|
color: Theme.of(context)
|
|
.textTheme
|
|
.bodySmall!
|
|
.color,
|
|
fontSize: 10))
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
if (isLoading)
|
|
Positioned.fill(
|
|
child: Container(
|
|
width: 300,
|
|
height: 150,
|
|
color: Colors.transparent,
|
|
child: UnconstrainedBox(
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(20),
|
|
color: Theme.of(context)
|
|
.scaffoldBackgroundColor,
|
|
),
|
|
height: 50,
|
|
width: 50,
|
|
child: const Center(
|
|
child: ProgressCenter())),
|
|
),
|
|
),
|
|
)
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
});
|
|
},
|
|
),
|
|
actions: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
},
|
|
child: Text(l10n.cancel)),
|
|
const SizedBox(
|
|
width: 15,
|
|
),
|
|
],
|
|
)
|
|
],
|
|
);
|
|
});
|
|
}
|