Merge pull request #491 from NBA2K1/main

Refactor: Consolidate _openCategory() Methods into Unified Dialog
This commit is contained in:
Moustapha Kodjo Amadou 2025-06-19 18:05:30 +01:00 committed by GitHub
commit e635e495b8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 222 additions and 428 deletions

View file

@ -6,11 +6,9 @@ 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';
@ -24,6 +22,7 @@ import 'package:mangayomi/modules/manga/detail/providers/update_manga_detail_pro
import 'package:mangayomi/modules/more/categories/providers/isar_providers.dart';
import 'package:mangayomi/modules/more/settings/appearance/providers/theme_mode_state_provider.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/modules/widgets/category_selection_dialog.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';
@ -34,13 +33,11 @@ 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;
@ -578,7 +575,18 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
backgroundColor: Colors.transparent,
),
onPressed: () {
_openCategory();
final mangaIdsList = ref.watch(
mangasListStateProvider,
);
final List<Manga> bulkMangas = mangaIdsList
.map((id) => isar.mangas.getSync(id)!)
.toList();
showCategorySelectionDialog(
context: context,
ref: ref,
itemType: widget.itemType,
bulkMangas: bulkMangas,
);
},
child: Icon(
Icons.label_outline_rounded,
@ -1073,192 +1081,6 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
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 = [];

View file

@ -26,6 +26,7 @@ import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provi
import 'package:mangayomi/modules/more/settings/appearance/providers/pure_black_dark_mode_state_provider.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/modules/more/settings/track/widgets/track_listile.dart';
import 'package:mangayomi/modules/widgets/category_selection_dialog.dart';
import 'package:mangayomi/modules/widgets/custom_draggable_tabbar.dart';
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
@ -637,7 +638,12 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
widget.checkForUpdate(true);
break;
case 1:
_openCategory(widget.manga!);
showCategorySelectionDialog(
context: context,
ref: ref,
itemType: widget.manga!.itemType,
singleManga: widget.manga!,
);
break;
case 2:
final source = getSource(
@ -1133,110 +1139,6 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
);
}
void _openCategory(Manga manga) {
final l10n = l10nLocalizations(context)!;
List<int> categoryIds = List<int>.from(manga.categories ?? []);
showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setState) => AlertDialog(
title: Text(l10n.set_categories),
content: SizedBox(
width: context.width(0.8),
child: StreamBuilder(
stream: isar.categorys
.filter()
.idIsNotNull()
.and()
.forItemTypeEqualTo(manga.itemType)
.watch(fireImmediately: true),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Container();
}
final entries = snapshot.data!;
return SuperListView.builder(
shrinkWrap: true,
itemCount: entries.length,
itemBuilder: (context, index) {
final category = entries[index];
final selected = categoryIds.contains(category.id);
return ListTileChapterFilter(
label: category.name!,
onTap: () {
setState(() {
selected
? categoryIds.remove(category.id)
: categoryIds.add(category.id!);
});
},
type: selected ? 1 : 0,
);
},
);
},
),
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
onPressed: () {
context.push(
"/categories",
extra: (
true,
manga.itemType == ItemType.manga
? 0
: manga.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(() {
manga.favorite = true;
manga.categories = categoryIds;
manga.dateAdded =
DateTime.now().millisecondsSinceEpoch;
isar.mangas.putSync(manga);
final sync = ref.read(
synchingProvider(syncId: 1).notifier,
);
sync.addChangedPart(
ActionType.updateItem,
manga.id,
manga.toJson(),
false,
);
});
if (mounted) Navigator.pop(context);
},
child: Text(l10n.ok),
),
],
),
],
),
],
),
),
);
}
void _showDraggableMenu() {
final scanlators = ref.watch(scanlatorsFilterStateProvider(widget.manga!));
final l10n = l10nLocalizations(context)!;

View file

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/category.dart';
@ -10,15 +9,14 @@ import 'package:mangayomi/models/history.dart';
import 'package:mangayomi/modules/manga/detail/widgets/custom_floating_action_btn.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/modules/widgets/category_selection_dialog.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/utils/constant.dart';
import 'package:mangayomi/modules/manga/detail/manga_detail_view.dart';
import 'package:mangayomi/modules/manga/detail/providers/state_providers.dart';
import 'package:mangayomi/modules/manga/detail/widgets/chapter_filter_list_tile_widget.dart';
import 'package:mangayomi/modules/more/providers/incognito_mode_state_provider.dart';
import 'package:mangayomi/utils/extensions/chapter.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
class MangaDetailsView extends ConsumerStatefulWidget {
final Manga manga;
@ -237,36 +235,40 @@ class _MangaDetailsViewState extends ConsumerState<MangaDetailsView> {
elevation: 0,
),
onPressed: () {
final model = widget.manga;
final checkCategoryList = isar.categorys
.filter()
.idIsNotNull()
.and()
.forItemTypeEqualTo(widget.manga.itemType)
.forItemTypeEqualTo(model.itemType)
.isNotEmptySync();
if (checkCategoryList) {
_openCategory(widget.manga);
showCategorySelectionDialog(
context: context,
ref: ref,
itemType: model.itemType,
singleManga: model,
);
} else {
final model = widget.manga;
isar.writeTxnSync(() {
model.favorite = true;
model.dateAdded = DateTime.now().millisecondsSinceEpoch;
isar.mangas.putSync(model);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(
ActionType.addItem,
null,
model.toJson(),
false,
);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(
ActionType.updateItem,
model.id,
model.toJson(),
false,
);
final sync = ref.read(
synchingProvider(syncId: 1).notifier,
);
sync.addChangedPart(
ActionType.addItem,
null,
model.toJson(),
false,
);
sync.addChangedPart(
ActionType.updateItem,
model.id,
model.toJson(),
false,
);
});
}
},
@ -299,114 +301,4 @@ class _MangaDetailsViewState extends ConsumerState<MangaDetailsView> {
),
);
}
_openCategory(Manga manga) {
final l10n = l10nLocalizations(context)!;
List<int> categoryIds = [];
showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setState) => AlertDialog(
title: Text(l10n.set_categories),
content: SizedBox(
width: context.width(0.8),
child: StreamBuilder(
stream: isar.categorys
.filter()
.idIsNotNull()
.and()
.forItemTypeEqualTo(manga.itemType)
.watch(fireImmediately: true),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Container();
}
final entries = snapshot.data!;
return SuperListView.builder(
shrinkWrap: true,
itemCount: entries.length,
itemBuilder: (context, index) {
final category = entries[index];
final selected = categoryIds.contains(category.id);
return ListTileChapterFilter(
label: category.name!,
onTap: () {
setState(() {
selected
? categoryIds.remove(category.id)
: categoryIds.add(category.id!);
});
},
type: selected ? 1 : 0,
);
},
);
},
),
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
onPressed: () {
context.push(
"/categories",
extra: (
true,
manga.itemType == ItemType.manga
? 0
: manga.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(() {
manga.favorite = true;
manga.categories = categoryIds;
manga.dateAdded =
DateTime.now().millisecondsSinceEpoch;
isar.mangas.putSync(manga);
final sync = ref.read(
synchingProvider(syncId: 1).notifier,
);
sync.addChangedPart(
ActionType.addItem,
manga.id,
manga.toJson(),
false,
);
sync.addChangedPart(
ActionType.updateItem,
manga.id,
manga.toJson(),
false,
);
});
if (mounted) Navigator.pop(context);
},
child: Text(l10n.ok),
),
],
),
],
),
],
),
),
);
}
}

