Merge pull request #371 from Schnitzel5/feature/navigation-settings

allow user to reorder and toggle each navigation item
This commit is contained in:
Moustapha Kodjo Amadou 2025-01-29 19:43:52 +01:00 committed by GitHub
commit 2c5f50e00d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 1046 additions and 710 deletions

View file

@ -371,9 +371,6 @@
"sync_confirm_upload": "التحميل الكامل سيستبدل البيانات الحالية على الخادم ببياناتك الحالية!",
"sync_confirm_download": "التنزيل الكامل سيستبدل بياناتك الحالية بالبيانات الموجودة على الخادم!",
"dialog_confirm": "تأكيد",
"hide_manga": "إخفاء بعض العناصر المتعلقة بالمانغا.",
"hide_anime": "إخفاء بعض العناصر المتعلقة بالأنمي.",
"hide_novel": "إخفاء بعض العناصر المتعلقة بالروايات (الخفيفة).",
"full_screen_player": "استخدام الشاشة الكاملة",
"full_screen_player_info": "استخدام الشاشة الكاملة تلقائيًا عند تشغيل الفيديو.",
"novel_sources": "مصادر الروايات",

View file

@ -385,9 +385,8 @@
"n_day_ago": "Vor {day} Tag",
"now": "jetzt",
"library_last_updated": "Bibliothek zuletzt am {lastUpdated} aktualisiert.",
"hide_manga": "Einige Elemente im Zusammenhang mit Mangas ausblenden.",
"hide_anime": "Einige Elemente im Zusammenhang mit Animes ausblenden.",
"hide_novel": "Einige Elemente im Zusammenhang mit (Light-)Novels ausblenden.",
"reorder_navigation": "Navigation anpassen",
"reorder_navigation_description": "Du kannst die Anordnung und Sichbarheit der Navigation für dich selber anpassen.",
"novel_sources": "Novel-Quellen",
"novel_extensions": "Novel-Erweiterungen",
"sync_after_reading": "Nach dem Lesen oder Ansehen synchronisieren",

View file

@ -215,9 +215,8 @@
"sync_confirm_download": "A full download will completely replace your current data with the remote one!",
"dialog_confirm": "Confirm",
"description": "Description",
"hide_manga": "Hide some elements related to mangas.",
"hide_anime": "Hide some elements related to animes.",
"hide_novel": "Hide some elements related to (light) novels.",
"reorder_navigation": "Customize navigation",
"reorder_navigation_description": "Reorder and toggle each navigation to your needs.",
"full_screen_player": "Use Fullscreen",
"full_screen_player_info": "Automatically use fullscreen when playing a video.",
"episode_progress": "Progress: {n}",

View file

@ -371,9 +371,6 @@
"sync_confirm_upload": "¡Una carga completa reemplazará completamente los datos remotos con los actuales!",
"sync_confirm_download": "¡Una descarga completa reemplazará completamente tus datos actuales con los remotos!",
"dialog_confirm": "Confirmar",
"hide_manga": "Ocultar algunos elementos relacionados con mangas.",
"hide_anime": "Ocultar algunos elementos relacionados con animes.",
"hide_novel": "Ocultar algunos elementos relacionados con novelas ligeras.",
"full_screen_player": "Usar pantalla completa",
"full_screen_player_info": "Usar automáticamente la pantalla completa al reproducir un video.",
"novel_sources": "Fuentes de novelas",

View file

@ -371,9 +371,6 @@
"sync_confirm_upload": "¡Una carga completa reemplazará por completo los datos remotos con los actuales!",
"sync_confirm_download": "¡Una descarga completa reemplazará por completo tus datos actuales con los remotos!",
"dialog_confirm": "Confirmar",
"hide_manga": "Ocultar algunos elementos relacionados con mangas.",
"hide_anime": "Ocultar algunos elementos relacionados con animes.",
"hide_novel": "Ocultar algunos elementos relacionados con novelas (ligeras).",
"full_screen_player": "Usar pantalla completa",
"full_screen_player_info": "Usar automáticamente la pantalla completa al reproducir un video.",
"novel_sources": "Fuentes de novelas",

View file

@ -372,9 +372,6 @@
"sync_confirm_upload": "Un téléversement complet remplacera entièrement les données distantes par vos données actuelles !",
"sync_confirm_download": "Un téléchargement complet remplacera entièrement vos données actuelles par les données distantes !",
"dialog_confirm": "Confirmer",
"hide_manga": "Masquer certains éléments liés aux mangas.",
"hide_anime": "Masquer certains éléments liés aux animes.",
"hide_novel": "Masquer certains éléments liés aux (light) novels.",
"full_screen_player": "Utiliser le mode plein écran",
"full_screen_player_info": "Utiliser automatiquement le mode plein écran lors de la lecture d'une vidéo.",
"novel_sources": "Sources de romans",

View file

@ -371,9 +371,6 @@
"sync_confirm_upload": "Unggahan penuh akan sepenuhnya menggantikan data jarak jauh dengan data Anda saat ini!",
"sync_confirm_download": "Unduhan penuh akan sepenuhnya menggantikan data Anda saat ini dengan data jarak jauh!",
"dialog_confirm": "Konfirmasi",
"hide_manga": "Sembunyikan beberapa elemen terkait manga.",
"hide_anime": "Sembunyikan beberapa elemen terkait anime.",
"hide_novel": "Sembunyikan beberapa elemen terkait (light) novel.",
"full_screen_player": "Gunakan Layar Penuh",
"full_screen_player_info": "Otomatis gunakan layar penuh saat memutar video.",
"novel_sources": "Sumber Novel",

View file

