mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-03-11 21:35:32 +00:00
Better text rendering of "ignore filters" checkbox on mobile. Adds a line break to mobile devices, where horizontal space is limited.
2603 lines
110 KiB
Dart
2603 lines
110 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/changed.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/more/settings/sync/providers/sync_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';
|
||
import 'package:super_sliver_list/super_sliver_list.dart';
|
||
|
||
class LibraryScreen extends ConsumerStatefulWidget {
|
||
final ItemType itemType;
|
||
final String? presetInput;
|
||
const LibraryScreen({
|
||
required this.itemType,
|
||
required this.presetInput,
|
||
super.key,
|
||
});
|
||
|
||
@override
|
||
ConsumerState<LibraryScreen> createState() => _LibraryScreenState();
|
||
}
|
||
|
||
class _LibraryScreenState extends ConsumerState<LibraryScreen>
|
||
with TickerProviderStateMixin {
|
||
bool _isSearch = false;
|
||
final List<Manga> _entries = [];
|
||
final _textEditingController = TextEditingController();
|
||
TabController? tabBarController;
|
||
int _tabIndex = 0;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
if (widget.presetInput != null) {
|
||
_isSearch = true;
|
||
_textEditingController.text = widget.presetInput!;
|
||
}
|
||
}
|
||
|
||
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) {
|
||
data.sort((a, b) => (a.pos ?? 0).compareTo(b.pos ?? 0));
|
||
|
||
final entr =
|
||
data.where((e) => !(e.hide ?? false)).toList();
|
||
int tabCount =
|
||
withoutCategory.isNotEmpty
|
||
? entr.length + 1
|
||
: entr.length;
|
||
if (tabBarController == null ||
|
||
tabBarController!.length != tabCount) {
|
||
int newTabIndex = _tabIndex;
|
||
if (newTabIndex >= tabCount) {
|
||
newTabIndex = tabCount - 1;
|
||
}
|
||
tabBarController?.dispose();
|
||
tabBarController = TabController(
|
||
length: tabCount,
|
||
vsync: this,
|
||
initialIndex: newTabIndex,
|
||
);
|
||
_tabIndex = newTabIndex;
|
||
tabBarController!.addListener(() {
|
||
setState(() {
|
||
_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();
|
||
},
|
||
);
|
||
}
|
||
|
||
bool matchesSearchQuery(Manga manga, String query) {
|
||
final keywords = query
|
||
.toLowerCase()
|
||
.split(',')
|
||
.map((k) => k.trim())
|
||
.where((k) => k.isNotEmpty);
|
||
|
||
return keywords.any(
|
||
(keyword) =>
|
||
(manga.name?.toLowerCase().contains(keyword) ?? false) ||
|
||
(manga.source?.toLowerCase().contains(keyword) ?? false) ||
|
||
(manga.genre?.any((g) => g.toLowerCase().contains(keyword)) ?? false),
|
||
);
|
||
}
|
||
|
||
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;
|
||
final searchQuery = _textEditingController.text;
|
||
// Skip all filters, just do search
|
||
if (searchQuery.isNotEmpty && _ignoreFiltersOnSearch) {
|
||
mangas =
|
||
data
|
||
.where((element) => matchesSearchQuery(element, searchQuery))
|
||
.toList();
|
||
} else {
|
||
// Apply filters + search
|
||
mangas =
|
||
data
|
||
.where((element) {
|
||
// Filter by download
|
||
List list = [];
|
||
if (downloadFilterType == 1) {
|
||
for (var chap in element.chapters) {
|
||
final modelChapDownload =
|
||
isar.downloads
|
||
.filter()
|
||
.idEqualTo(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()
|
||
.idEqualTo(chap.id)
|
||
.findAllSync();
|
||
if (!(modelChapDownload.isNotEmpty &&
|
||
modelChapDownload.first.isDownload == true)) {
|
||
list.add(true);
|
||
}
|
||
}
|
||
return list.length == element.chapters.length;
|
||
}
|
||
return true;
|
||
})
|
||
.where((element) {
|
||
// Filter by unread or started
|
||
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) {
|
||
// Filter by bookmarked
|
||
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) =>
|
||
searchQuery.isNotEmpty
|
||
? matchesSearchQuery(element, searchQuery)
|
||
: true,
|
||
)
|
||
.toList();
|
||
}
|
||
// Sorting the data based on selected sort type
|
||
mangas.sort((a, b) {
|
||
switch (sortType) {
|
||
case 0:
|
||
return a.name!.compareTo(b.name!);
|
||
case 1:
|
||
return a.lastRead!.compareTo(b.lastRead!);
|
||
case 2:
|
||
return a.lastUpdate?.compareTo(b.lastUpdate ?? 0) ?? 0;
|
||
case 3:
|
||
return a.chapters
|
||
.where((e) => !e.isRead!)
|
||
.length
|
||
.compareTo(b.chapters.where((e) => !e.isRead!).length);
|
||
case 4:
|
||
return a.chapters.length.compareTo(b.chapters.length);
|
||
case 5:
|
||
return (a.chapters.lastOrNull?.dateUpload ?? "").compareTo(
|
||
b.chapters.lastOrNull?.dateUpload ?? "",
|
||
);
|
||
case 6:
|
||
return a.dateAdded?.compareTo(b.dateAdded ?? 0) ?? 0;
|
||
default:
|
||
return 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 data = snapshot.data!;
|
||
data.sort(
|
||
(a, b) => (a.pos ?? 0).compareTo(b.pos ?? 0),
|
||
);
|
||
|
||
final entries =
|
||
data
|
||
.where((e) => !(e.hide ?? false))
|
||
.toList();
|
||
if (entries.isEmpty) {
|
||
return Text(l10n.library_no_category_exist);
|
||
}
|
||
return SuperListView.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 == ItemType.manga
|
||
? 0
|
||
: widget.itemType == ItemType.anime
|
||
? 1
|
||
: 2,
|
||
),
|
||
);
|
||
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(
|
||
synchingProvider(
|
||
syncId: 1,
|
||
).notifier,
|
||
)
|
||
.addChangedPart(
|
||
ActionType.updateItem,
|
||
manga.id,
|
||
manga.toJson(),
|
||
false,
|
||
);
|
||
}
|
||
});
|
||
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 == ItemType.manga
|
||
? 0
|
||
: widget.itemType == ItemType.anime
|
||
? 1
|
||
: 2,
|
||
),
|
||
);
|
||
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!);
|
||
ref
|
||
.read(
|
||
synchingProvider(syncId: 1).notifier,
|
||
)
|
||
.addChangedPart(
|
||
ActionType.removeItem,
|
||
manga.id,
|
||
"{}",
|
||
false,
|
||
);
|
||
} else {
|
||
manga.favorite = false;
|
||
isar.mangas.putSync(manga);
|
||
ref
|
||
.read(
|
||
synchingProvider(syncId: 1).notifier,
|
||
)
|
||
.addChangedPart(
|
||
ActionType.updateItem,
|
||
manga.id,
|
||
manga.toJson(),
|
||
false,
|
||
);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
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()
|
||
.idEqualTo(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;
|
||
}
|
||
|
||
bool _ignoreFiltersOnSearch = false;
|
||
final bool _isMobile = Platform.isIOS || Platform.isAndroid;
|
||
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),
|
||
),
|
||
if (_isSearch)
|
||
Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Text(
|
||
_isMobile
|
||
// Adds a line break where spaces exist for better mobile layout.
|
||
// Works for languages that use spaces between words.
|
||
? l10n.ignore_filters.replaceAll(' ', '\n')
|
||
// Removes manually added line breaks for Thai and Chinese,
|
||
// where spaces aren’t used, to ensure proper desktop rendering.
|
||
: l10n.ignore_filters.replaceAll('\n', ''),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
Checkbox(
|
||
value: _ignoreFiltersOnSearch,
|
||
onChanged: (val) {
|
||
setState(() {
|
||
_ignoreFiltersOnSearch = val ?? false;
|
||
});
|
||
},
|
||
),
|
||
],
|
||
),
|
||
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(
|
||
ref: ref,
|
||
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),
|
||
],
|
||
),
|
||
],
|
||
);
|
||
},
|
||
);
|
||
}
|