added custom draggable tabbar & fix
This commit is contained in:
parent
468db9d8a6
commit
c564bed263
7 changed files with 1068 additions and 1321 deletions
|
|
@ -1,7 +1,6 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:draggable_menu/draggable_menu.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
|
@ -14,6 +13,7 @@ import 'package:mangayomi/modules/anime/widgets/desktop.dart';
|
|||
import 'package:mangayomi/modules/anime/widgets/mobile.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/providers/push_router.dart';
|
||||
import 'package:mangayomi/modules/more/settings/player/providers/player_state_provider.dart';
|
||||
import 'package:mangayomi/modules/widgets/custom_draggable_tabbar.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/services/aniskip.dart';
|
||||
|
|
@ -45,9 +45,8 @@ class _AnimePlayerViewState extends riv.ConsumerState<AnimePlayerView> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final serversData = ref.watch(getVideoListProvider(
|
||||
episode: widget.episode,
|
||||
));
|
||||
final serversData =
|
||||
ref.watch(getVideoListProvider(episode: widget.episode));
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
|
||||
return serversData.when(
|
||||
data: (data) {
|
||||
|
|
@ -128,7 +127,8 @@ class AnimeStreamPage extends riv.ConsumerStatefulWidget {
|
|||
riv.ConsumerState<AnimeStreamPage> createState() => _AnimeStreamPageState();
|
||||
}
|
||||
|
||||
class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
||||
class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
||||
with TickerProviderStateMixin {
|
||||
late final GlobalKey<VideoState> _key = GlobalKey<VideoState>();
|
||||
late final Player _player = Player();
|
||||
late final VideoController _controller = VideoController(_player);
|
||||
|
|
@ -294,6 +294,19 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
}
|
||||
}
|
||||
|
||||
Widget textWidget(String text, bool selected) => Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(text,
|
||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
fontSize: 16,
|
||||
fontStyle: selected ? FontStyle.italic : null,
|
||||
color: selected ? context.primaryColor : null),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis)),
|
||||
],
|
||||
);
|
||||
|
||||
Widget _videoQualityWidget(BuildContext context) {
|
||||
List<VideoPrefs> videoQuality = _player.state.tracks.video
|
||||
.where((element) =>
|
||||
|
|
@ -311,184 +324,52 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
}
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: videoQuality.map((quality) {
|
||||
final selected =
|
||||
_video.value!.videoTrack!.title == quality.videoTrack!.title ||
|
||||
widget.isLocal;
|
||||
return GestureDetector(
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Icon(
|
||||
Icons.check,
|
||||
color: selected ? Colors.white : Colors.transparent,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.isLocal
|
||||
? _firstVid.quality
|
||||
: quality.videoTrack!.title!,
|
||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
fontSize: 16,
|
||||
color: selected
|
||||
? Colors.white
|
||||
: Colors.white.withOpacity(0.6)),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
_video.value = quality; // change the video quality
|
||||
if (quality.isLocal) {
|
||||
if (widget.isLocal) {
|
||||
_player.setVideoTrack(quality.videoTrack!);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 12),
|
||||
child: Column(
|
||||
children: videoQuality.map((quality) {
|
||||
final selected =
|
||||
_video.value!.videoTrack!.title == quality.videoTrack!.title ||
|
||||
widget.isLocal;
|
||||
return GestureDetector(
|
||||
child: textWidget(
|
||||
widget.isLocal ? _firstVid.quality : quality.videoTrack!.title!,
|
||||
selected),
|
||||
onTap: () {
|
||||
_video.value = quality; // change the video quality
|
||||
if (quality.isLocal) {
|
||||
if (widget.isLocal) {
|
||||
_player.setVideoTrack(quality.videoTrack!);
|
||||
} else {
|
||||
_player.open(Media(quality.videoTrack!.id,
|
||||
httpHeaders: quality.headers));
|
||||
}
|
||||
} else {
|
||||
_player.open(Media(quality.videoTrack!.id,
|
||||
httpHeaders: quality.headers));
|
||||
}
|
||||
} else {
|
||||
_player.open(
|
||||
Media(quality.videoTrack!.id, httpHeaders: quality.headers));
|
||||
}
|
||||
_seekToCurrentPosition(duration: _currentPosition.value);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
_seekToCurrentPosition(duration: _currentPosition.value);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _videoSettingDraggableMenu(BuildContext context) async {
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
_player.pause();
|
||||
await DraggableMenu.open(
|
||||
context,
|
||||
DraggableMenu(
|
||||
ui: ClassicDraggableMenu(
|
||||
radius: 30,
|
||||
barItem: Container(),
|
||||
color: Colors.black.withOpacity(0.6)),
|
||||
minimizeThreshold: 0.6,
|
||||
levels: [
|
||||
DraggableMenuLevel.ratio(ratio: 2 / 3),
|
||||
DraggableMenuLevel.ratio(ratio: 0.9),
|
||||
],
|
||||
minimizeBeforeFastDrag: true,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 8, left: 12, bottom: 5),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(l10n.video_quality,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20)),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 5)),
|
||||
_videoQualityWidget(context)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
color: Colors.white,
|
||||
width: 0.2,
|
||||
height: context.mediaHeight(1)),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 8, left: 12, bottom: 5),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
l10n.video_subtitle,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 5)),
|
||||
_videoSubtitle(context)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
color: Colors.white,
|
||||
width: 0.2,
|
||||
height: context.mediaHeight(1)),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 8, left: 12, bottom: 5),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
l10n.video_audio,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 5)),
|
||||
_videoAudios(context)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)),
|
||||
),
|
||||
));
|
||||
await customDraggableTabBar(tabs: [
|
||||
Tab(text: l10n.video_quality),
|
||||
Tab(text: l10n.video_subtitle),
|
||||
Tab(text: l10n.video_audio),
|
||||
], children: [
|
||||
_videoQualityWidget(context),
|
||||
_videoSubtitle(context),
|
||||
_videoAudios(context)
|
||||
], context: context, vsync: this, fullWidth: true);
|
||||
|
||||
_player.play();
|
||||
}
|
||||
|
||||
|
|
@ -535,46 +416,29 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
videoSubtitle.sort((a, b) => a.title!.compareTo(b.title!));
|
||||
videoSubtitle.insert(
|
||||
0, VideoPrefs(isLocal: false, subtitle: SubtitleTrack.no()));
|
||||
return Column(
|
||||
children: videoSubtitle.toSet().toList().map((sub) {
|
||||
final selected = subtitle != null && sub.subtitle == subtitle;
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
try {
|
||||
_player.setSubtitleTrack(sub.subtitle!);
|
||||
if (!widget.isLocal) _subtitle.value = sub.subtitle;
|
||||
} catch (_) {}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Icon(
|
||||
Icons.check,
|
||||
color: selected ? Colors.white : Colors.transparent,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
sub.title ??
|
||||
sub.subtitle?.title ??
|
||||
sub.subtitle?.language ??
|
||||
sub.subtitle?.channels ??
|
||||
"None",
|
||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
fontSize: 16,
|
||||
color: selected
|
||||
? Colors.white
|
||||
: Colors.white.withOpacity(0.6)),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 12),
|
||||
child: Column(
|
||||
children: videoSubtitle.toSet().toList().map((sub) {
|
||||
final selected = subtitle != null && sub.subtitle == subtitle;
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
try {
|
||||
_player.setSubtitleTrack(sub.subtitle!);
|
||||
if (!widget.isLocal) _subtitle.value = sub.subtitle;
|
||||
} catch (_) {}
|
||||
},
|
||||
child: textWidget(
|
||||
sub.title ??
|
||||
sub.subtitle?.title ??
|
||||
sub.subtitle?.language ??
|
||||
sub.subtitle?.channels ??
|
||||
"None",
|
||||
selected),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -621,46 +485,29 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
videoAudio.sort((a, b) => a.title!.compareTo(b.title!));
|
||||
videoAudio.insert(
|
||||
0, VideoPrefs(isLocal: false, subtitle: SubtitleTrack.no()));
|
||||
return Column(
|
||||
children: videoAudio.toSet().toList().map((aud) {
|
||||
final selected = audio != null && aud.audio == audio;
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
try {
|
||||
_player.setAudioTrack(aud.audio!);
|
||||
if (!widget.isLocal) _audio.value = aud.audio;
|
||||
} catch (_) {}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Icon(
|
||||
Icons.check,
|
||||
color: selected ? Colors.white : Colors.transparent,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
aud.title ??
|
||||
aud.audio?.title ??
|
||||
aud.audio?.language ??
|
||||
aud.audio?.channels ??
|
||||
"None",
|
||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
fontSize: 16,
|
||||
color: selected
|
||||
? Colors.white
|
||||
: Colors.white.withOpacity(0.6)),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 12),
|
||||
child: Column(
|
||||
children: videoAudio.toSet().toList().map((aud) {
|
||||
final selected = audio != null && aud.audio == audio;
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
try {
|
||||
_player.setAudioTrack(aud.audio!);
|
||||
if (!widget.isLocal) _audio.value = aud.audio;
|
||||
} catch (_) {}
|
||||
},
|
||||
child: textWidget(
|
||||
aud.title ??
|
||||
aud.audio?.title ??
|
||||
aud.audio?.language ??
|
||||
aud.audio?.channels ??
|
||||
"None",
|
||||
selected),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import 'dart:io';
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:draggable_menu/draggable_menu.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
|
@ -18,6 +17,7 @@ import 'package:mangayomi/models/settings.dart';
|
|||
import 'package:mangayomi/modules/library/providers/local_archive.dart';
|
||||
import 'package:mangayomi/modules/manga/detail/providers/update_manga_detail_providers.dart';
|
||||
import 'package:mangayomi/modules/more/categories/providers/isar_providers.dart';
|
||||
import 'package:mangayomi/modules/widgets/custom_draggable_tabbar.dart';
|
||||
import 'package:mangayomi/modules/widgets/manga_image_card_widget.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/providers/storage_provider.dart';
|
||||
|
|
@ -1229,418 +1229,297 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
|
|||
}
|
||||
|
||||
_showDraggableMenu(Settings settings) {
|
||||
late TabController tabBarController;
|
||||
tabBarController = TabController(length: 3, vsync: this);
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
DraggableMenu.open(
|
||||
context,
|
||||
DraggableMenu(
|
||||
ui: SoftModernDraggableMenu(barItem: Container(), radius: 20),
|
||||
minimizeThreshold: 0.6,
|
||||
levels: [
|
||||
DraggableMenuLevel.ratio(ratio: 1 / 3),
|
||||
DraggableMenuLevel.ratio(ratio: 2 / 3),
|
||||
DraggableMenuLevel.ratio(ratio: 0.9),
|
||||
],
|
||||
minimizeBeforeFastDrag: true,
|
||||
child: DefaultTabController(
|
||||
length: 3,
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Theme.of(context).scaffoldBackgroundColor),
|
||||
child: Column(
|
||||
children: [
|
||||
TabBar(
|
||||
controller: tabBarController,
|
||||
tabs: [
|
||||
Tab(text: l10n.filter),
|
||||
Tab(text: l10n.sort),
|
||||
Tab(text: l10n.display),
|
||||
],
|
||||
),
|
||||
Flexible(
|
||||
child: TabBarView(
|
||||
controller: tabBarController,
|
||||
children: [
|
||||
Consumer(builder: (context, ref, chil) {
|
||||
return Column(
|
||||
children: [
|
||||
ListTileChapterFilter(
|
||||
label: l10n.downloaded,
|
||||
type: ref.watch(
|
||||
mangaFilterDownloadedStateProvider(
|
||||
isManga: widget.isManga,
|
||||
mangaList: _entries,
|
||||
settings: settings)),
|
||||
onTap: () {
|
||||
ref
|
||||
.read(
|
||||
mangaFilterDownloadedStateProvider(
|
||||
isManga:
|
||||
widget.isManga,
|
||||
mangaList: _entries,
|
||||
settings: settings)
|
||||
.notifier)
|
||||
.update();
|
||||
}),
|
||||
ListTileChapterFilter(
|
||||
label: l10n.unread,
|
||||
type: ref.watch(
|
||||
mangaFilterUnreadStateProvider(
|
||||
isManga: widget.isManga,
|
||||
mangaList: _entries,
|
||||
settings: settings)),
|
||||
onTap: () {
|
||||
ref
|
||||
.read(
|
||||
mangaFilterUnreadStateProvider(
|
||||
isManga:
|
||||
widget.isManga,
|
||||
mangaList: _entries,
|
||||
settings: settings)
|
||||
.notifier)
|
||||
.update();
|
||||
}),
|
||||
ListTileChapterFilter(
|
||||
label: l10n.started,
|
||||
type: ref.watch(
|
||||
mangaFilterStartedStateProvider(
|
||||
isManga: widget.isManga,
|
||||
mangaList: _entries,
|
||||
settings: settings)),
|
||||
onTap: () {
|
||||
ref
|
||||
.read(
|
||||
mangaFilterStartedStateProvider(
|
||||
isManga:
|
||||
widget.isManga,
|
||||
mangaList: _entries,
|
||||
settings: settings)
|
||||
.notifier)
|
||||
.update();
|
||||
}),
|
||||
ListTileChapterFilter(
|
||||
label: l10n.bookmarked,
|
||||
type: ref.watch(
|
||||
mangaFilterBookmarkedStateProvider(
|
||||
isManga: widget.isManga,
|
||||
mangaList: _entries,
|
||||
settings: settings)),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
ref
|
||||
.read(
|
||||
mangaFilterBookmarkedStateProvider(
|
||||
isManga: widget
|
||||
.isManga,
|
||||
mangaList:
|
||||
_entries,
|
||||
settings:
|
||||
settings)
|
||||
.notifier)
|
||||
.update();
|
||||
});
|
||||
}),
|
||||
],
|
||||
);
|
||||
}),
|
||||
Consumer(builder: (context, ref, chil) {
|
||||
final reverse = ref
|
||||
.read(sortLibraryMangaStateProvider(
|
||||
isManga: widget.isManga,
|
||||
settings: settings)
|
||||
.notifier)
|
||||
.isReverse();
|
||||
final reverseChapter = ref.watch(
|
||||
sortLibraryMangaStateProvider(
|
||||
isManga: widget.isManga,
|
||||
settings: settings));
|
||||
return Column(
|
||||
children: [
|
||||
for (var i = 0; i < 7; i++)
|
||||
ListTileChapterSort(
|
||||
label:
|
||||
_getSortNameByIndex(i, context),
|
||||
reverse: reverse,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(
|
||||
sortLibraryMangaStateProvider(
|
||||
isManga:
|
||||
widget.isManga,
|
||||
settings: settings)
|
||||
.notifier)
|
||||
.set(i);
|
||||
},
|
||||
showLeading:
|
||||
reverseChapter.index == i,
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
Consumer(builder: (context, ref, chil) {
|
||||
final display = ref.watch(
|
||||
libraryDisplayTypeStateProvider(
|
||||
isManga: widget.isManga,
|
||||
settings: settings));
|
||||
final displayV = ref.read(
|
||||
libraryDisplayTypeStateProvider(
|
||||
isManga: widget.isManga,
|
||||
settings: settings)
|
||||
.notifier);
|
||||
final showCategoryTabs = ref.watch(
|
||||
libraryShowCategoryTabsStateProvider(
|
||||
isManga: widget.isManga,
|
||||
settings: settings));
|
||||
final continueReaderBtn = ref.watch(
|
||||
libraryShowContinueReadingButtonStateProvider(
|
||||
isManga: widget.isManga,
|
||||
settings: settings));
|
||||
final showNumbersOfItems = ref.watch(
|
||||
libraryShowNumbersOfItemsStateProvider(
|
||||
isManga: widget.isManga,
|
||||
settings: settings));
|
||||
final downloadedChapter = ref.watch(
|
||||
libraryDownloadedChaptersStateProvider(
|
||||
isManga: widget.isManga,
|
||||
settings: settings));
|
||||
final language = ref.watch(
|
||||
libraryLanguageStateProvider(
|
||||
isManga: widget.isManga,
|
||||
settings: settings));
|
||||
final localSource = ref.watch(
|
||||
libraryLocalSourceStateProvider(
|
||||
isManga: widget.isManga,
|
||||
settings: settings));
|
||||
return SingleChildScrollView(
|
||||
physics:
|
||||
const NeverScrollableScrollPhysics(),
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 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 ==
|
||||
displayV
|
||||
.getLibraryDisplayTypeValue(
|
||||
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
|
||||
.withOpacity(0.2)
|
||||
: Colors.transparent),
|
||||
onPressed: () {
|
||||
displayV
|
||||
.setLibraryDisplayType(
|
||||
e);
|
||||
},
|
||||
child: Text(
|
||||
displayV
|
||||
.getLibraryDisplayTypeName(
|
||||
e.name, context),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.color,
|
||||
fontSize: 14),
|
||||
)),
|
||||
);
|
||||
}
|
||||
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(
|
||||
isManga: widget.isManga,
|
||||
mangaList: _entries,
|
||||
settings: settings)),
|
||||
onTap: () {
|
||||
ref
|
||||
.read(mangaFilterDownloadedStateProvider(
|
||||
isManga: widget.isManga,
|
||||
mangaList: _entries,
|
||||
settings: settings)
|
||||
.notifier)
|
||||
.update();
|
||||
}),
|
||||
ListTileChapterFilter(
|
||||
label: l10n.unread,
|
||||
type: ref.watch(mangaFilterUnreadStateProvider(
|
||||
isManga: widget.isManga,
|
||||
mangaList: _entries,
|
||||
settings: settings)),
|
||||
onTap: () {
|
||||
ref
|
||||
.read(mangaFilterUnreadStateProvider(
|
||||
isManga: widget.isManga,
|
||||
mangaList: _entries,
|
||||
settings: settings)
|
||||
.notifier)
|
||||
.update();
|
||||
}),
|
||||
ListTileChapterFilter(
|
||||
label: l10n.started,
|
||||
type: ref.watch(mangaFilterStartedStateProvider(
|
||||
isManga: widget.isManga,
|
||||
mangaList: _entries,
|
||||
settings: settings)),
|
||||
onTap: () {
|
||||
ref
|
||||
.read(mangaFilterStartedStateProvider(
|
||||
isManga: widget.isManga,
|
||||
mangaList: _entries,
|
||||
settings: settings)
|
||||
.notifier)
|
||||
.update();
|
||||
}),
|
||||
ListTileChapterFilter(
|
||||
label: l10n.bookmarked,
|
||||
type: ref.watch(mangaFilterBookmarkedStateProvider(
|
||||
isManga: widget.isManga,
|
||||
mangaList: _entries,
|
||||
settings: settings)),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
ref
|
||||
.read(mangaFilterBookmarkedStateProvider(
|
||||
isManga: widget.isManga,
|
||||
mangaList: _entries,
|
||||
settings: settings)
|
||||
.notifier)
|
||||
.update();
|
||||
});
|
||||
}),
|
||||
],
|
||||
);
|
||||
}),
|
||||
Consumer(builder: (context, ref, chil) {
|
||||
final reverse = ref
|
||||
.read(sortLibraryMangaStateProvider(
|
||||
isManga: widget.isManga, settings: settings)
|
||||
.notifier)
|
||||
.isReverse();
|
||||
final reverseChapter = ref.watch(sortLibraryMangaStateProvider(
|
||||
isManga: widget.isManga, settings: settings));
|
||||
return Column(
|
||||
children: [
|
||||
for (var i = 0; i < 7; i++)
|
||||
ListTileChapterSort(
|
||||
label: _getSortNameByIndex(i, context),
|
||||
reverse: reverse,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(sortLibraryMangaStateProvider(
|
||||
isManga: widget.isManga, settings: settings)
|
||||
.notifier)
|
||||
.set(i);
|
||||
},
|
||||
showLeading: reverseChapter.index == i,
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
Consumer(builder: (context, ref, chil) {
|
||||
final display = ref.watch(libraryDisplayTypeStateProvider(
|
||||
isManga: widget.isManga, settings: settings));
|
||||
final displayV = ref.read(libraryDisplayTypeStateProvider(
|
||||
isManga: widget.isManga, settings: settings)
|
||||
.notifier);
|
||||
final showCategoryTabs = ref.watch(libraryShowCategoryTabsStateProvider(
|
||||
isManga: widget.isManga, settings: settings));
|
||||
final continueReaderBtn = ref.watch(
|
||||
libraryShowContinueReadingButtonStateProvider(
|
||||
isManga: widget.isManga, settings: settings));
|
||||
final showNumbersOfItems = ref.watch(
|
||||
libraryShowNumbersOfItemsStateProvider(
|
||||
isManga: widget.isManga, settings: settings));
|
||||
final downloadedChapter = ref.watch(
|
||||
libraryDownloadedChaptersStateProvider(
|
||||
isManga: widget.isManga, settings: settings));
|
||||
final language = ref.watch(libraryLanguageStateProvider(
|
||||
isManga: widget.isManga, settings: settings));
|
||||
final localSource = ref.watch(libraryLocalSourceStateProvider(
|
||||
isManga: widget.isManga, settings: settings));
|
||||
return SingleChildScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 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 == displayV.getLibraryDisplayTypeValue(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.withOpacity(0.2)
|
||||
: Colors.transparent),
|
||||
onPressed: () {
|
||||
displayV.setLibraryDisplayType(e);
|
||||
},
|
||||
child: Text(
|
||||
displayV.getLibraryDisplayTypeName(e.name, 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()),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 20, top: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(l10n.badges),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 5),
|
||||
child: Column(
|
||||
children: [
|
||||
ListTileChapterFilter(
|
||||
label:
|
||||
l10n.downloaded_chapters,
|
||||
type:
|
||||
downloadedChapter ? 1 : 0,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(libraryDownloadedChaptersStateProvider(
|
||||
isManga: widget
|
||||
.isManga,
|
||||
settings:
|
||||
settings)
|
||||
.notifier)
|
||||
.set(
|
||||
!downloadedChapter);
|
||||
}),
|
||||
ListTileChapterFilter(
|
||||
label: l10n.language,
|
||||
type: language ? 1 : 0,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(libraryLanguageStateProvider(
|
||||
isManga: widget
|
||||
.isManga,
|
||||
settings:
|
||||
settings)
|
||||
.notifier)
|
||||
.set(!language);
|
||||
}),
|
||||
ListTileChapterFilter(
|
||||
label: l10n.local_source,
|
||||
type: localSource ? 1 : 0,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(libraryLocalSourceStateProvider(
|
||||
isManga: widget
|
||||
.isManga,
|
||||
settings:
|
||||
settings)
|
||||
.notifier)
|
||||
.set(!localSource);
|
||||
}),
|
||||
ListTileChapterFilter(
|
||||
label: l10n
|
||||
.show_continue_reading_buttons,
|
||||
type:
|
||||
continueReaderBtn ? 1 : 0,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(libraryShowContinueReadingButtonStateProvider(
|
||||
isManga: widget
|
||||
.isManga,
|
||||
settings:
|
||||
settings)
|
||||
.notifier)
|
||||
.set(
|
||||
!continueReaderBtn);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 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(
|
||||
isManga: widget
|
||||
.isManga,
|
||||
settings:
|
||||
settings)
|
||||
.notifier)
|
||||
.set(!showCategoryTabs);
|
||||
}),
|
||||
ListTileChapterFilter(
|
||||
label: l10n
|
||||
.show_numbers_of_items,
|
||||
type: showNumbersOfItems
|
||||
? 1
|
||||
: 0,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(libraryShowNumbersOfItemsStateProvider(
|
||||
isManga: widget
|
||||
.isManga,
|
||||
settings:
|
||||
settings)
|
||||
.notifier)
|
||||
.set(
|
||||
!showNumbersOfItems);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
))));
|
||||
// RadioListTile<
|
||||
// DisplayType>(
|
||||
// dense: true,
|
||||
// title: ,
|
||||
// value: e,
|
||||
// groupValue: displayV
|
||||
// .getLibraryDisplayTypeValue(
|
||||
// display),
|
||||
// selected: true,
|
||||
// onChanged: (value) {
|
||||
// displayV
|
||||
// .setLibraryDisplayType(
|
||||
// value!);
|
||||
// },
|
||||
// ),
|
||||
).toList()),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 20, top: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(l10n.badges),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 5),
|
||||
child: Column(
|
||||
children: [
|
||||
ListTileChapterFilter(
|
||||
label: l10n.downloaded_chapters,
|
||||
type: downloadedChapter ? 1 : 0,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(libraryDownloadedChaptersStateProvider(
|
||||
isManga: widget.isManga,
|
||||
settings: settings)
|
||||
.notifier)
|
||||
.set(!downloadedChapter);
|
||||
}),
|
||||
ListTileChapterFilter(
|
||||
label: l10n.language,
|
||||
type: language ? 1 : 0,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(libraryLanguageStateProvider(
|
||||
isManga: widget.isManga,
|
||||
settings: settings)
|
||||
.notifier)
|
||||
.set(!language);
|
||||
}),
|
||||
ListTileChapterFilter(
|
||||
label: l10n.local_source,
|
||||
type: localSource ? 1 : 0,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(libraryLocalSourceStateProvider(
|
||||
isManga: widget.isManga,
|
||||
settings: settings)
|
||||
.notifier)
|
||||
.set(!localSource);
|
||||
}),
|
||||
ListTileChapterFilter(
|
||||
label: l10n.show_continue_reading_buttons,
|
||||
type: continueReaderBtn ? 1 : 0,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(
|
||||
libraryShowContinueReadingButtonStateProvider(
|
||||
isManga: widget.isManga,
|
||||
settings: settings)
|
||||
.notifier)
|
||||
.set(!continueReaderBtn);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 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(
|
||||
isManga: widget.isManga,
|
||||
settings: settings)
|
||||
.notifier)
|
||||
.set(!showCategoryTabs);
|
||||
}),
|
||||
ListTileChapterFilter(
|
||||
label: l10n.show_numbers_of_items,
|
||||
type: showNumbersOfItems ? 1 : 0,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(libraryShowNumbersOfItemsStateProvider(
|
||||
isManga: widget.isManga,
|
||||
settings: settings)
|
||||
.notifier)
|
||||
.set(!showNumbersOfItems);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
], context: context, vsync: this);
|
||||
}
|
||||
|
||||
String _getSortNameByIndex(int index, BuildContext context) {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import 'package:mangayomi/modules/manga/detail/widgets/tracker_widget.dart';
|
|||
import 'package:mangayomi/modules/manga/reader/providers/reader_controller_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/custom_draggable_tabbar.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/providers/storage_provider.dart';
|
||||
import 'package:mangayomi/services/get_source_baseurl.dart';
|
||||
|
|
@ -931,282 +932,236 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
|
|||
}
|
||||
|
||||
void _showDraggableMenu() {
|
||||
late TabController tabBarController;
|
||||
tabBarController = TabController(length: 3, vsync: this);
|
||||
tabBarController.animateTo(0);
|
||||
DraggableMenu.open(
|
||||
context,
|
||||
Consumer(builder: (context, ref, child) {
|
||||
final scanlators =
|
||||
ref.watch(scanlatorsFilterStateProvider(widget.manga!));
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
return DraggableMenu(
|
||||
ui: ClassicDraggableMenu(barItem: Container(), radius: 20),
|
||||
levels: [
|
||||
DraggableMenuLevel.ratio(
|
||||
ratio: scanlators.$1.isEmpty ? 1 / 3.6 : 1 / 3.3),
|
||||
DraggableMenuLevel.ratio(ratio: 2 / 3),
|
||||
],
|
||||
fastDrag: false,
|
||||
minimizeBeforeFastDrag: false,
|
||||
child: DefaultTabController(
|
||||
length: 3,
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Theme.of(context).scaffoldBackgroundColor),
|
||||
child: Column(
|
||||
children: [
|
||||
TabBar(
|
||||
controller: tabBarController,
|
||||
tabs: [
|
||||
Tab(text: l10n.filter),
|
||||
Tab(text: l10n.sort),
|
||||
Tab(text: l10n.display),
|
||||
],
|
||||
),
|
||||
Flexible(
|
||||
child: TabBarView(
|
||||
controller: tabBarController,
|
||||
children: [
|
||||
Consumer(builder: (context, ref, chil) {
|
||||
return Column(
|
||||
children: [
|
||||
if (!isLocalArchive)
|
||||
ListTileChapterFilter(
|
||||
label: l10n.downloaded,
|
||||
type: ref.watch(
|
||||
chapterFilterDownloadedStateProvider(
|
||||
mangaId: widget.manga!.id!)),
|
||||
onTap: () {
|
||||
ref
|
||||
.read(
|
||||
chapterFilterDownloadedStateProvider(
|
||||
mangaId: widget
|
||||
.manga!.id!)
|
||||
.notifier)
|
||||
.update();
|
||||
}),
|
||||
ListTileChapterFilter(
|
||||
label: l10n.unread,
|
||||
type: ref.watch(
|
||||
chapterFilterUnreadStateProvider(
|
||||
mangaId: widget.manga!.id!)),
|
||||
onTap: () {
|
||||
ref
|
||||
.read(
|
||||
chapterFilterUnreadStateProvider(
|
||||
mangaId:
|
||||
widget.manga!.id!)
|
||||
.notifier)
|
||||
.update();
|
||||
}),
|
||||
ListTileChapterFilter(
|
||||
label: l10n.bookmarked,
|
||||
type: ref.watch(
|
||||
chapterFilterBookmarkedStateProvider(
|
||||
mangaId: widget.manga!.id!)),
|
||||
onTap: () {
|
||||
ref
|
||||
.read(
|
||||
chapterFilterBookmarkedStateProvider(
|
||||
mangaId:
|
||||
widget.manga!.id!)
|
||||
.notifier)
|
||||
.update();
|
||||
}),
|
||||
if (scanlators.$1.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 18),
|
||||
child: Row(
|
||||
final scanlators = ref.watch(scanlatorsFilterStateProvider(widget.manga!));
|
||||
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: [
|
||||
if (!isLocalArchive)
|
||||
ListTileChapterFilter(
|
||||
label: l10n.downloaded,
|
||||
type: ref.watch(chapterFilterDownloadedStateProvider(
|
||||
mangaId: widget.manga!.id!)),
|
||||
onTap: () {
|
||||
ref
|
||||
.read(chapterFilterDownloadedStateProvider(
|
||||
mangaId: widget.manga!.id!)
|
||||
.notifier)
|
||||
.update();
|
||||
}),
|
||||
ListTileChapterFilter(
|
||||
label: l10n.unread,
|
||||
type: ref.watch(chapterFilterUnreadStateProvider(
|
||||
mangaId: widget.manga!.id!)),
|
||||
onTap: () {
|
||||
ref
|
||||
.read(chapterFilterUnreadStateProvider(
|
||||
mangaId: widget.manga!.id!)
|
||||
.notifier)
|
||||
.update();
|
||||
}),
|
||||
ListTileChapterFilter(
|
||||
label: l10n.bookmarked,
|
||||
type: ref.watch(chapterFilterBookmarkedStateProvider(
|
||||
mangaId: widget.manga!.id!)),
|
||||
onTap: () {
|
||||
ref
|
||||
.read(chapterFilterBookmarkedStateProvider(
|
||||
mangaId: widget.manga!.id!)
|
||||
.notifier)
|
||||
.update();
|
||||
}),
|
||||
if (scanlators.$1.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final scanlators = ref.watch(
|
||||
scanlatorsFilterStateProvider(
|
||||
widget.manga!));
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
l10n.filter_scanlator_groups,
|
||||
),
|
||||
content: SizedBox(
|
||||
width: context.mediaWidth(0.8),
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: scanlators.$1.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ListTileChapterFilter(
|
||||
label: scanlators.$1[index],
|
||||
type: scanlators.$3.contains(
|
||||
scanlators.$1[index])
|
||||
? 2
|
||||
: 0,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(
|
||||
scanlatorsFilterStateProvider(
|
||||
widget
|
||||
.manga!)
|
||||
.notifier)
|
||||
.setFilteredList(
|
||||
scanlators
|
||||
.$1[index]);
|
||||
});
|
||||
},
|
||||
)),
|
||||
actions: [
|
||||
Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return Consumer(
|
||||
builder: (context,
|
||||
ref, child) {
|
||||
final scanlators =
|
||||
ref.watch(
|
||||
scanlatorsFilterStateProvider(
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(scanlatorsFilterStateProvider(
|
||||
widget
|
||||
.manga!));
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
l10n.filter_scanlator_groups,
|
||||
),
|
||||
content: SizedBox(
|
||||
width: context
|
||||
.mediaWidth(
|
||||
0.8),
|
||||
child: ListView
|
||||
.builder(
|
||||
shrinkWrap:
|
||||
true,
|
||||
itemCount:
|
||||
scanlators
|
||||
.$1
|
||||
.length,
|
||||
itemBuilder:
|
||||
(context,
|
||||
index) {
|
||||
return ListTileChapterFilter(
|
||||
label: scanlators.$1[
|
||||
index],
|
||||
type: scanlators.$3.contains(scanlators.$1[index])
|
||||
? 2
|
||||
: 0,
|
||||
onTap:
|
||||
() {
|
||||
ref.read(scanlatorsFilterStateProvider(widget.manga!).notifier).setFilteredList(scanlators.$1[index]);
|
||||
});
|
||||
},
|
||||
)),
|
||||
actions: [
|
||||
Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child:
|
||||
Row(
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
ref.read(scanlatorsFilterStateProvider(widget.manga!).notifier).set([]);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
l10n.reset,
|
||||
style: TextStyle(color: context.primaryColor),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
l10n.cancel,
|
||||
style: TextStyle(color: context.primaryColor),
|
||||
)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
ref.read(scanlatorsFilterStateProvider(widget.manga!).notifier).set(scanlators.$3);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
l10n.filter,
|
||||
style: TextStyle(color: context.primaryColor),
|
||||
)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
child: Text(l10n
|
||||
.filter_scanlator_groups)),
|
||||
.manga!)
|
||||
.notifier)
|
||||
.set([]);
|
||||
Navigator.pop(
|
||||
context);
|
||||
},
|
||||
child: Text(
|
||||
l10n.reset,
|
||||
style: TextStyle(
|
||||
color: context
|
||||
.primaryColor),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment
|
||||
.spaceBetween,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(
|
||||
context);
|
||||
},
|
||||
child: Text(
|
||||
l10n.cancel,
|
||||
style: TextStyle(
|
||||
color: context
|
||||
.primaryColor),
|
||||
)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(scanlatorsFilterStateProvider(
|
||||
widget
|
||||
.manga!)
|
||||
.notifier)
|
||||
.set(scanlators
|
||||
.$3);
|
||||
Navigator.pop(
|
||||
context);
|
||||
},
|
||||
child: Text(
|
||||
l10n.filter,
|
||||
style: TextStyle(
|
||||
color: context
|
||||
.primaryColor),
|
||||
)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
Consumer(builder: (context, ref, chil) {
|
||||
final reverse = ref
|
||||
.read(sortChapterStateProvider(
|
||||
mangaId: widget.manga!.id!)
|
||||
.notifier)
|
||||
.isReverse();
|
||||
final scanlators = ref.watch(
|
||||
scanlatorsFilterStateProvider(
|
||||
widget.manga!));
|
||||
final reverseChapter = ref.watch(
|
||||
sortChapterStateProvider(
|
||||
mangaId: widget.manga!.id!));
|
||||
return Column(
|
||||
children: [
|
||||
if (scanlators.$1.isNotEmpty)
|
||||
ListTileChapterSort(
|
||||
label: _getSortNameByIndex(0, context),
|
||||
reverse: reverse,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(sortChapterStateProvider(
|
||||
mangaId:
|
||||
widget.manga!.id!)
|
||||
.notifier)
|
||||
.set(0);
|
||||
},
|
||||
showLeading: reverseChapter.index == 0,
|
||||
),
|
||||
for (var i = 1; i < 4; i++)
|
||||
ListTileChapterSort(
|
||||
label: _getSortNameByIndex(i, context),
|
||||
reverse: reverse,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(sortChapterStateProvider(
|
||||
mangaId:
|
||||
widget.manga!.id!)
|
||||
.notifier)
|
||||
.set(i);
|
||||
},
|
||||
showLeading: reverseChapter.index == i,
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
Consumer(builder: (context, ref, chil) {
|
||||
return Column(
|
||||
children: [
|
||||
RadioListTile(
|
||||
dense: true,
|
||||
title: Text(l10n.source_title),
|
||||
value: "e",
|
||||
groupValue: "e",
|
||||
selected: true,
|
||||
onChanged: (value) {},
|
||||
),
|
||||
RadioListTile(
|
||||
dense: true,
|
||||
title: Text(l10n.chapter_number),
|
||||
value: "ej",
|
||||
groupValue: "e",
|
||||
selected: false,
|
||||
onChanged: (value) {},
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
child: Text(l10n.filter_scanlator_groups)),
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
barrier: true,
|
||||
);
|
||||
Consumer(builder: (context, ref, chil) {
|
||||
final reverse = ref
|
||||
.read(sortChapterStateProvider(mangaId: widget.manga!.id!).notifier)
|
||||
.isReverse();
|
||||
final scanlators =
|
||||
ref.watch(scanlatorsFilterStateProvider(widget.manga!));
|
||||
final reverseChapter =
|
||||
ref.watch(sortChapterStateProvider(mangaId: widget.manga!.id!));
|
||||
return Column(
|
||||
children: [
|
||||
if (scanlators.$1.isNotEmpty)
|
||||
ListTileChapterSort(
|
||||
label: _getSortNameByIndex(0, context),
|
||||
reverse: reverse,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(sortChapterStateProvider(mangaId: widget.manga!.id!)
|
||||
.notifier)
|
||||
.set(0);
|
||||
},
|
||||
showLeading: reverseChapter.index == 0,
|
||||
),
|
||||
for (var i = 1; i < 4; i++)
|
||||
ListTileChapterSort(
|
||||
label: _getSortNameByIndex(i, context),
|
||||
reverse: reverse,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(sortChapterStateProvider(mangaId: widget.manga!.id!)
|
||||
.notifier)
|
||||
.set(i);
|
||||
},
|
||||
showLeading: reverseChapter.index == i,
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
Consumer(builder: (context, ref, chil) {
|
||||
return Column(
|
||||
children: [
|
||||
RadioListTile(
|
||||
dense: true,
|
||||
title: Text(l10n.source_title),
|
||||
value: "e",
|
||||
groupValue: "e",
|
||||
selected: true,
|
||||
onChanged: (value) {},
|
||||
),
|
||||
RadioListTile(
|
||||
dense: true,
|
||||
title: Text(l10n.chapter_number),
|
||||
value: "ej",
|
||||
groupValue: "e",
|
||||
selected: false,
|
||||
onChanged: (value) {},
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
], context: context, vsync: this);
|
||||
}
|
||||
|
||||
String _getSortNameByIndex(int index, BuildContext context) {
|
||||
|
|
@ -1935,6 +1890,7 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
|
|||
radius: 20,
|
||||
barItem: Container(),
|
||||
color: Theme.of(context).scaffoldBackgroundColor),
|
||||
allowToShrink: true,
|
||||
child: Material(
|
||||
color: context.isLight
|
||||
? Theme.of(context).scaffoldBackgroundColor.withOpacity(0.9)
|
||||
|
|
@ -1961,7 +1917,6 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
|
|||
builder: (context, snapshot) {
|
||||
List<Track>? trackRes =
|
||||
snapshot.hasData ? snapshot.data : [];
|
||||
|
||||
return trackRes!.isNotEmpty
|
||||
? TrackerWidget(
|
||||
mangaId: widget.manga!.id!,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'dart:io';
|
||||
import 'package:draggable_menu/draggable_menu.dart';
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
|
@ -22,6 +21,7 @@ import 'package:mangayomi/modules/manga/reader/providers/color_filter_provider.d
|
|||
import 'package:mangayomi/modules/manga/reader/providers/crop_borders_provider.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/widgets/color_filter_widget.dart';
|
||||
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
|
||||
import 'package:mangayomi/modules/widgets/custom_draggable_tabbar.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/providers/storage_provider.dart';
|
||||
import 'package:mangayomi/sources/utils/utils.dart';
|
||||
|
|
@ -1960,387 +1960,292 @@ class _MangaChapterPageGalleryState
|
|||
void _showModalSettings() async {
|
||||
_autoScroll.value = false;
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
late TabController tabBarController;
|
||||
tabBarController = TabController(length: 3, vsync: this);
|
||||
await DraggableMenu.open(
|
||||
context,
|
||||
DraggableMenu(
|
||||
ui: const ClassicDraggableMenu(
|
||||
barItem: SizedBox.shrink(), radius: 20),
|
||||
minimizeThreshold: 0.6,
|
||||
levels: [
|
||||
DraggableMenuLevel.ratio(ratio: 1.5 / 3),
|
||||
DraggableMenuLevel.ratio(ratio: 1),
|
||||
],
|
||||
minimizeBeforeFastDrag: true,
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Theme.of(context).scaffoldBackgroundColor),
|
||||
child: Column(
|
||||
children: [
|
||||
TabBar(
|
||||
controller: tabBarController,
|
||||
tabs: [
|
||||
Tab(text: l10n.reading_mode),
|
||||
Tab(text: l10n.general),
|
||||
Tab(text: l10n.custom_filter),
|
||||
],
|
||||
),
|
||||
Flexible(
|
||||
child: TabBarView(
|
||||
controller: tabBarController,
|
||||
children: [
|
||||
Consumer(builder: (context, ref, chil) {
|
||||
final readerMode = ref.watch(_currentReaderMode);
|
||||
final usePageTapZones =
|
||||
ref.watch(usePageTapZonesStateProvider);
|
||||
final cropBorders =
|
||||
ref.watch(cropBordersStateProvider);
|
||||
await customDraggableTabBar(tabs: [
|
||||
Tab(text: l10n.reading_mode),
|
||||
Tab(text: l10n.general),
|
||||
Tab(text: l10n.custom_filter),
|
||||
], children: [
|
||||
Consumer(builder: (context, ref, chil) {
|
||||
final readerMode = ref.watch(_currentReaderMode);
|
||||
final usePageTapZones = ref.watch(usePageTapZonesStateProvider);
|
||||
final cropBorders = ref.watch(cropBordersStateProvider);
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 20),
|
||||
child: Column(
|
||||
children: [
|
||||
CustomPopupMenuButton<ReaderMode>(
|
||||
label: l10n.reading_mode,
|
||||
title: getReaderModeName(
|
||||
readerMode!, context),
|
||||
onSelected: (value) {
|
||||
ref
|
||||
.read(_currentReaderMode.notifier)
|
||||
.state = value;
|
||||
_setReaderMode(value, ref);
|
||||
},
|
||||
value: readerMode,
|
||||
list: ReaderMode.values,
|
||||
itemText: (mode) {
|
||||
return getReaderModeName(mode, context);
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
value: cropBorders,
|
||||
title: Text(
|
||||
l10n.crop_borders,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.color!
|
||||
.withOpacity(0.9),
|
||||
fontSize: 14),
|
||||
),
|
||||
onChanged: (value) {
|
||||
ref
|
||||
.read(cropBordersStateProvider
|
||||
.notifier)
|
||||
.set(value);
|
||||
}),
|
||||
SwitchListTile(
|
||||
value: usePageTapZones,
|
||||
title: Text(l10n.use_page_tap_zones,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.color!
|
||||
.withOpacity(0.9),
|
||||
fontSize: 14)),
|
||||
onChanged: (value) {
|
||||
ref
|
||||
.read(usePageTapZonesStateProvider
|
||||
.notifier)
|
||||
.set(value);
|
||||
}),
|
||||
if (readerMode ==
|
||||
ReaderMode.verticalContinuous ||
|
||||
readerMode == ReaderMode.webtoon)
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _autoScrollPage,
|
||||
builder: (context, valueT, child) {
|
||||
return Column(
|
||||
children: [
|
||||
SwitchListTile(
|
||||
secondary: Icon(valueT
|
||||
? Icons.timer
|
||||
: Icons.timer_outlined),
|
||||
value: valueT,
|
||||
title: Text(
|
||||
context.l10n.auto_scroll,
|
||||
style: TextStyle(
|
||||
color: Theme.of(
|
||||
context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.color!
|
||||
.withOpacity(0.9),
|
||||
fontSize: 14)),
|
||||
onChanged: (val) {
|
||||
_readerController
|
||||
.setAutoScroll(val,
|
||||
_pageOffset.value);
|
||||
_autoScrollPage.value = val;
|
||||
_autoScroll.value = val;
|
||||
}),
|
||||
if (valueT)
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _pageOffset,
|
||||
builder: (context, value,
|
||||
child) =>
|
||||
Slider(
|
||||
min: 2.0,
|
||||
max: 30.0,
|
||||
divisions: max(28, 3),
|
||||
value: value,
|
||||
onChanged: (val) {
|
||||
_pageOffset.value =
|
||||
val;
|
||||
},
|
||||
onChangeEnd: (val) {
|
||||
_readerController
|
||||
.setAutoScroll(
|
||||
valueT,
|
||||
val);
|
||||
}),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
Consumer(builder: (context, ref, chil) {
|
||||
final showPageNumber = ref.watch(_showPagesNumber);
|
||||
final animatePageTransitions =
|
||||
ref.watch(animatePageTransitionsStateProvider);
|
||||
final scaleType = ref.watch(scaleTypeStateProvider);
|
||||
final fullScreenReader =
|
||||
ref.watch(fullScreenReaderStateProvider);
|
||||
final backgroundColor =
|
||||
ref.watch(backgroundColorStateProvider);
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CustomPopupMenuButton<BackgroundColor>(
|
||||
label: l10n.background_color,
|
||||
title: getBackgroundColorName(
|
||||
backgroundColor, context),
|
||||
onSelected: (value) {
|
||||
ref
|
||||
.read(backgroundColorStateProvider
|
||||
.notifier)
|
||||
.set(value);
|
||||
},
|
||||
value: backgroundColor,
|
||||
list: BackgroundColor.values,
|
||||
itemText: (color) {
|
||||
return getBackgroundColorName(
|
||||
color, context);
|
||||
},
|
||||
),
|
||||
CustomPopupMenuButton<ScaleType>(
|
||||
label: l10n.scale_type,
|
||||
title: getScaleTypeNames(
|
||||
context)[scaleType.index],
|
||||
onSelected: (value) {
|
||||
ref
|
||||
.read(
|
||||
scaleTypeStateProvider.notifier)
|
||||
.set(ScaleType.values[value.index]);
|
||||
},
|
||||
value: scaleType,
|
||||
list: ScaleType.values.where((scale) {
|
||||
try {
|
||||
return getScaleTypeNames(context)
|
||||
.contains(getScaleTypeNames(
|
||||
context)[scale.index]);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}).toList(),
|
||||
itemText: (scale) {
|
||||
return getScaleTypeNames(
|
||||
context)[scale.index];
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
value: fullScreenReader,
|
||||
title: Text(
|
||||
l10n.fullscreen,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.color!
|
||||
.withOpacity(0.9),
|
||||
fontSize: 14),
|
||||
),
|
||||
onChanged: (value) {
|
||||
_setFullScreen(value: value);
|
||||
}),
|
||||
SwitchListTile(
|
||||
value: showPageNumber,
|
||||
title: Text(
|
||||
l10n.show_page_number,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.color!
|
||||
.withOpacity(0.9),
|
||||
fontSize: 14),
|
||||
),
|
||||
onChanged: (value) {
|
||||
ref
|
||||
.read(_showPagesNumber.notifier)
|
||||
.state = value;
|
||||
_readerController
|
||||
.setShowPageNumber(value);
|
||||
}),
|
||||
SwitchListTile(
|
||||
value: animatePageTransitions,
|
||||
title: Text(
|
||||
l10n.animate_page_transitions,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.color!
|
||||
.withOpacity(0.9),
|
||||
fontSize: 14),
|
||||
),
|
||||
onChanged: (value) {
|
||||
ref
|
||||
.read(
|
||||
animatePageTransitionsStateProvider
|
||||
.notifier)
|
||||
.set(value);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
Consumer(builder: (context, ref, chil) {
|
||||
final customColorFilter =
|
||||
ref.watch(customColorFilterStateProvider);
|
||||
final enableCustomColorFilter =
|
||||
ref.watch(enableCustomColorFilterStateProvider);
|
||||
int r = customColorFilter?.r ?? 0;
|
||||
int g = customColorFilter?.g ?? 0;
|
||||
int b = customColorFilter?.b ?? 0;
|
||||
int a = customColorFilter?.a ?? 0;
|
||||
final colorFilterBlendMode =
|
||||
ref.watch(colorFilterBlendModeStateProvider);
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ExpansionTile(
|
||||
title: Text(
|
||||
l10n.custom_color_filter,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
shape: const StarBorder(),
|
||||
initiallyExpanded:
|
||||
enableCustomColorFilter,
|
||||
trailing: IgnorePointer(
|
||||
child: Switch(
|
||||
value: enableCustomColorFilter,
|
||||
onChanged: (_) {},
|
||||
),
|
||||
),
|
||||
onExpansionChanged: (val) {
|
||||
ref
|
||||
.read(
|
||||
enableCustomColorFilterStateProvider
|
||||
.notifier)
|
||||
.set(val);
|
||||
},
|
||||
children: [
|
||||
customColorFilterListTile(
|
||||
isDesktop, "r", r, (val) {
|
||||
ref
|
||||
.read(
|
||||
customColorFilterStateProvider
|
||||
.notifier)
|
||||
.set(a, val.$1.toInt(), g, b,
|
||||
val.$2);
|
||||
}, context),
|
||||
customColorFilterListTile(
|
||||
isDesktop, "g", g, (val) {
|
||||
ref
|
||||
.read(
|
||||
customColorFilterStateProvider
|
||||
.notifier)
|
||||
.set(a, r, val.$1.toInt(), b,
|
||||
val.$2);
|
||||
}, context),
|
||||
customColorFilterListTile(
|
||||
isDesktop, "b", b, (val) {
|
||||
ref
|
||||
.read(
|
||||
customColorFilterStateProvider
|
||||
.notifier)
|
||||
.set(a, r, g, val.$1.toInt(),
|
||||
val.$2);
|
||||
}, context),
|
||||
customColorFilterListTile(
|
||||
isDesktop, "a", a, (val) {
|
||||
ref
|
||||
.read(
|
||||
customColorFilterStateProvider
|
||||
.notifier)
|
||||
.set(val.$1.toInt(), r, g, b,
|
||||
val.$2);
|
||||
}, context),
|
||||
CustomPopupMenuButton<
|
||||
ColorFilterBlendMode>(
|
||||
label: l10n.color_filter_blend_mode,
|
||||
title: getColorFilterBlendModeName(
|
||||
colorFilterBlendMode, context),
|
||||
onSelected: (value) {
|
||||
ref
|
||||
.read(
|
||||
colorFilterBlendModeStateProvider
|
||||
.notifier)
|
||||
.set(value);
|
||||
},
|
||||
value: colorFilterBlendMode,
|
||||
list: ColorFilterBlendMode.values,
|
||||
itemText: (va) {
|
||||
return getColorFilterBlendModeName(
|
||||
va, context);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
child: Column(
|
||||
children: [
|
||||
CustomPopupMenuButton<ReaderMode>(
|
||||
label: l10n.reading_mode,
|
||||
title: getReaderModeName(readerMode!, context),
|
||||
onSelected: (value) {
|
||||
ref.read(_currentReaderMode.notifier).state = value;
|
||||
_setReaderMode(value, ref);
|
||||
},
|
||||
value: readerMode,
|
||||
list: ReaderMode.values,
|
||||
itemText: (mode) {
|
||||
return getReaderModeName(mode, context);
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
value: cropBorders,
|
||||
title: Text(
|
||||
l10n.crop_borders,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.color!
|
||||
.withOpacity(0.9),
|
||||
fontSize: 14),
|
||||
),
|
||||
onChanged: (value) {
|
||||
ref.read(cropBordersStateProvider.notifier).set(value);
|
||||
}),
|
||||
SwitchListTile(
|
||||
value: usePageTapZones,
|
||||
title: Text(l10n.use_page_tap_zones,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.color!
|
||||
.withOpacity(0.9),
|
||||
fontSize: 14)),
|
||||
onChanged: (value) {
|
||||
ref
|
||||
.read(usePageTapZonesStateProvider.notifier)
|
||||
.set(value);
|
||||
}),
|
||||
if (readerMode == ReaderMode.verticalContinuous ||
|
||||
readerMode == ReaderMode.webtoon)
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _autoScrollPage,
|
||||
builder: (context, valueT, child) {
|
||||
return Column(
|
||||
children: [
|
||||
SwitchListTile(
|
||||
secondary: Icon(
|
||||
valueT ? Icons.timer : Icons.timer_outlined),
|
||||
value: valueT,
|
||||
title: Text(context.l10n.auto_scroll,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.color!
|
||||
.withOpacity(0.9),
|
||||
fontSize: 14)),
|
||||
onChanged: (val) {
|
||||
_readerController.setAutoScroll(
|
||||
val, _pageOffset.value);
|
||||
_autoScrollPage.value = val;
|
||||
_autoScroll.value = val;
|
||||
}),
|
||||
if (valueT)
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _pageOffset,
|
||||
builder: (context, value, child) => Slider(
|
||||
min: 2.0,
|
||||
max: 30.0,
|
||||
divisions: max(28, 3),
|
||||
value: value,
|
||||
onChanged: (val) {
|
||||
_pageOffset.value = val;
|
||||
},
|
||||
onChangeEnd: (val) {
|
||||
_readerController.setAutoScroll(
|
||||
valueT, val);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
Consumer(builder: (context, ref, chil) {
|
||||
final showPageNumber = ref.watch(_showPagesNumber);
|
||||
final animatePageTransitions =
|
||||
ref.watch(animatePageTransitionsStateProvider);
|
||||
final scaleType = ref.watch(scaleTypeStateProvider);
|
||||
final fullScreenReader = ref.watch(fullScreenReaderStateProvider);
|
||||
final backgroundColor = ref.watch(backgroundColorStateProvider);
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CustomPopupMenuButton<BackgroundColor>(
|
||||
label: l10n.background_color,
|
||||
title: getBackgroundColorName(backgroundColor, context),
|
||||
onSelected: (value) {
|
||||
ref.read(backgroundColorStateProvider.notifier).set(value);
|
||||
},
|
||||
value: backgroundColor,
|
||||
list: BackgroundColor.values,
|
||||
itemText: (color) {
|
||||
return getBackgroundColorName(color, context);
|
||||
},
|
||||
),
|
||||
CustomPopupMenuButton<ScaleType>(
|
||||
label: l10n.scale_type,
|
||||
title: getScaleTypeNames(context)[scaleType.index],
|
||||
onSelected: (value) {
|
||||
ref
|
||||
.read(scaleTypeStateProvider.notifier)
|
||||
.set(ScaleType.values[value.index]);
|
||||
},
|
||||
value: scaleType,
|
||||
list: ScaleType.values.where((scale) {
|
||||
try {
|
||||
return getScaleTypeNames(context)
|
||||
.contains(getScaleTypeNames(context)[scale.index]);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}).toList(),
|
||||
itemText: (scale) {
|
||||
return getScaleTypeNames(context)[scale.index];
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
value: fullScreenReader,
|
||||
title: Text(
|
||||
l10n.fullscreen,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.color!
|
||||
.withOpacity(0.9),
|
||||
fontSize: 14),
|
||||
),
|
||||
onChanged: (value) {
|
||||
_setFullScreen(value: value);
|
||||
}),
|
||||
SwitchListTile(
|
||||
value: showPageNumber,
|
||||
title: Text(
|
||||
l10n.show_page_number,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.color!
|
||||
.withOpacity(0.9),
|
||||
fontSize: 14),
|
||||
),
|
||||
onChanged: (value) {
|
||||
ref.read(_showPagesNumber.notifier).state = value;
|
||||
_readerController.setShowPageNumber(value);
|
||||
}),
|
||||
SwitchListTile(
|
||||
value: animatePageTransitions,
|
||||
title: Text(
|
||||
l10n.animate_page_transitions,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.color!
|
||||
.withOpacity(0.9),
|
||||
fontSize: 14),
|
||||
),
|
||||
onChanged: (value) {
|
||||
ref
|
||||
.read(animatePageTransitionsStateProvider.notifier)
|
||||
.set(value);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
Consumer(builder: (context, ref, chil) {
|
||||
final customColorFilter = ref.watch(customColorFilterStateProvider);
|
||||
final enableCustomColorFilter =
|
||||
ref.watch(enableCustomColorFilterStateProvider);
|
||||
int r = customColorFilter?.r ?? 0;
|
||||
int g = customColorFilter?.g ?? 0;
|
||||
int b = customColorFilter?.b ?? 0;
|
||||
int a = customColorFilter?.a ?? 0;
|
||||
final colorFilterBlendMode =
|
||||
ref.watch(colorFilterBlendModeStateProvider);
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ExpansionTile(
|
||||
title: Text(
|
||||
l10n.custom_color_filter,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
shape: const StarBorder(),
|
||||
initiallyExpanded: enableCustomColorFilter,
|
||||
trailing: IgnorePointer(
|
||||
child: Switch(
|
||||
value: enableCustomColorFilter,
|
||||
onChanged: (_) {},
|
||||
),
|
||||
),
|
||||
onExpansionChanged: (val) {
|
||||
ref
|
||||
.read(enableCustomColorFilterStateProvider.notifier)
|
||||
.set(val);
|
||||
},
|
||||
children: [
|
||||
customColorFilterListTile(isDesktop, "r", r, (val) {
|
||||
ref
|
||||
.read(customColorFilterStateProvider.notifier)
|
||||
.set(a, val.$1.toInt(), g, b, val.$2);
|
||||
}, context),
|
||||
customColorFilterListTile(isDesktop, "g", g, (val) {
|
||||
ref
|
||||
.read(customColorFilterStateProvider.notifier)
|
||||
.set(a, r, val.$1.toInt(), b, val.$2);
|
||||
}, context),
|
||||
customColorFilterListTile(isDesktop, "b", b, (val) {
|
||||
ref
|
||||
.read(customColorFilterStateProvider.notifier)
|
||||
.set(a, r, g, val.$1.toInt(), val.$2);
|
||||
}, context),
|
||||
customColorFilterListTile(isDesktop, "a", a, (val) {
|
||||
ref
|
||||
.read(customColorFilterStateProvider.notifier)
|
||||
.set(val.$1.toInt(), r, g, b, val.$2);
|
||||
}, context),
|
||||
CustomPopupMenuButton<ColorFilterBlendMode>(
|
||||
label: l10n.color_filter_blend_mode,
|
||||
title: getColorFilterBlendModeName(
|
||||
colorFilterBlendMode, context),
|
||||
onSelected: (value) {
|
||||
ref
|
||||
.read(colorFilterBlendModeStateProvider.notifier)
|
||||
.set(value);
|
||||
},
|
||||
value: colorFilterBlendMode,
|
||||
list: ColorFilterBlendMode.values,
|
||||
itemText: (va) {
|
||||
return getColorFilterBlendModeName(va, context);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)));
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
], context: context, vsync: this, fullWidth: true);
|
||||
|
||||
if (_autoScrollPage.value) {
|
||||
autoPagescroll();
|
||||
_autoScroll.value = true;
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@ class BlendLevelState extends _$BlendLevelState {
|
|||
return isar.settings.getSync(227)!.flexColorSchemeBlendLevel!;
|
||||
}
|
||||
|
||||
void setBlendLevel(double blendLevelValue) {
|
||||
void setBlendLevel(double blendLevelValue, {bool end = false}) {
|
||||
final settings = isar.settings.getSync(227);
|
||||
state = blendLevelValue;
|
||||
isar.writeTxnSync(() =>
|
||||
isar.settings.putSync(settings!..flexColorSchemeBlendLevel = state));
|
||||
if (end) {
|
||||
isar.writeTxnSync(() =>
|
||||
isar.settings.putSync(settings!..flexColorSchemeBlendLevel = state));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,13 +22,16 @@ class BlendLevelSlider extends ConsumerWidget {
|
|||
),
|
||||
),
|
||||
Slider(
|
||||
min: 0.0,
|
||||
max: 40.0,
|
||||
divisions: max(39, 1),
|
||||
value: blendLevel,
|
||||
onChanged: (value) {
|
||||
ref.read(blendLevelStateProvider.notifier).setBlendLevel(value);
|
||||
}),
|
||||
min: 0.0,
|
||||
max: 40.0,
|
||||
divisions: max(39, 1),
|
||||
value: blendLevel,
|
||||
onChanged: (value) =>
|
||||
ref.read(blendLevelStateProvider.notifier).setBlendLevel(value),
|
||||
onChangeEnd: (value) => ref
|
||||
.read(blendLevelStateProvider.notifier)
|
||||
.setBlendLevel(value, end: true),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
156
lib/modules/widgets/custom_draggable_tabbar.dart
Normal file
156
lib/modules/widgets/custom_draggable_tabbar.dart
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
import 'package:draggable_menu/draggable_menu.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/router/router.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
|
||||
class MeasureWidgetSize extends StatefulWidget {
|
||||
final Function(Size? size) onCalculateSize;
|
||||
final Widget child;
|
||||
|
||||
const MeasureWidgetSize(
|
||||
{super.key, required this.onCalculateSize, required this.child});
|
||||
|
||||
@override
|
||||
State<MeasureWidgetSize> createState() => _MeasureWidgetSizeState();
|
||||
}
|
||||
|
||||
class _MeasureWidgetSizeState extends State<MeasureWidgetSize> {
|
||||
final _key = GlobalKey();
|
||||
|
||||
@override
|
||||
initState() {
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => widget.onCalculateSize(_key.currentContext?.size));
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(key: _key, child: widget.child);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> customDraggableTabBar(
|
||||
{required List<Widget> tabs,
|
||||
required List<Widget> children,
|
||||
required BuildContext context,
|
||||
required TickerProvider vsync,
|
||||
bool fullWidth = false}) async {
|
||||
final controller = DraggableMenuController();
|
||||
late TabController tabBarController;
|
||||
tabBarController = TabController(length: tabs.length, vsync: vsync);
|
||||
final maxHeight = context.mediaHeight(0.8).toInt();
|
||||
|
||||
int index = 0;
|
||||
List<Map<String, dynamic>> widgetsHeight = [];
|
||||
|
||||
void refresh() {
|
||||
controller.animateTo(
|
||||
widgetsHeight.indexWhere((element) => element["index"] == index));
|
||||
}
|
||||
|
||||
tabBarController.animation!.addListener(() {
|
||||
final currentIndex = tabBarController.animation!.value.round();
|
||||
index = tabBarController.index;
|
||||
if (index != currentIndex) {
|
||||
index = currentIndex;
|
||||
refresh();
|
||||
} else {
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
|
||||
double additionnalHeight = 0.1;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return Material(
|
||||
child: Column(
|
||||
children: [
|
||||
for (var i = 0; i < children.length; i++) ...[
|
||||
MeasureWidgetSize(
|
||||
onCalculateSize: (size) {
|
||||
double newHeight = size!.height + 52.0 + additionnalHeight;
|
||||
additionnalHeight += 0.1;
|
||||
if (!(newHeight <= maxHeight)) {
|
||||
newHeight = maxHeight - 80;
|
||||
}
|
||||
widgetsHeight.add({"index": i, "height": newHeight});
|
||||
if (widgetsHeight.length == children.length) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: children[i])
|
||||
]
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
widgetsHeight
|
||||
.sort((a, b) => (a["height"] as double).compareTo(b["height"] as double));
|
||||
if (context.mounted) {
|
||||
await DraggableMenu.open(
|
||||
context,
|
||||
DraggableMenu(
|
||||
curve: Curves.fastLinearToSlowEaseIn,
|
||||
controller: controller,
|
||||
levels: widgetsHeight
|
||||
.map((e) => e["height"])
|
||||
.map((e) => DraggableMenuLevel(height: e))
|
||||
.toList(),
|
||||
customUi: Consumer(builder: (context, ref, child) {
|
||||
final location =
|
||||
ref.watch(routerCurrentLocationStateProvider(context));
|
||||
final width = context.isTablet && !fullWidth
|
||||
? switch (location) {
|
||||
null => 100,
|
||||
!= '/MangaLibrary' &&
|
||||
!= '/AnimeLibrary' &&
|
||||
!= '/history' &&
|
||||
!= '/browse' &&
|
||||
!= '/more' =>
|
||||
0,
|
||||
_ => 100,
|
||||
}
|
||||
: 0;
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
body: Container(
|
||||
width: context.mediaWidth(1) - width,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20)),
|
||||
color: Theme.of(context).scaffoldBackgroundColor),
|
||||
child: DefaultTabController(
|
||||
length: tabs.length,
|
||||
child: Column(children: [
|
||||
TabBar(
|
||||
unselectedLabelStyle:
|
||||
const TextStyle(fontWeight: FontWeight.w500),
|
||||
labelStyle:
|
||||
const TextStyle(fontWeight: FontWeight.bold),
|
||||
dividerColor:
|
||||
context.isLight ? Colors.black : Colors.grey,
|
||||
dividerHeight: 0.7,
|
||||
controller: tabBarController,
|
||||
tabs: tabs),
|
||||
Flexible(
|
||||
child: TabBarView(
|
||||
controller: tabBarController,
|
||||
children: children
|
||||
.map((e) => SingleChildScrollView(
|
||||
child: MeasureWidgetSize(
|
||||
onCalculateSize: (_) => refresh(),
|
||||
child: e)))
|
||||
.toList()))
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
child: const SizedBox.shrink()));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue