Merge pull request #536 from NBA2K1/main

Refactor select bar widget and unify manga read state logic
This commit is contained in:
Moustapha Kodjo Amadou 2025-08-19 12:38:16 +01:00 committed by GitHub
commit a15afd4334
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 429 additions and 711 deletions

View file

@ -18,11 +18,13 @@ 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/state_providers.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/providers/downloaded_only_state_provider.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/bottom_select_bar.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';
@ -570,159 +572,85 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
return const ProgressCenter();
},
),
bottomNavigationBar: Consumer(
builder: (context, ref, child) {
final isLongPressed = ref.watch(isLongPressedMangaStateProvider);
final color = Theme.of(context).textTheme.bodyLarge!.color!;
bottomNavigationBar: Builder(
builder: (context) {
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),
final color = Theme.of(context).textTheme.bodyLarge!.color!;
return BottomSelectBar(
isVisible: ref.watch(isLongPressedStateProvider),
actions: [
BottomSelectButton(
icon: Icon(Icons.label_outline_rounded, color: color),
onPressed: () {
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,
);
},
),
),
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: () {
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,
color: color,
),
BottomSelectButton(
icon: Icon(Icons.done_all_sharp, color: color),
onPressed: () {
ref
.read(
mangasSetIsReadStateProvider(
mangaIds: mangaIds,
markAsRead: true,
).notifier,
)
.set();
ref.invalidate(
getAllMangaWithoutCategoriesStreamProvider(
itemType: widget.itemType,
),
),
),
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),
);
ref.invalidate(
getAllMangaStreamProvider(
categoryId: null,
itemType: widget.itemType,
),
),
),
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),
);
},
),
BottomSelectButton(
icon: Icon(Icons.remove_done_sharp, color: color),
onPressed: () {
ref
.read(
mangasSetIsReadStateProvider(
mangaIds: mangaIds,
markAsRead: false,
).notifier,
)
.set();
ref.invalidate(
getAllMangaWithoutCategoriesStreamProvider(
itemType: widget.itemType,
),
),
),
// 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,
),
);
ref.invalidate(
getAllMangaStreamProvider(
categoryId: null,
itemType: widget.itemType,
),
),
),
],
),
);
},
),
// BottomBarAction(
// icon: Icon(Icons.download_outlined, color: color),
// onPressed: () {}
// ),
BottomSelectButton(
icon: Icon(Icons.delete_outline_outlined, color: color),
onPressed: () => _deleteManga(),
),
],
);
},
),
@ -1229,7 +1157,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
ref.read(mangasListStateProvider.notifier).clear();
ref
.read(isLongPressedMangaStateProvider.notifier)
.read(isLongPressedStateProvider.notifier)
.update(false);
if (mounted) {
Navigator.pop(context);
@ -1882,7 +1810,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
int? categoryId,
Settings settings,
) {
final isLongPressed = ref.watch(isLongPressedMangaStateProvider);
final isLongPressed = ref.watch(isLongPressedStateProvider);
final mangaIdsList = ref.watch(mangasListStateProvider);
final manga = categoryId == null
? ref.watch(
@ -1911,7 +1839,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
ref.read(mangasListStateProvider.notifier).clear();
ref
.read(isLongPressedMangaStateProvider.notifier)
.read(isLongPressedStateProvider.notifier)
.update(!isLongPressed);
},
icon: const Icon(Icons.clear),
@ -1936,7 +1864,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
.selectSome(manga);
}
ref
.read(isLongPressedMangaStateProvider.notifier)
.read(isLongPressedStateProvider.notifier)
.update(false);
} else {
for (var manga in data) {

View file

@ -3,6 +3,7 @@ import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/modules/manga/detail/providers/state_providers.dart';
import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provider.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -785,7 +786,7 @@ class MangasListState extends _$MangasListState {
newList.add(value.id!);
}
if (newList.isEmpty) {
ref.read(isLongPressedMangaStateProvider.notifier).update(false);
ref.read(isLongPressedStateProvider.notifier).update(false);
}
state = newList;
}
@ -814,65 +815,24 @@ class MangasListState extends _$MangasListState {
}
}
@riverpod
class IsLongPressedMangaState extends _$IsLongPressedMangaState {
@override
bool build() {
return false;
}
void update(bool value) {
state = value;
}
}
@riverpod
class MangasSetIsReadState extends _$MangasSetIsReadState {
@override
void build({required List<int> mangaIds}) {}
void build({required List<int> mangaIds, required bool markAsRead}) {}
void set() {
final allChapters = <Chapter>[];
final allMangas = <Manga>[];
final now = DateTime.now().millisecondsSinceEpoch;
for (var mangaid in mangaIds) {
final manga = isar.mangas.getSync(mangaid)!;
final chapters = manga.chapters;
if (chapters.isNotEmpty) {
chapters.last.updateTrackChapterRead(ref);
for (var chapter in chapters) {
chapter.isRead = true;
chapter.lastPageRead = "1";
chapter.updatedAt = DateTime.now().millisecondsSinceEpoch;
chapter.manga.value = manga;
allChapters.add(chapter);
}
allMangas.add(manga);
}
}
isar.writeTxnSync(() {
isar.chapters.putAllSync(allChapters);
isar.mangas.putAllSync(allMangas);
});
ref.read(isLongPressedMangaStateProvider.notifier).update(false);
ref.read(mangasListStateProvider.notifier).clear();
}
}
@riverpod
class MangasSetUnReadState extends _$MangasSetUnReadState {
@override
void build({required List<int> mangaIds}) {}
void set() {
final allChapters = <Chapter>[];
final allMangas = <Manga>[];
for (var mangaid in mangaIds) {
final manga = isar.mangas.getSync(mangaid)!;
for (var chapter in manga.chapters) {
chapter.isRead = false;
chapter.updatedAt = DateTime.now().millisecondsSinceEpoch;
if (chapters.isEmpty) continue;
if (markAsRead) chapters.last.updateTrackChapterRead(ref);
for (var chapter in chapters) {
chapter.isRead = markAsRead;
if (markAsRead) chapter.lastPageRead = "1";
chapter.updatedAt = now;
chapter.manga.value = manga;
allChapters.add(chapter);
}
@ -884,7 +844,7 @@ class MangasSetUnReadState extends _$MangasSetUnReadState {
isar.mangas.putAllSync(allMangas);
});
ref.read(isLongPressedMangaStateProvider.notifier).update(false);
ref.read(isLongPressedStateProvider.notifier).update(false);
ref.read(mangasListStateProvider.notifier).clear();
}
}

View file

@ -2517,7 +2517,7 @@ class _SortLibraryMangaStateProviderElement
Settings get settings => (origin as SortLibraryMangaStateProvider).settings;
}
String _$mangasListStateHash() => r'ad1cc419dfd3793bfc8c90f3ce8b7726561dd9ad';
String _$mangasListStateHash() => r'bbd2e3600ec22a774b1774ae3c221815e52bfef6';
/// See also [MangasListState].
@ProviderFor(MangasListState)
@ -2533,32 +2533,17 @@ final mangasListStateProvider =
);
typedef _$MangasListState = AutoDisposeNotifier<List<int>>;
String _$isLongPressedMangaStateHash() =>
r'f77076b0335e92df26a75ea0c338d4214a330184';
/// See also [IsLongPressedMangaState].
@ProviderFor(IsLongPressedMangaState)
final isLongPressedMangaStateProvider =
AutoDisposeNotifierProvider<IsLongPressedMangaState, bool>.internal(
IsLongPressedMangaState.new,
name: r'isLongPressedMangaStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$isLongPressedMangaStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$IsLongPressedMangaState = AutoDisposeNotifier<bool>;
String _$mangasSetIsReadStateHash() =>
r'8f4f7f22ea8e82cf2370fb997033e1a4ec03168f';
r'2a1b1005e2ed5068d36188a3fb969d21b64bfef6';
abstract class _$MangasSetIsReadState
extends BuildlessAutoDisposeNotifier<void> {
late final List<int> mangaIds;
late final bool markAsRead;
void build({
required List<int> mangaIds,
required bool markAsRead,
});
}
@ -2574,9 +2559,11 @@ class MangasSetIsReadStateFamily extends Family<void> {
/// See also [MangasSetIsReadState].
MangasSetIsReadStateProvider call({
required List<int> mangaIds,
required bool markAsRead,
}) {
return MangasSetIsReadStateProvider(
mangaIds: mangaIds,
markAsRead: markAsRead,
);
}
@ -2586,6 +2573,7 @@ class MangasSetIsReadStateFamily extends Family<void> {
) {
return call(
mangaIds: provider.mangaIds,
markAsRead: provider.markAsRead,
);
}
@ -2610,8 +2598,11 @@ class MangasSetIsReadStateProvider
/// See also [MangasSetIsReadState].
MangasSetIsReadStateProvider({
required List<int> mangaIds,
required bool markAsRead,
}) : this._internal(
() => MangasSetIsReadState()..mangaIds = mangaIds,
() => MangasSetIsReadState()
..mangaIds = mangaIds
..markAsRead = markAsRead,
from: mangasSetIsReadStateProvider,
name: r'mangasSetIsReadStateProvider',
debugGetCreateSourceHash:
@ -2622,6 +2613,7 @@ class MangasSetIsReadStateProvider
allTransitiveDependencies:
MangasSetIsReadStateFamily._allTransitiveDependencies,
mangaIds: mangaIds,
markAsRead: markAsRead,
);
MangasSetIsReadStateProvider._internal(
@ -2632,9 +2624,11 @@ class MangasSetIsReadStateProvider
required super.debugGetCreateSourceHash,
required super.from,
required this.mangaIds,
required this.markAsRead,
}) : super.internal();
final List<int> mangaIds;
final bool markAsRead;
@override
void runNotifierBuild(
@ -2642,6 +2636,7 @@ class MangasSetIsReadStateProvider
) {
return notifier.build(
mangaIds: mangaIds,
markAsRead: markAsRead,
);
}
@ -2650,13 +2645,16 @@ class MangasSetIsReadStateProvider
return ProviderOverride(
origin: this,
override: MangasSetIsReadStateProvider._internal(
() => create()..mangaIds = mangaIds,
() => create()
..mangaIds = mangaIds
..markAsRead = markAsRead,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
mangaIds: mangaIds,
markAsRead: markAsRead,
),
);
}
@ -2669,13 +2667,16 @@ class MangasSetIsReadStateProvider
@override
bool operator ==(Object other) {
return other is MangasSetIsReadStateProvider && other.mangaIds == mangaIds;
return other is MangasSetIsReadStateProvider &&
other.mangaIds == mangaIds &&
other.markAsRead == markAsRead;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, mangaIds.hashCode);
hash = _SystemHash.combine(hash, markAsRead.hashCode);
return _SystemHash.finish(hash);
}
@ -2686,6 +2687,9 @@ class MangasSetIsReadStateProvider
mixin MangasSetIsReadStateRef on AutoDisposeNotifierProviderRef<void> {
/// The parameter `mangaIds` of this provider.
List<int> get mangaIds;
/// The parameter `markAsRead` of this provider.
bool get markAsRead;
}
class _MangasSetIsReadStateProviderElement
@ -2695,153 +2699,8 @@ class _MangasSetIsReadStateProviderElement
@override
List<int> get mangaIds => (origin as MangasSetIsReadStateProvider).mangaIds;
}
String _$mangasSetUnReadStateHash() =>
r'09ddd287b110fd76494f9f56bd5cf76f58936f1f';
abstract class _$MangasSetUnReadState
extends BuildlessAutoDisposeNotifier<void> {
late final List<int> mangaIds;
void build({
required List<int> mangaIds,
});
}
/// See also [MangasSetUnReadState].
@ProviderFor(MangasSetUnReadState)
const mangasSetUnReadStateProvider = MangasSetUnReadStateFamily();
/// See also [MangasSetUnReadState].
class MangasSetUnReadStateFamily extends Family<void> {
/// See also [MangasSetUnReadState].
const MangasSetUnReadStateFamily();
/// See also [MangasSetUnReadState].
MangasSetUnReadStateProvider call({
required List<int> mangaIds,
}) {
return MangasSetUnReadStateProvider(
mangaIds: mangaIds,
);
}
@override
MangasSetUnReadStateProvider getProviderOverride(
covariant MangasSetUnReadStateProvider provider,
) {
return call(
mangaIds: provider.mangaIds,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'mangasSetUnReadStateProvider';
}
/// See also [MangasSetUnReadState].
class MangasSetUnReadStateProvider
extends AutoDisposeNotifierProviderImpl<MangasSetUnReadState, void> {
/// See also [MangasSetUnReadState].
MangasSetUnReadStateProvider({
required List<int> mangaIds,
}) : this._internal(
() => MangasSetUnReadState()..mangaIds = mangaIds,
from: mangasSetUnReadStateProvider,
name: r'mangasSetUnReadStateProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$mangasSetUnReadStateHash,
dependencies: MangasSetUnReadStateFamily._dependencies,
allTransitiveDependencies:
MangasSetUnReadStateFamily._allTransitiveDependencies,
mangaIds: mangaIds,
);
MangasSetUnReadStateProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.mangaIds,
}) : super.internal();
final List<int> mangaIds;
@override
void runNotifierBuild(
covariant MangasSetUnReadState notifier,
) {
return notifier.build(
mangaIds: mangaIds,
);
}
@override
Override overrideWith(MangasSetUnReadState Function() create) {
return ProviderOverride(
origin: this,
override: MangasSetUnReadStateProvider._internal(
() => create()..mangaIds = mangaIds,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
mangaIds: mangaIds,
),
);
}
@override
AutoDisposeNotifierProviderElement<MangasSetUnReadState, void>
createElement() {
return _MangasSetUnReadStateProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is MangasSetUnReadStateProvider && other.mangaIds == mangaIds;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, mangaIds.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin MangasSetUnReadStateRef on AutoDisposeNotifierProviderRef<void> {
/// The parameter `mangaIds` of this provider.
List<int> get mangaIds;
}
class _MangasSetUnReadStateProviderElement
extends AutoDisposeNotifierProviderElement<MangasSetUnReadState, void>
with MangasSetUnReadStateRef {
_MangasSetUnReadStateProviderElement(super.provider);
@override
List<int> get mangaIds => (origin as MangasSetUnReadStateProvider).mangaIds;
bool get markAsRead => (origin as MangasSetIsReadStateProvider).markAsRead;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View file

@ -9,6 +9,7 @@ import 'package:mangayomi/models/history.dart';
import 'package:mangayomi/modules/library/providers/isar_providers.dart';
import 'package:mangayomi/modules/library/providers/library_state_provider.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/modules/manga/detail/providers/state_providers.dart';
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/utils/constant.dart';
@ -52,7 +53,7 @@ class _LibraryGridViewWidgetState extends State<LibraryGridViewWidget> {
Widget build(BuildContext context) {
return Consumer(
builder: (context, ref, child) {
final isLongPressed = ref.watch(isLongPressedMangaStateProvider);
final isLongPressed = ref.watch(isLongPressedStateProvider);
final itemType = widget.itemType;
final gridSize = ref.watch(
@ -430,7 +431,7 @@ class _LibraryGridViewWidgetState extends State<LibraryGridViewWidget> {
) {
if (!isLongPressed) {
ref.read(mangasListStateProvider.notifier).update(entry);
ref.read(isLongPressedMangaStateProvider.notifier).update(!isLongPressed);
ref.read(isLongPressedStateProvider.notifier).update(!isLongPressed);
} else {
ref.read(mangasListStateProvider.notifier).update(entry);
}

View file

@ -9,6 +9,7 @@ import 'package:mangayomi/models/history.dart';
import 'package:mangayomi/modules/library/providers/isar_providers.dart';
import 'package:mangayomi/modules/library/providers/library_state_provider.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/modules/manga/detail/providers/state_providers.dart';
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/utils/constant.dart';
@ -44,7 +45,7 @@ class LibraryListViewWidget extends StatelessWidget {
bool isLocalArchive = entry.isLocalArchive ?? false;
return Consumer(
builder: (context, ref, child) {
final isLongPressed = ref.watch(isLongPressedMangaStateProvider);
final isLongPressed = ref.watch(isLongPressedStateProvider);
return Material(
borderRadius: BorderRadius.circular(5),
color: Colors.transparent,
@ -80,7 +81,7 @@ class LibraryListViewWidget extends StatelessWidget {
ref.read(mangasListStateProvider.notifier).update(entry);
ref
.read(isLongPressedMangaStateProvider.notifier)
.read(isLongPressedStateProvider.notifier)
.update(!isLongPressed);
} else {
ref.read(mangasListStateProvider.notifier).update(entry);
@ -91,7 +92,7 @@ class LibraryListViewWidget extends StatelessWidget {
ref.read(mangasListStateProvider.notifier).update(entry);
ref
.read(isLongPressedMangaStateProvider.notifier)
.read(isLongPressedStateProvider.notifier)
.update(!isLongPressed);
} else {
ref.read(mangasListStateProvider.notifier).update(entry);

View file

@ -24,7 +24,7 @@ import 'package:mangayomi/router/router.dart';
import 'package:mangayomi/services/fetch_sources_list.dart';
import 'package:mangayomi/services/sync_server.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/modules/library/providers/library_state_provider.dart';
import 'package:mangayomi/modules/manga/detail/providers/state_providers.dart';
import 'package:mangayomi/modules/more/providers/incognito_mode_state_provider.dart';
final libLocationRegex = RegExp(r"^/(Manga|Anime|Novel)Library$");
@ -241,7 +241,7 @@ class _MainScreenState extends ConsumerState<MainScreen> {
final incognitoMode = ref.watch(incognitoModeStateProvider);
final downloadedOnly = ref.watch(downloadedOnlyStateProvider);
final isLongPressed = ref.watch(isLongPressedMangaStateProvider);
final isLongPressed = ref.watch(isLongPressedStateProvider);
return Column(
children: [

View file

@ -25,6 +25,7 @@ import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provi
import 'package:mangayomi/modules/more/providers/algorithm_weights_state_provider.dart';
import 'package:mangayomi/modules/more/settings/appearance/providers/pure_black_dark_mode_state_provider.dart';
import 'package:mangayomi/modules/more/settings/track/widgets/track_listile.dart';
import 'package:mangayomi/modules/widgets/bottom_select_bar.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';
@ -827,8 +828,8 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
],
),
),
bottomNavigationBar: Consumer(
builder: (context, ref, child) {
bottomNavigationBar: Builder(
builder: (context) {
final chap = ref.watch(chaptersListStateProvider);
bool getLength1 = chap.length == 1;
bool checkFirstBookmarked =
@ -837,340 +838,241 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
chap.isNotEmpty && chap.first.isRead! && getLength1;
final l10n = l10nLocalizations(context)!;
final color = Theme.of(context).textTheme.bodyLarge!.color!;
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),
return BottomSelectBar(
isVisible: isLongPressed,
actions: [
BottomSelectButton(
icon: Icon(
checkFirstBookmarked
? Icons.bookmark_remove_outlined
: Icons.bookmark_add_outlined,
color: color,
),
onPressed: () {
final chapters = ref.watch(chaptersListStateProvider);
final List<Chapter> updatedChapters = [];
final now = DateTime.now().millisecondsSinceEpoch;
for (var chapter in chapters) {
chapter.isBookmarked = !chapter.isBookmarked!;
chapter.updatedAt = now;
chapter.manga.value = widget.manga;
updatedChapters.add(chapter);
}
isar.writeTxnSync(() {
isar.chapters.putAllSync(updatedChapters);
});
ref
.read(isLongPressedStateProvider.notifier)
.update(false);
ref.read(chaptersListStateProvider.notifier).clear();
},
),
),
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(
elevation: 0,
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
),
onPressed: () {
final chapters = ref.watch(
chaptersListStateProvider,
);
final List<Chapter> updatedChapters = [];
final now = DateTime.now().millisecondsSinceEpoch;
for (var chapter in chapters) {
chapter.isBookmarked = !chapter.isBookmarked!;
chapter.updatedAt = now;
chapter.manga.value = widget.manga;
updatedChapters.add(chapter);
}
isar.writeTxnSync(() {
isar.chapters.putAllSync(updatedChapters);
});
ref
.read(isLongPressedStateProvider.notifier)
.update(false);
ref
.read(chaptersListStateProvider.notifier)
.clear();
},
child: Icon(
checkFirstBookmarked
? Icons.bookmark_remove_outlined
: Icons.bookmark_add_outlined,
color: color,
),
),
),
BottomSelectButton(
icon: Icon(
checkReadBookmarked
? Icons.remove_done_sharp
: 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: () {
final chapters = ref.watch(
chaptersListStateProvider,
);
final List<Chapter> updatedChapters = [];
final now = DateTime.now().millisecondsSinceEpoch;
for (var chapter in chapters) {
chapter.isRead = !chapter.isRead!;
if (!chapter.isRead!) {
chapter.lastPageRead = "1";
}
chapter.updatedAt = now;
chapter.manga.value = widget.manga;
updatedChapters.add(chapter);
if (chapter.isRead!) {
chapter.updateTrackChapterRead(ref);
}
}
isar.writeTxnSync(() {
isar.chapters.putAllSync(updatedChapters);
isar.mangas.putSync(widget.manga!);
});
ref
.read(isLongPressedStateProvider.notifier)
.update(false);
ref
.read(chaptersListStateProvider.notifier)
.clear();
},
child: Icon(
checkReadBookmarked
? Icons.remove_done_sharp
: Icons.done_all_sharp,
color: color,
),
),
),
),
if (getLength1)
Expanded(
child: SizedBox(
height: 70,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
elevation: 0,
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
),
onPressed: () {
int index = chapters.indexOf(chap.first);
final List<Chapter> updatedChapters = [];
final now = DateTime.now().millisecondsSinceEpoch;
chapters[index + 1].updateTrackChapterRead(ref);
for (
var i = index + 1;
i < chapters.length;
i++
) {
final chapter = chapters[i];
if (!chapter.isRead!) {
chapter.isRead = true;
chapter.lastPageRead = "1";
chapter.updatedAt = now;
chapter.manga.value = widget.manga;
updatedChapters.add(chapter);
}
}
isar.writeTxnSync(() {
isar.chapters.putAllSync(updatedChapters);
isar.mangas.putSync(widget.manga!);
});
ref
.read(isLongPressedStateProvider.notifier)
.update(false);
ref
.read(chaptersListStateProvider.notifier)
.clear();
},
child: Stack(
children: [
Icon(Icons.done_outlined, color: color),
Positioned(
bottom: 0,
right: 0,
child: Icon(
Icons.arrow_downward_outlined,
size: 11,
color: color,
),
),
],
),
),
),
),
if (!isLocalArchive)
Expanded(
child: SizedBox(
height: 70,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
elevation: 0,
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
),
onPressed: () {
for (var chapter in ref.watch(
chaptersListStateProvider,
)) {
final entries = isar.downloads
.filter()
.idEqualTo(chapter.id)
.findAllSync();
if (entries.isEmpty ||
!entries.first.isDownload!) {
ref.read(
addDownloadToQueueProvider(
chapter: chapter,
),
);
}
}
ref.watch(processDownloadsProvider());
ref
.read(isLongPressedStateProvider.notifier)
.update(false);
ref
.read(chaptersListStateProvider.notifier)
.clear();
},
child: Icon(Icons.download_outlined, color: color),
),
),
),
if (isLocalArchive)
Expanded(
child: SizedBox(
height: 70,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
elevation: 0,
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
),
onPressed: () {
final selectedChapters = ref.watch(
chaptersListStateProvider,
);
final totalChapters =
widget.manga!.chapters.length;
final isLastChapters =
selectedChapters.length == totalChapters;
final isAnime = widget.itemType == ItemType.anime;
final entryType = isAnime
? l10n.episode
: l10n.chapter;
final pluralEntryType = isAnime
? l10n.episodes
: l10n.chapters;
final mediaType = isAnime
? l10n.anime
: l10n.manga;
final warningMessage = l10n
.last_entry_delete_warning(
totalChapters,
entryType,
pluralEntryType,
mediaType,
);
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(l10n.delete_chapters),
content: isLastChapters
? Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const Icon(
Icons.warning_amber_rounded,
color: Colors.orange,
),
const SizedBox(width: 12),
Expanded(
child: Text(
warningMessage,
style: TextStyle(
color: Colors.red,
),
),
),
],
)
: null,
actions: [
Row(
mainAxisAlignment:
MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(l10n.cancel),
),
const SizedBox(width: 15),
TextButton(
onPressed: () async {
final navigator = Navigator.of(
context,
);
await isar.writeTxn(() async {
final idsToDelete =
selectedChapters
.map((c) => c.id!)
.toList();
await isar.chapters.deleteAll(
idsToDelete,
);
});
if (!mounted) return;
ref
.read(
isLongPressedStateProvider
.notifier,
)
.update(false);
ref
.read(
chaptersListStateProvider
.notifier,
)
.clear();
navigator.pop();
if (isLastChapters) {
navigator.pop();
Future.delayed(
const Duration(
milliseconds: 350,
),
() {
isar.writeTxn(
() => isar.mangas.delete(
widget.manga!.id!,
),
);
},
);
}
},
child: Text(l10n.delete),
),
],
),
],
);
},
);
},
onPressed: () {
final chapters = ref.watch(chaptersListStateProvider);
final List<Chapter> updatedChapters = [];
final now = DateTime.now().millisecondsSinceEpoch;
for (var chapter in chapters) {
chapter.isRead = !chapter.isRead!;
if (!chapter.isRead!) {
chapter.lastPageRead = "1";
}
chapter.updatedAt = now;
chapter.manga.value = widget.manga;
updatedChapters.add(chapter);
if (chapter.isRead!) {
chapter.updateTrackChapterRead(ref);
}
}
isar.writeTxnSync(() {
isar.chapters.putAllSync(updatedChapters);
isar.mangas.putSync(widget.manga!);
});
ref
.read(isLongPressedStateProvider.notifier)
.update(false);
ref.read(chaptersListStateProvider.notifier).clear();
},
),
if (getLength1)
BottomSelectButton(
icon: Stack(
children: [
Icon(Icons.done_outlined, color: color),
Positioned(
bottom: 0,
right: 0,
child: Icon(
Icons.delete_outline_outlined,
Icons.arrow_downward_outlined,
size: 11,
color: color,
),
),
),
],
),
],
),
onPressed: () {
int index = chapters.indexOf(chap.first);
final List<Chapter> updatedChapters = [];
final now = DateTime.now().millisecondsSinceEpoch;
chapters[index + 1].updateTrackChapterRead(ref);
for (var i = index + 1; i < chapters.length; i++) {
final chapter = chapters[i];
if (!chapter.isRead!) {
chapter.isRead = true;
chapter.lastPageRead = "1";
chapter.updatedAt = now;
chapter.manga.value = widget.manga;
updatedChapters.add(chapter);
}
}
isar.writeTxnSync(() {
isar.chapters.putAllSync(updatedChapters);
isar.mangas.putSync(widget.manga!);
});
ref
.read(isLongPressedStateProvider.notifier)
.update(false);
ref.read(chaptersListStateProvider.notifier).clear();
},
),
if (!isLocalArchive)
BottomSelectButton(
icon: Icon(Icons.download_outlined, color: color),
onPressed: () {
for (var chapter in ref.watch(
chaptersListStateProvider,
)) {
final entries = isar.downloads
.filter()
.idEqualTo(chapter.id)
.findAllSync();
if (entries.isEmpty || !entries.first.isDownload!) {
ref.read(
addDownloadToQueueProvider(chapter: chapter),
);
}
}
ref.watch(processDownloadsProvider());
ref
.read(isLongPressedStateProvider.notifier)
.update(false);
ref.read(chaptersListStateProvider.notifier).clear();
},
),
if (isLocalArchive)
BottomSelectButton(
icon: Icon(Icons.delete_outline_outlined, color: color),
onPressed: () {
final selectedChapters = ref.watch(
chaptersListStateProvider,
);
final totalChapters = widget.manga!.chapters.length;
final isLastChapters =
selectedChapters.length == totalChapters;
final isAnime = widget.itemType == ItemType.anime;
final entryType = isAnime ? l10n.episode : l10n.chapter;
final pluralEntryType = isAnime
? l10n.episodes
: l10n.chapters;
final mediaType = isAnime ? l10n.anime : l10n.manga;
final warningMessage = l10n.last_entry_delete_warning(
totalChapters,
entryType,
pluralEntryType,
mediaType,
);
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(l10n.delete_chapters),
content: isLastChapters
? Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const Icon(
Icons.warning_amber_rounded,
color: Colors.orange,
),
const SizedBox(width: 12),
Expanded(
child: Text(
warningMessage,
style: TextStyle(color: Colors.red),
),
),
],
)
: null,
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(l10n.cancel),
),
const SizedBox(width: 15),
TextButton(
onPressed: () async {
final navigator = Navigator.of(context);
await isar.writeTxn(() async {
final idsToDelete = selectedChapters
.map((c) => c.id!)
.toList();
await isar.chapters.deleteAll(
idsToDelete,
);
});
if (!mounted) return;
ref
.read(
isLongPressedStateProvider
.notifier,
)
.update(false);
ref
.read(
chaptersListStateProvider
.notifier,
)
.clear();
navigator.pop();
if (isLastChapters) {
navigator.pop();
Future.delayed(
const Duration(milliseconds: 350),
() {
isar.writeTxn(
() => isar.mangas.delete(
widget.manga!.id!,
),
);
},
);
}
},
child: Text(l10n.delete),
),
],
),
],
);
},
);
},
),
],
);
},
),