@ -371,9 +371,6 @@
"sync_confirm_upload": "Un caricamento completo sostituirà completamente i dati remoti con quelli attuali!",
"sync_confirm_download": "Un download completo sostituirà completamente i tuoi dati attuali con quelli remoti!",
"dialog_confirm": "Conferma",
"hide_manga": "Nascondi alcuni elementi relativi ai manga.",
"hide_anime": "Nascondi alcuni elementi relativi agli anime.",
"hide_novel": "Nascondi alcuni elementi relativi ai romanzi (leggeri).",
"full_screen_player": "Usa schermo intero",
"full_screen_player_info": "Usa automaticamente lo schermo intero durante la riproduzione di un video.",
"novel_sources": "Fonti romanzo",

View file

@ -371,9 +371,6 @@
"sync_confirm_upload": "Um upload completo substituirá completamente os dados remotos pelos seus dados atuais!",
"sync_confirm_download": "Um download completo substituirá completamente seus dados atuais pelos dados remotos!",
"dialog_confirm": "Confirmar",
"hide_manga": "Ocultar alguns elementos relacionados a mangas.",
"hide_anime": "Ocultar alguns elementos relacionados a animes.",
"hide_novel": "Ocultar alguns elementos relacionados a (light) novels.",
"full_screen_player": "Usar tela cheia",
"full_screen_player_info": "Usar automaticamente tela cheia ao reproduzir um vídeo.",
"novel_sources": "Fontes de novels",

View file

@ -371,9 +371,6 @@
"sync_confirm_upload": "Um upload completo substituirá completamente os dados remotos pelos atuais!",
"sync_confirm_download": "Um download completo substituirá completamente seus dados atuais pelos dados remotos!",
"dialog_confirm": "Confirmar",
"hide_manga": "Ocultar alguns elementos relacionados a mangás.",
"hide_anime": "Ocultar alguns elementos relacionados a animes.",
"hide_novel": "Ocultar alguns elementos relacionados a (light) novels.",
"full_screen_player": "Usar tela cheia",
"full_screen_player_info": "Usar automaticamente tela cheia ao reproduzir um vídeo.",
"novel_sources": "Fontes de novels",

View file

@ -371,9 +371,6 @@
"sync_confirm_upload": "Полная загрузка полностью заменит удаленные данные на ваши текущие!",
"sync_confirm_download": "Полное скачивание полностью заменит ваши текущие данные на удаленные!",
"dialog_confirm": "Подтвердить",
"hide_manga": "Скрыть элементы, связанные с мангой.",
"hide_anime": "Скрыть элементы, связанные с аниме.",
"hide_novel": "Скрыть элементы, связанные с (лёгкими) романами.",
"full_screen_player": "Использовать полноэкранный режим",
"full_screen_player_info": "Автоматически использовать полноэкранный режим при воспроизведении видео.",
"novel_sources": "Источники романов",

View file

@ -373,9 +373,6 @@
"sync_confirm_upload": "การอัปโหลดทั้งหมดจะทำการแทนที่ข้อมูลระยะไกลด้วยข้อมูลปัจจุบันของคุณ!",
"sync_confirm_download": "การดาวน์โหลดทั้งหมดจะทำการแทนที่ข้อมูลปัจจุบันของคุณด้วยข้อมูลจากระยะไกล!",
"dialog_confirm": "ยืนยัน",
"hide_manga": "ซ่อนบางส่วนที่เกี่ยวข้องกับมังงะ",
"hide_anime": "ซ่อนบางส่วนที่เกี่ยวข้องกับอนิเมะ",
"hide_novel": "ซ่อนบางส่วนที่เกี่ยวข้องกับ (ไลท์) นวนิยาย",
"full_screen_player": "ใช้โหมดเต็มหน้าจอ",
"full_screen_player_info": "ใช้โหมดเต็มหน้าจอโดยอัตโนมัติเมื่อเล่นวิดีโอ",
"novel_sources": "แหล่งข้อมูลนวนิยาย",

View file

@ -371,9 +371,6 @@
"sync_confirm_upload": "Tam yükleme, uzak veriyi tamamen mevcut verinizle değiştirecektir!",
"sync_confirm_download": "Tam indirme, mevcut verinizi uzak verilerle tamamen değiştirecektir!",
"dialog_confirm": "Onayla",
"hide_manga": "Mangalarla ilgili bazı öğeleri gizle.",
"hide_anime": "Animesiyle ilgili bazı öğeleri gizle.",
"hide_novel": "Hikayelerle ilgili bazı öğeleri gizle.",
"full_screen_player": "Tam ekran kullan",
"full_screen_player_info": "Bir video oynatıldığında otomatik olarak tam ekran kullan.",
"novel_sources": "Hikaye Kaynakları",

View file

@ -373,9 +373,6 @@
"sync_confirm_upload": "完整上传将用您当前的数据完全替换远程数据!",
"sync_confirm_download": "完整下载将用远程数据完全替换您当前的数据!",
"dialog_confirm": "确认",
"hide_manga": "隐藏与漫画相关的一些元素。",
"hide_anime": "隐藏与动漫相关的一些元素。",
"hide_novel": "隐藏与(轻)小说相关的一些元素。",
"full_screen_player": "使用全屏",
"full_screen_player_info": "播放视频时自动使用全屏。",
"novel_sources": "小说来源",

View file

@ -220,11 +220,9 @@ class Settings {
@enumerated
late NovelTextAlign novelTextAlign;
bool? hideManga;
List<String>? navigationOrder;
bool? hideAnime;
bool? hideNovel;
List<String>? hideItems;
bool? clearChapterCacheOnAppLaunch;
@ -325,9 +323,8 @@ class Settings {
this.novelDisplayType = DisplayType.comfortableGrid,
this.novelFontSize = 14,
this.novelTextAlign = NovelTextAlign.left,
this.hideManga = false,
this.hideAnime = false,
this.hideNovel = false,
this.navigationOrder,
this.hideItems,
this.clearChapterCacheOnAppLaunch = false});
Settings.fromJson(Map<String, dynamic> json) {
@ -503,9 +500,12 @@ class Settings {
}
novelTextAlign = NovelTextAlign
.values[json['novelTextAlign'] ?? NovelTextAlign.left.index];
hideManga = json['hideManga'];
hideAnime = json['hideAnime'];
hideNovel = json['hideNovel'];
if (json['navigationOrder'] != null) {
navigationOrder = (json['navigationOrder'] as List).cast<String>();
}
if (json['hideItems'] != null) {
hideItems = (json['hideItems'] as List).cast<String>();
}
clearChapterCacheOnAppLaunch = json['clearChapterCacheOnAppLaunch'];
}
@ -621,9 +621,8 @@ class Settings {
'novelDisplayType': novelDisplayType.index,
'novelFontSize': novelFontSize,
'novelTextAlign': novelTextAlign.index,
'hideManga': hideManga,
'hideAnime': hideAnime,
'hideNovel': hideNovel,
'navigationOrder': navigationOrder,
'hideItems': hideItems,
'clearChapterCacheOnAppLaunch': clearChapterCacheOnAppLaunch
};
}