View file

@ -0,0 +1,178 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/category.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/modules/library/providers/library_state_provider.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/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
void showCategorySelectionDialog({
required BuildContext context,
required WidgetRef ref,
required ItemType itemType,
Manga? singleManga,
List<Manga>? bulkMangas,
}) {
assert(
(singleManga != null) ^ (bulkMangas != null),
"Provide either singleManga or bulkMangas, not both.",
);
final l10n = l10nLocalizations(context)!;
final bool isBulk = bulkMangas != null;
final bool isFavorite = !isBulk && (singleManga!.favorite ?? false);
List<int> categoryIds = [];
if (!isBulk && isFavorite) {
categoryIds = List<int>.from(singleManga.categories ?? []);
}
showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setState) => AlertDialog(
title: Text(l10n.set_categories),
content: SizedBox(
width: context.width(0.8),
child: StreamBuilder(
stream: isar.categorys
.filter()
.idIsNotNull()
.and()
.forItemTypeEqualTo(itemType)
.watch(fireImmediately: true),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Text(l10n.library_no_category_exist);
}
var entries = (snapshot.data!
..sort((a, b) => (a.pos ?? 0).compareTo(b.pos ?? 0)));
if (isFavorite || isBulk) {
// When item is in library, hide hidden categories in list
entries = entries.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) {
final category = entries[index];
final isSelected = categoryIds.contains(category.id);
if (!isBulk) {
return ListTileChapterFilter(
label: category.name!,
onTap: () {
setState(() {
isSelected
? categoryIds.remove(category.id)
: categoryIds.add(category.id!);
});
},
type: isSelected ? 1 : 0,
);
}
return ListTileMangaCategory(
category: category,
categoryIds: categoryIds,
mangasList: bulkMangas,
onTap: () {
setState(() {
if (isSelected) {
categoryIds.remove(category.id);
} else {
categoryIds.add(category.id!);
}
});
},
res: (res) {
if (res.isNotEmpty && !isSelected) {
categoryIds.add(category.id!);
}
},
);
},
);
},
),
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
child: Text(l10n.edit),
onPressed: () {
context.push(
"/categories",
extra: (
true,
itemType == ItemType.manga
? 0
: itemType == ItemType.anime
? 1
: 2,
),
);
Navigator.pop(context);
},
),
Row(
children: [
TextButton(
child: Text(l10n.cancel),
onPressed: () => Navigator.pop(context),
),
const SizedBox(width: 15),
TextButton(
child: Text(l10n.ok),
onPressed: () {
isar.writeTxnSync(() {
if (isBulk) {
for (var manga in bulkMangas) {
manga.categories = categoryIds;
isar.mangas.putSync(manga);
_syncManga(manga, isFavorite, ref);
}
} else {
if (!isFavorite) {
singleManga!.favorite = true;
singleManga.dateAdded =
DateTime.now().millisecondsSinceEpoch;
}
singleManga.categories = categoryIds;
isar.mangas.putSync(singleManga);
_syncManga(singleManga, isFavorite, ref);
}
if (isBulk) {
ref.read(mangasListStateProvider.notifier).clear();
ref
.read(isLongPressedMangaStateProvider.notifier)
.update(false);
}
});
if (context.mounted) Navigator.pop(context);
},
),
],
),
],
),
],
),
),
);
}
void _syncManga(Manga manga, bool isFavorite, WidgetRef ref) {
final sync = ref.read(synchingProvider(syncId: 1).notifier);
if (isFavorite) {
sync.addChangedPart(ActionType.updateItem, manga.id, manga.toJson(), false);
} else {
sync.addChangedPart(ActionType.addItem, manga.id, manga.toJson(), false);
}
}