added custom draggable tabbar & fix

This commit is contained in:
kodjomoustapha 2024-02-21 15:21:26 +01:00
parent 468db9d8a6
commit c564bed263
7 changed files with 1068 additions and 1321 deletions

View file

@ -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(),
),
);
}

View file

@ -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) {

View file

@ -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!,

View file

@ -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;

View file

@ -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));
}
}
}

View file

@ -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),
),
],
);
}

View 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()));
}
}