File diff suppressed because it is too large Load diff

View file

@ -22,18 +22,16 @@ class BrowseScreen extends ConsumerStatefulWidget {
class _BrowseScreenState extends ConsumerState<BrowseScreen>
with TickerProviderStateMixin {
late final hideManga = ref.watch(hideMangaStateProvider);
late final hideAnime = ref.watch(hideAnimeStateProvider);
late final hideNovel = ref.watch(hideNovelStateProvider);
late final hideItems = ref.watch(hideItemsStateProvider);
late TabController _tabBarController;
late final _tabList = [
if (!hideManga) 'manga',
if (!hideAnime) 'anime',
if (!hideNovel) 'novel',
if (!hideManga) 'mangaExtension',
if (!hideAnime) 'animeExtension',
if (!hideNovel) 'novelExtension',
if (!hideItems.contains("/MangaLibrary")) 'manga',
if (!hideItems.contains("/AnimeLibrary")) 'anime',
if (!hideItems.contains("/NovelLibrary")) 'novel',
if (!hideItems.contains("/MangaLibrary")) 'mangaExtension',
if (!hideItems.contains("/AnimeLibrary")) 'animeExtension',
if (!hideItems.contains("/NovelLibrary")) 'novelExtension',
];
@override
@ -151,10 +149,10 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
isScrollable: true,
controller: _tabBarController,
tabs: [
if (!hideManga) Tab(text: l10n.manga_sources),
if (!hideAnime) Tab(text: l10n.anime_sources),
if (!hideNovel) Tab(text: l10n.novel_sources),
if (!hideManga)
if (!hideItems.contains("/MangaLibrary")) Tab(text: l10n.manga_sources),
if (!hideItems.contains("/AnimeLibrary")) Tab(text: l10n.anime_sources),
if (!hideItems.contains("/NovelLibrary")) Tab(text: l10n.novel_sources),
if (!hideItems.contains("/MangaLibrary"))
Tab(
child: Row(
children: [
@ -164,7 +162,7 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
],
),
),
if (!hideAnime)
if (!hideItems.contains("/AnimeLibrary"))
Tab(
child: Row(
children: [
@ -174,7 +172,7 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
],
),
),
if (!hideNovel)
if (!hideItems.contains("/NovelLibrary"))
Tab(
child: Row(
children: [
@ -187,34 +185,34 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
]),
),
body: TabBarView(controller: _tabBarController, children: [
if (!hideManga)
if (!hideItems.contains("/MangaLibrary"))
SourcesScreen(
itemType: ItemType.manga,
tabIndex: (index) {
_tabBarController.animateTo(index);
},
),
if (!hideAnime)
if (!hideItems.contains("/AnimeLibrary"))
SourcesScreen(
itemType: ItemType.anime,
tabIndex: (index) {
_tabBarController.animateTo(index);
},
),
if (!hideNovel)
if (!hideItems.contains("/NovelLibrary"))
SourcesScreen(
itemType: ItemType.novel,
tabIndex: (index) {
_tabBarController.animateTo(index);
},
),
if (!hideManga)
if (!hideItems.contains("/MangaLibrary"))
ExtensionScreen(
query: _textEditingController.text, itemType: ItemType.manga),
if (!hideAnime)
if (!hideItems.contains("/AnimeLibrary"))
ExtensionScreen(
query: _textEditingController.text, itemType: ItemType.anime),
if (!hideNovel)
if (!hideItems.contains("/NovelLibrary"))
ExtensionScreen(
query: _textEditingController.text, itemType: ItemType.novel),
]),

View file

@ -54,13 +54,11 @@ class _HistoryScreenState extends ConsumerState<HistoryScreen>
@override
Widget build(BuildContext context) {
int newTabs = 0;
final hideManga = ref.watch(hideMangaStateProvider);
final hideAnime = ref.watch(hideAnimeStateProvider);
final hideNovel = ref.watch(hideNovelStateProvider);
final hideItems = ref.watch(hideItemsStateProvider);
if (!hideManga) newTabs++;
if (!hideAnime) newTabs++;
if (!hideNovel) newTabs++;
if (!hideItems.contains("/MangaLibrary")) newTabs++;
if (!hideItems.contains("/AnimeLibrary")) newTabs++;
if (!hideItems.contains("/NovelLibrary")) newTabs++;
if (newTabs == 0) {
return SizedBox.shrink();
}
@ -147,14 +145,17 @@ class _HistoryScreenState extends ConsumerState<HistoryScreen>
.itemTypeEqualTo(_tabBarController
.index ==
0 &&
!hideManga
!hideItems.contains(
"/MangaLibrary")
? ItemType.manga
: _tabBarController.index ==
1 -
(hideManga
(hideItems.contains(
"/MangaLibrary")
? 1
: 0) &&
!hideAnime
!hideItems.contains(
"/AnimeLibrary")
? ItemType.anime
: ItemType.novel)))
.findAllSync()
@ -182,26 +183,26 @@ class _HistoryScreenState extends ConsumerState<HistoryScreen>
indicatorSize: TabBarIndicatorSize.tab,
controller: _tabBarController,
tabs: [
if (!hideManga) Tab(text: l10n.manga),
if (!hideAnime) Tab(text: l10n.anime),
if (!hideNovel) Tab(text: l10n.novel),
if (!hideItems.contains("/MangaLibrary")) Tab(text: l10n.manga),
if (!hideItems.contains("/AnimeLibrary")) Tab(text: l10n.anime),
if (!hideItems.contains("/NovelLibrary")) Tab(text: l10n.novel),
],
),
),
body: Padding(
padding: const EdgeInsets.only(top: 10),
child: TabBarView(controller: _tabBarController, children: [
if (!hideManga)
if (!hideItems.contains("/MangaLibrary"))
HistoryTab(
itemType: ItemType.manga,
query: _textEditingController.text,
),
if (!hideAnime)
if (!hideItems.contains("/AnimeLibrary"))
HistoryTab(
itemType: ItemType.anime,
query: _textEditingController.text,
),
if (!hideNovel)
if (!hideItems.contains("/NovelLibrary"))
HistoryTab(
itemType: ItemType.novel,
query: _textEditingController.text,

View file

@ -51,18 +51,10 @@ class _MainScreenState extends ConsumerState<MainScreen> {
}
}
late bool hideManga = ref.watch(hideMangaStateProvider);
late bool hideAnime = ref.watch(hideAnimeStateProvider);
late bool hideNovel = ref.watch(hideNovelStateProvider);
late final navigationOrder = ref.watch(navigationOrderStateProvider);
late String? location =
ref.watch(routerCurrentLocationStateProvider(context));
late String defaultLocation = hideManga
? hideAnime
? hideNovel
? '/more'
: '/NovelLibrary'
: '/AnimeLibrary'
: '/MangaLibrary';
late String defaultLocation = navigationOrder.first;
@override
initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
@ -84,33 +76,17 @@ class _MainScreenState extends ConsumerState<MainScreen> {
Widget build(BuildContext context) {
final l10n = context.l10n;
final route = GoRouter.of(context);
final navigationOrder = ref.watch(navigationOrderStateProvider);
final hideItems = ref.watch(hideItemsStateProvider);
location = ref.watch(routerCurrentLocationStateProvider(context));
return ref.watch(migrationProvider).when(data: (_) {
return Consumer(builder: (context, ref, chuld) {
hideManga = ref.watch(hideMangaStateProvider);
hideAnime = ref.watch(hideAnimeStateProvider);
hideNovel = ref.watch(hideNovelStateProvider);
bool isReadingScreen = location == '/mangaReaderView' ||
location == '/animePlayerView' ||
location == '/novelReaderView';
final dest = [
'/MangaLibrary',
'/AnimeLibrary',
'/NovelLibrary',
'/updates',
'/history',
'/browse',
'/more'
];
if (hideManga) {
dest.removeWhere((d) => d == "/MangaLibrary");
}
if (hideAnime) {
dest.removeWhere((d) => d == "/AnimeLibrary");
}
if (hideNovel) {
dest.removeWhere((d) => d == "/NovelLibrary");
}
final dest =
navigationOrder.where((nav) => !hideItems.contains(nav)).toList();
int currentIndex = dest.indexOf(location ?? defaultLocation);
if (currentIndex == -1) {
currentIndex = dest.length - 1;
@ -183,91 +159,9 @@ class _MainScreenState extends ConsumerState<MainScreen> {
return NavigationRail(
labelType: NavigationRailLabelType.all,
useIndicator: true,
destinations: [
if (!hideManga)
NavigationRailDestination(
selectedIcon: const Icon(
Icons.collections_bookmark),
icon: const Icon(Icons
.collections_bookmark_outlined),
label: Padding(
padding:
const EdgeInsets.only(
top: 5),
child: Text(l10n.manga))),
if (!hideAnime)
NavigationRailDestination(
selectedIcon: const Icon(
Icons.video_collection),
icon: const Icon(Icons
.video_collection_outlined),
label: Padding(
padding:
const EdgeInsets.only(
top: 5),
child: Text(l10n.anime))),
if (!hideNovel)
NavigationRailDestination(
selectedIcon: const Icon(
Icons.local_library),
icon: const Icon(
Icons.local_library_outlined),
label: Padding(
padding:
const EdgeInsets.only(
top: 5),
child: Text(l10n.novel))),
NavigationRailDestination(
selectedIcon: _updatesTotalNumbers(
ref, Icon(Icons.new_releases)),
icon: _updatesTotalNumbers(
ref,
Icon(Icons
.new_releases_outlined)),
label: Padding(
padding:
const EdgeInsets.only(top: 5),
child: Text(
getHyphenatedUpdatesLabel(
ref
.watch(
l10nLocaleStateProvider)
.languageCode,
l10n.updates,
),
textAlign: TextAlign.center,
),
)),
NavigationRailDestination(
selectedIcon:
const Icon(Icons.history),
icon: const Icon(
Icons.history_outlined),
label: Padding(
padding: const EdgeInsets.only(
top: 5),
child: Text(l10n.history))),
NavigationRailDestination(
selectedIcon:
_extensionUpdateTotalNumbers(
ref, Icon(Icons.explore)),
icon: _extensionUpdateTotalNumbers(
ref,
Icon(Icons.explore_outlined)),
label: Padding(
padding: const EdgeInsets.only(
top: 5),
child: Text(l10n.browse))),
NavigationRailDestination(
selectedIcon:
const Icon(Icons.more_horiz),
icon: const Icon(
Icons.more_horiz_outlined),
label: Padding(
padding: const EdgeInsets.only(
top: 5),
child: Text(l10n.more))),
],
destinations:
_buildNavigationWidgetsDesktop(
ref, dest, context),
selectedIndex: currentIndex,
onDestinationSelected: (newIndex) {
route.go(dest[newIndex]);
@ -313,49 +207,8 @@ class _MainScreenState extends ConsumerState<MainScreen> {
animationDuration:
const Duration(milliseconds: 500),
selectedIndex: currentIndex,
destinations: [
if (!hideManga)
NavigationDestination(
selectedIcon:
const Icon(Icons.collections_bookmark),
icon: const Icon(
Icons.collections_bookmark_outlined),
label: l10n.manga),
if (!hideAnime)
NavigationDestination(
selectedIcon:
const Icon(Icons.video_collection),
icon: const Icon(
Icons.video_collection_outlined),
label: l10n.anime),
if (!hideNovel)
NavigationDestination(
selectedIcon:
const Icon(Icons.local_library),
icon: const Icon(
Icons.local_library_outlined),
label: l10n.novel),
NavigationDestination(
selectedIcon: _updatesTotalNumbers(
ref, Icon(Icons.new_releases)),
icon: _updatesTotalNumbers(
ref, Icon(Icons.new_releases_outlined)),
label: l10n.updates),
NavigationDestination(
selectedIcon: const Icon(Icons.history),
icon: const Icon(Icons.history_outlined),
label: l10n.history),
NavigationDestination(
selectedIcon: _extensionUpdateTotalNumbers(
ref, Icon(Icons.explore)),
icon: _extensionUpdateTotalNumbers(
ref, Icon(Icons.explore_outlined)),
label: l10n.browse),
NavigationDestination(
selectedIcon: const Icon(Icons.more_horiz),
icon: const Icon(Icons.more_horiz_outlined),
label: l10n.more),
],
destinations: _buildNavigationWidgetsMobile(
ref, dest, context),
onDestinationSelected: (newIndex) {
route.go(dest[newIndex]);
},
@ -373,18 +226,136 @@ class _MainScreenState extends ConsumerState<MainScreen> {
return const LoadingIcon();
});
}
List<NavigationRailDestination> _buildNavigationWidgetsDesktop(
WidgetRef ref, List<String> dest, BuildContext context) {
final l10n = context.l10n;
final destinations =
List<NavigationRailDestination?>.filled(dest.length, null);
if (dest.contains("/MangaLibrary")) {
destinations[dest.indexOf("/MangaLibrary")] = NavigationRailDestination(
selectedIcon: const Icon(Icons.collections_bookmark),
icon: const Icon(Icons.collections_bookmark_outlined),
label: Padding(
padding: const EdgeInsets.only(top: 5), child: Text(l10n.manga)));
}
if (dest.contains("/AnimeLibrary")) {
destinations[dest.indexOf("/AnimeLibrary")] = NavigationRailDestination(
selectedIcon: const Icon(Icons.video_collection),
icon: const Icon(Icons.video_collection_outlined),
label: Padding(
padding: const EdgeInsets.only(top: 5), child: Text(l10n.anime)));
}
if (dest.contains("/NovelLibrary")) {
destinations[dest.indexOf("/NovelLibrary")] = NavigationRailDestination(
selectedIcon: const Icon(Icons.local_library),
icon: const Icon(Icons.local_library_outlined),
label: Padding(
padding: const EdgeInsets.only(top: 5), child: Text(l10n.novel)));
}
if (dest.contains("/updates")) {
destinations[dest.indexOf("/updates")] = NavigationRailDestination(
selectedIcon: _updatesTotalNumbers(ref, Icon(Icons.new_releases)),
icon: _updatesTotalNumbers(ref, Icon(Icons.new_releases_outlined)),
label: Padding(
padding: const EdgeInsets.only(top: 5),
child: Text(
getHyphenatedUpdatesLabel(
ref.watch(l10nLocaleStateProvider).languageCode,
l10n.updates,
),
textAlign: TextAlign.center,
),
));
}
if (dest.contains("/history")) {
destinations[dest.indexOf("/history")] = NavigationRailDestination(
selectedIcon: const Icon(Icons.history),
icon: const Icon(Icons.history_outlined),
label: Padding(
padding: const EdgeInsets.only(top: 5),
child: Text(l10n.history)));
}
if (dest.contains("/browse")) {
destinations[dest.indexOf("/browse")] = NavigationRailDestination(
selectedIcon: _extensionUpdateTotalNumbers(ref, Icon(Icons.explore)),
icon: _extensionUpdateTotalNumbers(ref, Icon(Icons.explore_outlined)),
label: Padding(
padding: const EdgeInsets.only(top: 5),
child: Text(l10n.browse)));
}
if (dest.contains("/more")) {
destinations[dest.indexOf("/more")] = NavigationRailDestination(
selectedIcon: const Icon(Icons.more_horiz),
icon: const Icon(Icons.more_horiz_outlined),
label: Padding(
padding: const EdgeInsets.only(top: 5), child: Text(l10n.more)));
}
return destinations.nonNulls.toList();
}
List<Widget> _buildNavigationWidgetsMobile(
WidgetRef ref, List<String> dest, BuildContext context) {
final l10n = context.l10n;
final destinations =
List<Widget>.filled(dest.length, const SizedBox.shrink());
if (dest.contains("/MangaLibrary")) {
destinations[dest.indexOf("/MangaLibrary")] = NavigationDestination(
selectedIcon: const Icon(Icons.collections_bookmark),
icon: const Icon(Icons.collections_bookmark_outlined),
label: l10n.manga);
}
if (dest.contains("/AnimeLibrary")) {
destinations[dest.indexOf("/AnimeLibrary")] = NavigationDestination(
selectedIcon: const Icon(Icons.video_collection),
icon: const Icon(Icons.video_collection_outlined),
label: l10n.anime);
}
if (dest.contains("/NovelLibrary")) {
destinations[dest.indexOf("/NovelLibrary")] = NavigationDestination(
selectedIcon: const Icon(Icons.local_library),
icon: const Icon(Icons.local_library_outlined),
label: l10n.novel);
}
if (dest.contains("/updates")) {
destinations[dest.indexOf("/updates")] = NavigationDestination(
selectedIcon: _updatesTotalNumbers(ref, Icon(Icons.new_releases)),
icon: _updatesTotalNumbers(ref, Icon(Icons.new_releases_outlined)),
label: l10n.updates);
}
if (dest.contains("/history")) {
destinations[dest.indexOf("/history")] = NavigationDestination(
selectedIcon: const Icon(Icons.history),
icon: const Icon(Icons.history_outlined),
label: l10n.history);
}
if (dest.contains("/browse")) {
destinations[dest.indexOf("/browse")] = NavigationDestination(
selectedIcon: _extensionUpdateTotalNumbers(ref, Icon(Icons.explore)),
icon: _extensionUpdateTotalNumbers(ref, Icon(Icons.explore_outlined)),
label: l10n.browse);
}
if (dest.contains("/more")) {
destinations[dest.indexOf("/more")] = NavigationDestination(
selectedIcon: const Icon(Icons.more_horiz),
icon: const Icon(Icons.more_horiz_outlined),
label: l10n.more);
}
return destinations;
}
}
Widget _extensionUpdateTotalNumbers(WidgetRef ref, Widget widget) {
final hideItems = ref.watch(hideItemsStateProvider);
return StreamBuilder(
stream: isar.sources
.filter()
.idIsNotNull()
.optional(ref.watch(hideMangaStateProvider),
.optional(hideItems.contains("/MangaLibrary"),
(q) => q.not().itemTypeEqualTo(ItemType.manga))
.optional(ref.watch(hideAnimeStateProvider),
.optional(hideItems.contains("/AnimeLibrary"),
(q) => q.not().itemTypeEqualTo(ItemType.anime))
.optional(ref.watch(hideNovelStateProvider),
.optional(hideItems.contains("/NovelLibrary"),
(q) => q.not().itemTypeEqualTo(ItemType.novel))
.and()
.isActiveEqualTo(true)
@ -405,24 +376,25 @@ Widget _extensionUpdateTotalNumbers(WidgetRef ref, Widget widget) {
}
Widget _updatesTotalNumbers(WidgetRef ref, Widget widget) {
final hideItems = ref.watch(hideItemsStateProvider);
return StreamBuilder(
stream: isar.updates
.filter()
.idIsNotNull()
.optional(
ref.watch(hideMangaStateProvider),
hideItems.contains("/MangaLibrary"),
(q) => q.chapter(
(c) =>
c.manga((m) => m.not().itemTypeEqualTo(ItemType.manga)),
))
.optional(
ref.watch(hideAnimeStateProvider),
hideItems.contains("/AnimeLibrary"),
(q) => q.chapter(
(c) =>
c.manga((m) => m.not().itemTypeEqualTo(ItemType.anime)),
))
.optional(
ref.watch(hideNovelStateProvider),
hideItems.contains("/NovelLibrary"),
(q) => q.chapter(
(c) =>
c.manga((m) => m.not().itemTypeEqualTo(ItemType.novel)),

View file

@ -32,12 +32,10 @@ class _CategoriesScreenState extends ConsumerState<CategoriesScreen>
@override
Widget build(BuildContext context) {
int newTabs = 0;
final hideManga = ref.watch(hideMangaStateProvider);
final hideAnime = ref.watch(hideAnimeStateProvider);
final hideNovel = ref.watch(hideNovelStateProvider);
if (!hideManga) newTabs++;
if (!hideAnime) newTabs++;
if (!hideNovel) newTabs++;
final hideItems = ref.watch(hideItemsStateProvider);
if (!hideItems.contains("/MangaLibrary")) newTabs++;
if (!hideItems.contains("/AnimeLibrary")) newTabs++;
if (!hideItems.contains("/NovelLibrary")) newTabs++;
if (tabs != newTabs) {
_tabBarController.dispose();
_tabBarController = TabController(length: newTabs, vsync: this);
@ -62,22 +60,22 @@ class _CategoriesScreenState extends ConsumerState<CategoriesScreen>
indicatorSize: TabBarIndicatorSize.tab,
controller: _tabBarController,
tabs: [
if (!hideManga) Tab(text: l10n.manga),
if (!hideAnime) Tab(text: l10n.anime),
if (!hideNovel) Tab(text: l10n.novel),
if (!hideItems.contains("/MangaLibrary")) Tab(text: l10n.manga),
if (!hideItems.contains("/AnimeLibrary")) Tab(text: l10n.anime),
if (!hideItems.contains("/NovelLibrary")) Tab(text: l10n.novel),
],
),
),
body: TabBarView(controller: _tabBarController, children: [
if (!hideManga)
if (!hideItems.contains("/MangaLibrary"))
CategoriesTab(
itemType: ItemType.manga,
),
if (!hideAnime)
if (!hideItems.contains("/AnimeLibrary"))
CategoriesTab(
itemType: ItemType.anime,
),
if (!hideNovel)
if (!hideItems.contains("/NovelLibrary"))
CategoriesTab(
itemType: ItemType.novel,
)

View file

@ -20,6 +20,7 @@ import 'package:mangayomi/modules/more/settings/appearance/providers/blend_level
import 'package:mangayomi/modules/more/settings/appearance/providers/flex_scheme_color_state_provider.dart';
import 'package:mangayomi/modules/more/settings/appearance/providers/pure_black_dark_mode_state_provider.dart';
import 'package:mangayomi/modules/more/settings/appearance/providers/theme_mode_state_provider.dart';
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -178,6 +179,8 @@ void restoreBackup(Ref ref, Map<String, dynamic> backup) {
ref.invalidate(flexSchemeColorStateProvider);
ref.invalidate(pureBlackDarkModeStateProvider);
ref.invalidate(l10nLocaleStateProvider);
ref.invalidate(navigationOrderStateProvider);
ref.invalidate(hideItemsStateProvider);
});
} catch (e) {
rethrow;

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:mangayomi/modules/more/settings/appearance/providers/app_font_family.dart';
import 'package:mangayomi/modules/more/settings/appearance/providers/theme_mode_state_provider.dart';
@ -16,6 +17,16 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:mangayomi/utils/language.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
final navigationItems = {
"/MangaLibrary": "Manga",
"/AnimeLibrary": "Anime",
"/NovelLibrary": "Novel",
"/updates": "Updates",
"/history": "History",
"/browse": "Browse",
"/more": "More",
};
class AppearanceScreen extends ConsumerWidget {
const AppearanceScreen({super.key});
@ -28,9 +39,6 @@ class AppearanceScreen extends ConsumerWidget {
final isDarkTheme = ref.watch(themeModeStateProvider);
final l10nLocale = ref.watch(l10nLocaleStateProvider);
final appFontFamily = ref.watch(appFontFamilyProvider);
final hideAnime = ref.watch(hideAnimeStateProvider);
final hideManga = ref.watch(hideMangaStateProvider);
final hideNovel = ref.watch(hideNovelStateProvider);
final appFontFamilySub = appFontFamily == null
? context.l10n.default0
: GoogleFonts.asMap()
@ -299,24 +307,16 @@ class AppearanceScreen extends ConsumerWidget {
fontSize: 11, color: context.secondaryColor),
),
),
SwitchListTile(
value: hideAnime,
title: Text(context.l10n.hide_anime),
onChanged: (value) {
ref.read(hideAnimeStateProvider.notifier).set(value);
}),
SwitchListTile(
value: hideManga,
title: Text(context.l10n.hide_manga),
onChanged: (value) {
ref.read(hideMangaStateProvider.notifier).set(value);
}),
SwitchListTile(
value: hideNovel,
title: Text(context.l10n.hide_novel),
onChanged: (value) {
ref.read(hideNovelStateProvider.notifier).set(value);
}),
ListTile(
onTap: () {
context.push("/customNavigationSettings");
},
title: Text(l10n.reorder_navigation),
subtitle: Text(
l10n.reorder_navigation_description,
style: TextStyle(
fontSize: 11, color: context.secondaryColor),
)),
],
),
),
@ -469,3 +469,74 @@ class AppearanceScreen extends ConsumerWidget {
);
}
}
class CustomNavigationSettings extends ConsumerStatefulWidget {
const CustomNavigationSettings({super.key});
@override
ConsumerState<CustomNavigationSettings> createState() =>
_CustomNavigationSettingsState();
}
class _CustomNavigationSettingsState
extends ConsumerState<CustomNavigationSettings> {
@override
Widget build(BuildContext context) {
final l10n = l10nLocalizations(context);
final navigationOrder = ref.watch(navigationOrderStateProvider);
final hideItems = ref.watch(hideItemsStateProvider);
return Scaffold(
appBar: AppBar(
title: Text(l10n!.reorder_navigation),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: ReorderableListView.builder(
shrinkWrap: true,
itemCount: navigationOrder.length,
itemBuilder: (context, index) {
final navigation = navigationOrder[index];
return SwitchListTile.adaptive(
key: Key(navigation),
dense: true,
contentPadding: const EdgeInsets.only(left: 0, right: 40),
value: !hideItems.contains(navigation),
onChanged: navigation == "/more"
? null
: (value) {
final temp = hideItems.toList();
if (!value && !hideItems.contains(navigation)) {
temp.add(navigation);
} else if (value) {
temp.remove(navigation);
}
ref.read(hideItemsStateProvider.notifier).set(temp);
},
title: Text(navigationItems[navigation]!),
);
},
onReorder: (oldIndex, newIndex) {
if (oldIndex < newIndex) {
final draggedItem = navigationOrder[oldIndex];
for (var i = oldIndex; i < newIndex - 1; i++) {
navigationOrder[i] = navigationOrder[i + 1];
}
navigationOrder[newIndex - 1] = draggedItem;
} else {
final draggedItem = navigationOrder[oldIndex];
for (var i = oldIndex; i > newIndex; i--) {
navigationOrder[i] = navigationOrder[i - 1];
}
navigationOrder[newIndex] = draggedItem;
}
ref
.read(navigationOrderStateProvider.notifier)
.set(navigationOrder);
},
),
),
),
);
}
}

View file

@ -139,47 +139,41 @@ class FullScreenReaderState extends _$FullScreenReaderState {
}
@riverpod
class HideMangaState extends _$HideMangaState {
class NavigationOrderState extends _$NavigationOrderState {
@override
bool build() {
return isar.settings.getSync(227)!.hideManga ?? false;
List<String> build() {
return isar.settings.getSync(227)!.navigationOrder ??
[
'/MangaLibrary',
'/AnimeLibrary',
'/NovelLibrary',
'/updates',
'/history',
'/browse',
'/more'
];
}
void set(bool value) {
void set(List<String> values) {
final settings = isar.settings.getSync(227);
state = value;
state = values;
isar.writeTxnSync(
() => isar.settings.putSync(settings!..hideManga = value));
() => isar.settings.putSync(settings!..navigationOrder = values));
}
}
@riverpod
class HideAnimeState extends _$HideAnimeState {
class HideItemsState extends _$HideItemsState {
@override
bool build() {
return isar.settings.getSync(227)!.hideAnime ?? false;
List<String> build() {
return isar.settings.getSync(227)!.hideItems ?? [];
}
void set(bool value) {
void set(List<String> values) {
final settings = isar.settings.getSync(227);
state = value;
state = values;
isar.writeTxnSync(
() => isar.settings.putSync(settings!..hideAnime = value));
}
}
@riverpod
class HideNovelState extends _$HideNovelState {
@override
bool build() {
return isar.settings.getSync(227)!.hideNovel ?? false;
}
void set(bool value) {
final settings = isar.settings.getSync(227);
state = value;
isar.writeTxnSync(
() => isar.settings.putSync(settings!..hideNovel = value));
() => isar.settings.putSync(settings!..hideItems = values));
}
}

View file

@ -157,54 +157,39 @@ final fullScreenReaderStateProvider =
);
typedef _$FullScreenReaderState = AutoDisposeNotifier<bool>;
String _$hideMangaStateHash() => r'fd24207581798fd1634ff6df2c85b8895053d14c';
String _$navigationOrderStateHash() =>
r'f1da55a7687995d136a6580d3f63f9b1b32a6ae8';
/// See also [HideMangaState].
@ProviderFor(HideMangaState)
final hideMangaStateProvider =
AutoDisposeNotifierProvider<HideMangaState, bool>.internal(
HideMangaState.new,
name: r'hideMangaStateProvider',
/// See also [NavigationOrderState].
@ProviderFor(NavigationOrderState)
final navigationOrderStateProvider =
AutoDisposeNotifierProvider<NavigationOrderState, List<String>>.internal(
NavigationOrderState.new,
name: r'navigationOrderStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$hideMangaStateHash,
: _$navigationOrderStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$HideMangaState = AutoDisposeNotifier<bool>;
String _$hideAnimeStateHash() => r'3e8748d9312b9ea84364959b7de17fed2204d303';
typedef _$NavigationOrderState = AutoDisposeNotifier<List<String>>;
String _$hideItemsStateHash() => r'b4a467e66f6a1f9b36e4b201a10b771e0dae6a80';
/// See also [HideAnimeState].
@ProviderFor(HideAnimeState)
final hideAnimeStateProvider =
AutoDisposeNotifierProvider<HideAnimeState, bool>.internal(
HideAnimeState.new,
name: r'hideAnimeStateProvider',
/// See also [HideItemsState].
@ProviderFor(HideItemsState)
final hideItemsStateProvider =
AutoDisposeNotifierProvider<HideItemsState, List<String>>.internal(
HideItemsState.new,
name: r'hideItemsStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$hideAnimeStateHash,
: _$hideItemsStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$HideAnimeState = AutoDisposeNotifier<bool>;
String _$hideNovelStateHash() => r'697efab85819783a7c1982797927feb397770191';
/// See also [HideNovelState].
@ProviderFor(HideNovelState)
final hideNovelStateProvider =
AutoDisposeNotifierProvider<HideNovelState, bool>.internal(
HideNovelState.new,
name: r'hideNovelStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$hideNovelStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$HideNovelState = AutoDisposeNotifier<bool>;
typedef _$HideItemsState = AutoDisposeNotifier<List<String>>;
String _$novelFontSizeStateHash() =>
r'fd104e358203d3f86e14d933518f2dbd067cec13';

View file

@ -95,12 +95,10 @@ class _UpdatesScreenState extends ConsumerState<UpdatesScreen>
@override
Widget build(BuildContext context) {
int newTabs = 0;
final hideManga = ref.watch(hideMangaStateProvider);
final hideAnime = ref.watch(hideAnimeStateProvider);
final hideNovel = ref.watch(hideNovelStateProvider);
if (!hideManga) newTabs++;
if (!hideAnime) newTabs++;
if (!hideNovel) newTabs++;
final hideItems = ref.watch(hideItemsStateProvider);
if (!hideItems.contains("/MangaLibrary")) newTabs++;
if (!hideItems.contains("/AnimeLibrary")) newTabs++;
if (!hideItems.contains("/NovelLibrary")) newTabs++;
if (newTabs == 0) {
return SizedBox.shrink();
}
@ -194,14 +192,17 @@ class _UpdatesScreenState extends ConsumerState<UpdatesScreen>
.itemTypeEqualTo(_tabBarController
.index ==
0 &&
!hideManga
!hideItems.contains(
"/MangaLibrary")
? ItemType.manga
: _tabBarController.index ==
1 -
(hideManga
(hideItems.contains(
"/MangaLibrary")
? 1
: 0) &&
!hideAnime
!hideItems.contains(
"/AnimeLibrary")
? ItemType.anime
: ItemType.novel)))
.findAllSync()
@ -229,7 +230,7 @@ class _UpdatesScreenState extends ConsumerState<UpdatesScreen>
indicatorSize: TabBarIndicatorSize.tab,
controller: _tabBarController,
tabs: [
if (!hideManga)
if (!hideItems.contains("/MangaLibrary"))
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@ -238,7 +239,7 @@ class _UpdatesScreenState extends ConsumerState<UpdatesScreen>
_updateNumbers(ref, ItemType.manga)
],
),
if (!hideAnime)
if (!hideItems.contains("/AnimeLibrary"))
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@ -247,7 +248,7 @@ class _UpdatesScreenState extends ConsumerState<UpdatesScreen>
_updateNumbers(ref, ItemType.anime)
],
),
if (!hideNovel)
if (!hideItems.contains("/NovelLibrary"))
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@ -262,17 +263,17 @@ class _UpdatesScreenState extends ConsumerState<UpdatesScreen>
body: Padding(
padding: const EdgeInsets.only(top: 10),
child: TabBarView(controller: _tabBarController, children: [
if (!hideManga)
if (!hideItems.contains("/MangaLibrary"))
UpdateTab(
itemType: ItemType.manga,
query: _textEditingController.text,
isLoading: _isLoading),
if (!hideAnime)
if (!hideItems.contains("/AnimeLibrary"))
UpdateTab(
itemType: ItemType.anime,
query: _textEditingController.text,
isLoading: _isLoading),
if (!hideNovel)
if (!hideItems.contains("/NovelLibrary"))
UpdateTab(
itemType: ItemType.novel,
query: _textEditingController.text,

View file

@ -594,6 +594,19 @@ class RouterNotifier extends ChangeNotifier {
);
},
),
GoRoute(
path: "/customNavigationSettings",
name: "customNavigationSettings",
builder: (context, state) {
return const CustomNavigationSettings();
},
pageBuilder: (context, state) {
return transitionPage(
key: state.pageKey,
child: const CustomNavigationSettings(),
);
},
),
];
}

View file

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

View file

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