View file

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
/// Bar, that appears at the bottom of the screen when long-pressing (selecting)
/// a Manga/Anime/Novel or Chapter/Episode
class BottomSelectBar extends StatelessWidget {
final bool isVisible;
final List<BottomSelectButton> actions;
const BottomSelectBar({
super.key,
required this.isVisible,
required this.actions,
});
@override
Widget build(BuildContext context) {
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: isVisible ? 70 : 0,
width: context.width(1),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: actions,
),
);
}
}
/// Button for the BottomSelectBar
class BottomSelectButton extends StatelessWidget {
final Widget icon;
final VoidCallback onPressed;
const BottomSelectButton({
super.key,
required this.icon,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
return Expanded(
child: SizedBox(
height: 70,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
elevation: 0,
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
),
onPressed: onPressed,
child: icon,
),
),
);
}
}

View file

@ -7,6 +7,7 @@ import 'package:mangayomi/models/category.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/providers/state_providers.dart';
import 'package:mangayomi/modules/manga/detail/widgets/chapter_filter_list_tile_widget.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
@ -151,7 +152,7 @@ void showCategorySelectionDialog({
if (isBulk) {
ref.read(mangasListStateProvider.notifier).clear();
ref
.read(isLongPressedMangaStateProvider.notifier)
.read(isLongPressedStateProvider.notifier)
.update(false);
}
});

