mangayomi-mirror/lib/router/router.dart
Moustapha Kodjo Amadou f729380223 feat: Implement app lock feature with biometric authentication
- Added AppLockScreen for biometric authentication to unlock the app.
- Introduced security settings screen to enable/disable app lock.
- Integrated local_auth package for biometric authentication support.
- Created security state providers to manage app lock state.
- Updated chapter list tile widget to support dismiss actions for bookmarking and marking chapters as read.
- Enhanced CBZ conversion process to include ComicInfo.xml metadata.
- Added conditional UI elements based on platform capabilities.
- Added Completed & Tracked filter in library
2026-03-04 11:56:49 +01:00

307 lines
12 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:io';
import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/models/track.dart';
import 'package:mangayomi/models/track_preference.dart';
import 'package:mangayomi/models/track_search.dart';
import 'package:mangayomi/modules/anime/anime_player_view.dart';
import 'package:mangayomi/modules/browse/extension/edit_code.dart';
import 'package:mangayomi/modules/browse/extension/extension_detail.dart';
import 'package:mangayomi/modules/browse/extension/widgets/create_extension.dart';
import 'package:mangayomi/modules/browse/sources/sources_filter_screen.dart';
import 'package:mangayomi/modules/calendar/calendar_screen.dart';
import 'package:mangayomi/modules/manga/detail/widgets/migrate_screen.dart';
import 'package:mangayomi/modules/manga/detail/widgets/recommendation_screen.dart';
import 'package:mangayomi/modules/manga/detail/widgets/watch_order_screen.dart';
import 'package:mangayomi/modules/more/data_and_storage/create_backup.dart';
import 'package:mangayomi/modules/more/data_and_storage/data_and_storage.dart';
import 'package:mangayomi/modules/more/settings/appearance/custom_navigation_settings.dart';
import 'package:mangayomi/modules/more/settings/browse/source_repositories.dart';
import 'package:mangayomi/modules/more/settings/player/custom_button_screen.dart';
import 'package:mangayomi/modules/more/settings/player/player_advanced_screen.dart';
import 'package:mangayomi/modules/more/settings/player/player_audio_screen.dart';
import 'package:mangayomi/modules/more/settings/player/player_decoder_screen.dart';
import 'package:mangayomi/modules/more/settings/player/player_overview_screen.dart';
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
import 'package:mangayomi/modules/more/statistics/statistics_screen.dart';
import 'package:mangayomi/modules/novel/novel_reader_view.dart';
import 'package:mangayomi/modules/tracker_library/tracker_library_screen.dart';
import 'package:mangayomi/modules/updates/updates_screen.dart';
import 'package:mangayomi/modules/more/categories/categories_screen.dart';
import 'package:mangayomi/modules/more/settings/downloads/downloads_screen.dart';
import 'package:mangayomi/modules/more/settings/player/player_screen.dart';
import 'package:mangayomi/modules/more/settings/sync/sync.dart';
import 'package:mangayomi/modules/more/settings/track/track.dart';
import 'package:mangayomi/modules/more/settings/track/manage_trackers/manage_trackers.dart';
import 'package:mangayomi/modules/more/settings/track/manage_trackers/tracking_detail.dart';
import 'package:mangayomi/modules/webview/webview.dart';
import 'package:mangayomi/modules/browse/browse_screen.dart';
import 'package:mangayomi/modules/browse/extension/extension_lang.dart';
import 'package:mangayomi/modules/browse/global_search/global_search_screen.dart';
import 'package:mangayomi/modules/main_view/main_screen.dart';
import 'package:mangayomi/modules/history/history_screen.dart';
import 'package:mangayomi/modules/library/library_screen.dart';
import 'package:mangayomi/modules/manga/detail/manga_detail_main.dart';
import 'package:mangayomi/modules/manga/home/manga_home_screen.dart';
import 'package:mangayomi/modules/manga/reader/reader_view.dart';
import 'package:mangayomi/modules/more/about/about_screen.dart';
import 'package:mangayomi/modules/more/download_queue/download_queue_screen.dart';
import 'package:mangayomi/modules/more/more_screen.dart';
import 'package:mangayomi/modules/more/settings/appearance/appearance_screen.dart';
import 'package:mangayomi/modules/more/settings/browse/browse_screen.dart';
import 'package:mangayomi/modules/more/settings/general/general_screen.dart';
import 'package:mangayomi/modules/more/settings/reader/reader_screen.dart';
import 'package:mangayomi/modules/more/settings/settings_screen.dart';
import 'package:mangayomi/modules/more/settings/security/security_screen.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:flutter/cupertino.dart';
part 'router.g.dart';
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@riverpod
GoRouter router(Ref ref) {
final router = RouterNotifier();
final hiddenItems = ref.read(hideItemsStateProvider);
final initLocation = ref
.watch(navigationOrderStateProvider)
.where((e) => !hiddenItems.contains(e))
.first;
return GoRouter(
observers: [BotToastNavigatorObserver()],
initialLocation: initLocation,
debugLogDiagnostics: kDebugMode,
refreshListenable: router,
routes: router._routes,
navigatorKey: navigatorKey,
onException: (context, state, router) => router.go(initLocation),
);
}
@riverpod
class RouterCurrentLocationState extends _$RouterCurrentLocationState {
bool _didSubscribe = false;
@override
String? build() {
ref.keepAlive();
// Delay listenerregistration until after the first frame.
if (!_didSubscribe) {
_didSubscribe = true;
// Schedule the registration to run after the first build/frame:
WidgetsBinding.instance.addPostFrameCallback((_) {
_listener();
});
}
return null;
}
void _listener() {
final router = ref.read(routerProvider);
router.routerDelegate.addListener(() {
WidgetsBinding.instance.addPostFrameCallback((_) {
final RouteMatchList matches =
router.routerDelegate.currentConfiguration;
final RouteMatch lastMatch = matches.last;
final RouteMatchList matchList = lastMatch is ImperativeRouteMatch
? lastMatch.matches
: matches;
state = matchList.uri.toString();
});
});
}
void refresh() {
_listener();
}
}
class RouterNotifier extends ChangeNotifier {
List<RouteBase> get _routes => [
ShellRoute(
builder: (context, state, child) => MainScreen(child: child),
routes: [
_genericRoute<String?>(
name: "MangaLibrary",
builder: (id) =>
LibraryScreen(itemType: ItemType.manga, presetInput: id),
),
_genericRoute<String?>(
name: "AnimeLibrary",
builder: (id) =>
LibraryScreen(itemType: ItemType.anime, presetInput: id),
),
_genericRoute<String?>(
name: "NovelLibrary",
builder: (id) =>
LibraryScreen(itemType: ItemType.novel, presetInput: id),
),
_genericRoute<String?>(
name: "trackerLibrary",
builder: (id) => TrackerLibraryScreen(presetInput: id),
),
_genericRoute(name: "history", child: const HistoryScreen()),
_genericRoute(name: "updates", child: const UpdatesScreen()),
_genericRoute(name: "browse", child: const BrowseScreen()),
_genericRoute(name: "more", child: const MoreScreen()),
],
),
_genericRoute<(Source?, bool)>(
name: "mangaHome",
builder: (id) => MangaHomeScreen(source: id.$1!, isLatest: id.$2),
),
_genericRoute<int>(
path: "/manga-reader/detail",
builder: (id) => MangaReaderDetail(mangaId: id),
),
_genericRoute<int>(
name: "mangaReaderView",
builder: (id) => MangaReaderView(chapterId: id),
),
_genericRoute<int>(
name: "animePlayerView",
builder: (id) => AnimePlayerView(episodeId: id),
),
_genericRoute<int>(
name: "novelReaderView",
builder: (id) => NovelReaderView(chapterId: id),
),
_genericRoute<ItemType>(
name: "ExtensionLang",
builder: (itemType) => ExtensionsLang(itemType: itemType),
),
_genericRoute(name: "settings", child: const SettingsScreen()),
_genericRoute(name: "appearance", child: const AppearanceScreen()),
_genericRoute<Source>(
name: "extension_detail",
builder: (source) => ExtensionDetail(source: source),
),
_genericRoute<(String?, ItemType)>(
name: "globalSearch",
builder: (data) => GlobalSearchScreen(search: data.$1, itemType: data.$2),
),
_genericRoute(name: "about", child: const AboutScreen()),
_genericRoute(name: "track", child: const TrackScreen()),
_genericRoute(name: "sync", child: const SyncScreen()),
_genericRoute<ItemType>(
name: "sourceFilter",
builder: (itemType) => SourcesFilterScreen(itemType: itemType),
),
_genericRoute(name: "downloadQueue", child: const DownloadQueueScreen()),
_genericRoute<Map<String, dynamic>>(
name: "mangawebview",
builder: (data) => MangaWebView(url: data["url"]!, title: data['title']!),
),
_genericRoute<(bool, int)>(
name: "categories",
builder: (data) => CategoriesScreen(data: data),
),
_genericRoute(name: "statistics", child: const StatisticsScreen()),
_genericRoute(name: "general", child: const GeneralScreen()),
_genericRoute(name: "readerMode", child: const ReaderScreen()),
_genericRoute(name: "browseS", child: const BrowseSScreen()),
_genericRoute<ItemType>(
name: "SourceRepositories",
builder: (itemType) => SourceRepositories(itemType: itemType),
),
_genericRoute(name: "downloads", child: const DownloadsScreen()),
_genericRoute(name: "dataAndStorage", child: const DataAndStorage()),
_genericRoute(name: "security", child: const SecurityScreen()),
_genericRoute(name: "manageTrackers", child: const ManageTrackersScreen()),
_genericRoute<TrackPreference>(
name: "trackingDetail",
builder: (trackerPref) => TrackingDetail(trackerPref: trackerPref),
),
_genericRoute(name: "playerOverview", child: const PlayerOverviewScreen()),
_genericRoute(name: "playerMode", child: const PlayerScreen()),
_genericRoute<int>(
name: "codeEditor",
builder: (sourceId) => CodeEditorPage(sourceId: sourceId),
),
_genericRoute(name: "createExtension", child: const CreateExtension()),
_genericRoute(name: "createBackup", child: const CreateBackup()),
_genericRoute(
name: "customNavigationSettings",
child: const CustomNavigationSettings(),
),
_genericRoute(
name: "customButtonScreen",
child: const CustomButtonScreen(),
),
_genericRoute(
name: "playerDecoderScreen",
child: const PlayerDecoderScreen(),
),
_genericRoute(name: "playerAudioScreen", child: const PlayerAudioScreen()),
_genericRoute(
name: "playerAdvancedScreen",
child: const PlayerAdvancedScreen(),
),
_genericRoute<ItemType?>(
name: "calendarScreen",
builder: (itemType) => CalendarScreen(itemType: itemType),
),
_genericRoute<Manga>(
name: "migrate",
builder: (manga) => MigrationScreen(manga: manga),
),
_genericRoute<(Manga, TrackSearch)>(
name: "migrate/tracker",
builder: (data) => MigrationScreen(manga: data.$1, trackSearch: data.$2),
),
_genericRoute<(String, ItemType, AlgorithmWeights)>(
name: "recommendations",
builder: (data) => RecommendationScreen(
name: data.$1,
itemType: data.$2,
algorithmWeights: data.$3,
),
),
_genericRoute<(String, Track?)>(
name: "watchOrder",
builder: (data) => WatchOrderScreen(name: data.$1, track: data.$2),
),
];
GoRoute _genericRoute<T>({
String? name,
String? path,
Widget Function(T extra)? builder,
Widget? child,
}) {
return GoRoute(
path: path ?? (name != null ? "/$name" : "/"),
name: name,
builder: (context, state) {
if (builder != null) {
final id = state.extra as T;
return builder(id);
} else {
return child!;
}
},
pageBuilder: (Platform.isIOS || Platform.isMacOS)
? (context, state) {
final pageChild = builder != null
? builder(state.extra as T)
: child!;
return transitionPage(key: state.pageKey, child: pageChild);
}
: null,
);
}
}
Page transitionPage({required LocalKey key, required child}) {
return CupertinoPage(key: key, child: child);
}
Route createRoute({required Widget page}) {
return (Platform.isIOS || Platform.isMacOS)
? CupertinoPageRoute(builder: (context) => page)
: MaterialPageRoute(builder: (context) => page);
}