- Introduced a new Flutter project for the cxyz Android manga/comic reader, including essential files such as pubspec.yaml, AndroidManifest.xml, and Gradle configuration. - Added .gitignore files to exclude unnecessary files and directories for both the main project and Android-specific files. - Implemented initial README.md with project description, features, setup instructions, and API details for ComicLand, MangaRead, and MangaTown. - Configured analysis options and dependencies in pubspec.yaml to support Flutter development. - Established a pnpm workspace and lockfile for dependency management. - Included various Android resources such as icons, styles, and launch backgrounds for a complete application setup.
156 lines
4.9 KiB
Dart
156 lines
4.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
|
|
import '../services/reader_direction.dart';
|
|
import '../sources/source.dart';
|
|
import '../sources/source_flags.dart';
|
|
import '../ui/catalog/source_picker_page.dart';
|
|
import '../ui/catalog_page.dart';
|
|
import '../ui/downloads_page.dart';
|
|
import '../ui/history_page.dart';
|
|
import '../ui/library_page.dart';
|
|
import '../ui/reader_page.dart';
|
|
import '../ui/settings_page.dart';
|
|
import '../ui/shell.dart';
|
|
import '../ui/title_page.dart';
|
|
|
|
final _rootKey = GlobalKey<NavigatorState>();
|
|
|
|
SourceId? _parseSourceId(String? raw) {
|
|
if (raw == null) return null;
|
|
return SourceId.tryParse(raw);
|
|
}
|
|
|
|
CatalogMenuKind? _parseMenu(String? raw) {
|
|
if (raw == null) return null;
|
|
for (final k in CatalogMenuKind.values) {
|
|
if (k.pathSegment == raw) return k;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
final routerProvider = Provider<GoRouter>((ref) {
|
|
return GoRouter(
|
|
navigatorKey: _rootKey,
|
|
initialLocation: '/catalog',
|
|
routes: [
|
|
StatefulShellRoute.indexedStack(
|
|
builder: (context, state, navigationShell) {
|
|
return AppShell(navigationShell: navigationShell);
|
|
},
|
|
branches: [
|
|
StatefulShellBranch(
|
|
routes: [
|
|
// Sibling routes (not nested under picker) so back stack is clean.
|
|
GoRoute(
|
|
path: '/catalog',
|
|
redirect: (context, state) {
|
|
final enabled = enabledSourceIds();
|
|
if (enabled.length == 1) {
|
|
return '/catalog/${enabled.first.name}';
|
|
}
|
|
return null;
|
|
},
|
|
builder: (context, state) => const SourcePickerPage(),
|
|
),
|
|
GoRoute(
|
|
path: '/catalog/:sourceId',
|
|
builder: (context, state) {
|
|
final id = _parseSourceId(state.pathParameters['sourceId']);
|
|
if (id == null) {
|
|
return const Scaffold(
|
|
body: Center(child: Text('Unknown source')),
|
|
);
|
|
}
|
|
final single = enabledSourceIds().length == 1;
|
|
return SourceHomePage(
|
|
sourceId: id,
|
|
embeddedAsRoot: single,
|
|
);
|
|
},
|
|
routes: [
|
|
GoRoute(
|
|
path: ':menu',
|
|
builder: (context, state) {
|
|
final id =
|
|
_parseSourceId(state.pathParameters['sourceId']);
|
|
final menu = _parseMenu(state.pathParameters['menu']);
|
|
if (id == null || menu == null) {
|
|
return const Scaffold(
|
|
body: Center(child: Text('Unknown catalog feed')),
|
|
);
|
|
}
|
|
return CatalogFeedPage(sourceId: id, menuKind: menu);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
StatefulShellBranch(
|
|
routes: [
|
|
GoRoute(
|
|
path: '/library',
|
|
builder: (context, state) => const LibraryPage(),
|
|
),
|
|
],
|
|
),
|
|
StatefulShellBranch(
|
|
routes: [
|
|
GoRoute(
|
|
path: '/history',
|
|
builder: (context, state) => const HistoryPage(),
|
|
),
|
|
],
|
|
),
|
|
StatefulShellBranch(
|
|
routes: [
|
|
GoRoute(
|
|
path: '/downloads',
|
|
builder: (context, state) => const DownloadsPage(),
|
|
routes: [
|
|
GoRoute(
|
|
path: 'queue',
|
|
builder: (context, state) => const DownloadsQueuePage(),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
GoRoute(
|
|
path: '/settings',
|
|
builder: (context, state) => const SettingsPage(),
|
|
),
|
|
GoRoute(
|
|
path: '/title/:id',
|
|
builder: (context, state) {
|
|
final id = int.parse(state.pathParameters['id']!);
|
|
return TitlePage(titleId: id);
|
|
},
|
|
),
|
|
GoRoute(
|
|
path: '/read/:titleId/:chapterId',
|
|
builder: (context, state) {
|
|
final titleId = int.parse(state.pathParameters['titleId']!);
|
|
final chapterId = int.parse(state.pathParameters['chapterId']!);
|
|
final modeParam = state.uri.queryParameters['mode'];
|
|
ReaderMode? initialMode;
|
|
if (modeParam == 'paged') {
|
|
initialMode = ReaderMode.paged;
|
|
} else if (modeParam == 'webtoon') {
|
|
initialMode = ReaderMode.webtoon;
|
|
}
|
|
return ReaderPage(
|
|
titleId: titleId,
|
|
chapterId: chapterId,
|
|
initialMode: initialMode,
|
|
);
|
|
},
|
|
),
|
|
],
|
|
);
|
|
});
|