View file

@ -6,7 +6,7 @@ part of 'aniskip.dart';
// RiverpodGenerator
// **************************************************************************
String _$aniSkipHash() => r'2e5d19b025a2207ff64da7bf7908450ea9e5ff8c';
String _$aniSkipHash() => r'887869b54e2e151633efd46da83bde845e14f421';
/// See also [AniSkip].
@ProviderFor(AniSkip)

View file

@ -6,7 +6,7 @@ part of 'sync_server.dart';
// RiverpodGenerator
// **************************************************************************
String _$syncServerHash() => r'97a778696e0cc8b8e4c706de50d60464bb7b2f03';
String _$syncServerHash() => r'141ba3be28182e05480e06fbf3f1de68f868cb8e';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -6,7 +6,7 @@ part of 'anilist.dart';
// RiverpodGenerator
// **************************************************************************
String _$anilistHash() => r'fafb964252b3a5741e981cb8c2f0f2090b3b86ae';
String _$anilistHash() => r'c786a526fdacc875e4a7e00886b2bda546eafeae';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -6,7 +6,7 @@ part of 'myanimelist.dart';
// RiverpodGenerator
// **************************************************************************
String _$myAnimeListHash() => r'a612e9ce814268ac79dc86d810ca6bd3671812e6';
String _$myAnimeListHash() => r'4391ad9446d14b1fb1ffdfbc5323ef04db5140f7';
/// Copied from Dart SDK
class _SystemHash {