Files
jamshalat-diary/lib/app/router.dart
2026-03-18 00:07:10 +07:00

422 lines
14 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:hive_flutter/hive_flutter.dart';
import '../data/local/hive_boxes.dart';
import '../data/local/models/app_settings.dart';
import '../core/widgets/bottom_nav_bar.dart';
import '../features/dashboard/presentation/dashboard_screen.dart';
import '../features/imsakiyah/presentation/imsakiyah_screen.dart';
import '../features/checklist/presentation/checklist_screen.dart';
import '../features/laporan/presentation/laporan_screen.dart';
import '../features/tools/presentation/tools_screen.dart';
import '../features/dzikir/presentation/dzikir_screen.dart';
import '../features/doa/presentation/doa_screen.dart';
import '../features/hadits/presentation/hadits_screen.dart';
import '../features/qibla/presentation/qibla_screen.dart';
import '../features/quran/presentation/quran_screen.dart';
import '../features/quran/presentation/quran_reading_screen.dart';
import '../features/quran/presentation/quran_murattal_screen.dart';
import '../features/quran/presentation/quran_bookmarks_screen.dart';
import '../features/quran/presentation/quran_enrichment_screen.dart';
import '../features/notifications/presentation/notification_center_screen.dart';
import '../features/settings/presentation/settings_screen.dart';
/// Navigation key for the shell navigator (bottom-nav screens).
final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _shellNavigatorKey = GlobalKey<NavigatorState>();
/// GoRouter configuration per PRD §5.2.
final GoRouter appRouter = GoRouter(
navigatorKey: _rootNavigatorKey,
initialLocation: '/',
routes: [
// ── Shell route (bottom nav persists) ──
ShellRoute(
navigatorKey: _shellNavigatorKey,
pageBuilder: (context, state, child) => NoTransitionPage(
key: ValueKey<String>('shell-${state.pageKey.value}'),
child: _ScaffoldWithNav(child: child),
),
routes: [
GoRoute(
path: '/',
pageBuilder: (context, state) => NoTransitionPage(
key: state.pageKey,
child: const DashboardScreen(),
),
routes: [
GoRoute(
path: 'qibla',
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state) => const QiblaScreen(),
),
],
),
GoRoute(
path: '/imsakiyah',
pageBuilder: (context, state) => NoTransitionPage(
key: state.pageKey,
child: const ImsakiyahScreen(),
),
),
GoRoute(
path: '/checklist',
pageBuilder: (context, state) => NoTransitionPage(
key: state.pageKey,
child: const ChecklistScreen(),
),
),
GoRoute(
path: '/laporan',
pageBuilder: (context, state) => NoTransitionPage(
key: state.pageKey,
child: const LaporanScreen(),
),
),
GoRoute(
path: '/tools',
pageBuilder: (context, state) => NoTransitionPage(
key: state.pageKey,
child: const ToolsScreen(),
),
routes: [
GoRoute(
path: 'dzikir',
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state) => const DzikirScreen(),
),
GoRoute(
path: 'quran',
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state) => const QuranScreen(),
routes: [
GoRoute(
path: 'enrichment',
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state) => const QuranEnrichmentScreen(),
),
GoRoute(
path: 'bookmarks',
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state) => const QuranBookmarksScreen(),
),
GoRoute(
path: ':surahId',
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state) {
final surahId = state.pathParameters['surahId']!;
final startVerse = int.tryParse(
state.uri.queryParameters['startVerse'] ?? '');
return QuranReadingScreen(
surahId: surahId, initialVerse: startVerse);
},
routes: [
GoRoute(
path: 'murattal',
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state) {
final surahId = state.pathParameters['surahId']!;
final qariId = state.uri.queryParameters['qariId'];
final autoplay =
state.uri.queryParameters['autoplay'] == 'true';
return QuranMurattalScreen(
surahId: surahId,
initialQariId: qariId,
autoPlay: autoplay,
);
},
),
],
),
],
),
GoRoute(
path: 'qibla',
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state) => const QiblaScreen(),
),
GoRoute(
path: 'doa',
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state) => const DoaScreen(),
),
GoRoute(
path: 'hadits',
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state) => const HaditsScreen(),
),
],
),
// Simple Mode Tab: Zikir
GoRoute(
path: '/dzikir',
builder: (context, state) =>
const DzikirScreen(isSimpleModeTab: true),
),
// Simple Mode Tab: Tilawah
GoRoute(
path: '/quran',
builder: (context, state) => const QuranScreen(isSimpleModeTab: true),
routes: [
GoRoute(
path: 'enrichment',
builder: (context, state) =>
const QuranEnrichmentScreen(isSimpleModeTab: true),
),
GoRoute(
path: 'bookmarks',
builder: (context, state) =>
const QuranBookmarksScreen(isSimpleModeTab: true),
),
GoRoute(
path: ':surahId',
builder: (context, state) {
final surahId = state.pathParameters['surahId']!;
final startVerse =
int.tryParse(state.uri.queryParameters['startVerse'] ?? '');
return QuranReadingScreen(
surahId: surahId,
initialVerse: startVerse,
isSimpleModeTab: true);
},
routes: [
GoRoute(
path: 'murattal',
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state) {
final surahId = state.pathParameters['surahId']!;
final qariId = state.uri.queryParameters['qariId'];
final autoplay =
state.uri.queryParameters['autoplay'] == 'true';
return QuranMurattalScreen(
surahId: surahId,
initialQariId: qariId,
autoPlay: autoplay,
isSimpleModeTab: true,
);
},
),
],
),
],
),
GoRoute(
path: '/doa',
builder: (context, state) => const DoaScreen(isSimpleModeTab: true),
),
GoRoute(
path: '/hadits',
builder: (context, state) =>
const HaditsScreen(isSimpleModeTab: true),
),
],
),
// ── Settings (pushed, no bottom nav) ──
GoRoute(
path: '/notifications',
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state) => const NotificationCenterScreen(),
),
GoRoute(
path: '/settings',
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state) => const SettingsScreen(),
),
],
);
/// Scaffold wrapper that provides the persistent bottom nav bar.
class _ScaffoldWithNav extends StatefulWidget {
const _ScaffoldWithNav({required this.child});
final Widget child;
@override
State<_ScaffoldWithNav> createState() => _ScaffoldWithNavState();
}
class _ScaffoldWithNavState extends State<_ScaffoldWithNav> {
DateTime? _lastBackPressedAt;
bool _shouldHideBottomNav({
required bool isSimpleMode,
required String path,
}) {
if (!isSimpleMode) return false;
if (path == '/dzikir') return true;
if (!path.startsWith('/quran/')) return false;
final tail = path.substring('/quran/'.length);
if (tail == 'bookmarks' || tail == 'enrichment') return false;
return !tail.contains('/');
}
/// Maps route locations to bottom nav indices.
int _currentIndex(BuildContext context) {
final location = GoRouterState.of(context).uri.toString();
final box = Hive.box<AppSettings>(HiveBoxes.settings);
final isSimpleMode = box.get('default')?.simpleMode ?? false;
if (isSimpleMode) {
if (location.startsWith('/imsakiyah')) return 1;
if (location.startsWith('/quran')) return 2;
if (location.startsWith('/dzikir')) return 3;
if (location.startsWith('/tools') ||
location.startsWith('/doa') ||
location.startsWith('/hadits')) {
return 4;
}
return 0;
} else {
if (location.startsWith('/imsakiyah')) return 1;
if (location.startsWith('/checklist')) return 2;
if (location.startsWith('/laporan')) return 3;
if (location.startsWith('/tools')) return 4;
return 0;
}
}
void _onTap(BuildContext context, int index) {
final box = Hive.box<AppSettings>(HiveBoxes.settings);
final isSimpleMode = box.get('default')?.simpleMode ?? false;
if (isSimpleMode) {
switch (index) {
case 0:
context.go('/');
break;
case 1:
context.go('/imsakiyah');
break;
case 2:
context.go('/quran');
break;
case 3:
context.go('/dzikir');
break;
case 4:
context.go('/tools');
break;
}
} else {
switch (index) {
case 0:
context.go('/');
break;
case 1:
context.go('/imsakiyah');
break;
case 2:
context.go('/checklist');
break;
case 3:
context.go('/laporan');
break;
case 4:
context.go('/tools');
break;
}
}
}
bool _isMainShellRoute({
required bool isSimpleMode,
required String path,
}) {
if (isSimpleMode) {
return path == '/' ||
path == '/imsakiyah' ||
path == '/quran' ||
path == '/dzikir' ||
path == '/tools';
}
return path == '/' ||
path == '/imsakiyah' ||
path == '/checklist' ||
path == '/laporan' ||
path == '/tools';
}
Future<void> _handleMainRouteBack(
BuildContext context, {
required String path,
}) async {
if (path != '/') {
context.go('/');
return;
}
final now = DateTime.now();
final pressedRecently = _lastBackPressedAt != null &&
now.difference(_lastBackPressedAt!) <= const Duration(seconds: 2);
if (pressedRecently) {
await SystemNavigator.pop();
return;
}
_lastBackPressedAt = now;
final messenger = ScaffoldMessenger.maybeOf(context);
messenger
?..hideCurrentSnackBar()
..showSnackBar(
const SnackBar(
content: Text('Tekan sekali lagi untuk keluar'),
duration: Duration(seconds: 2),
),
);
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<Box<AppSettings>>(
valueListenable: Hive.box<AppSettings>(HiveBoxes.settings).listenable(),
builder: (context, box, _) {
final isSimpleMode = box.get('default')?.simpleMode ?? false;
final path = GoRouterState.of(context).uri.path;
final hideBottomNav = _shouldHideBottomNav(
isSimpleMode: isSimpleMode,
path: path,
);
final modeScopedChild = KeyedSubtree(
key: ValueKey(
'shell:$path:${isSimpleMode ? 'simple' : 'full'}',
),
child: widget.child,
);
final pageBody = hideBottomNav
? SafeArea(
top: false,
child: modeScopedChild,
)
: modeScopedChild;
final handleBackInShell =
defaultTargetPlatform == TargetPlatform.android &&
_isMainShellRoute(isSimpleMode: isSimpleMode, path: path);
return PopScope(
canPop: !handleBackInShell,
onPopInvokedWithResult: (didPop, _) async {
if (didPop || !handleBackInShell) return;
await _handleMainRouteBack(context, path: path);
},
child: Scaffold(
body: pageBody,
bottomNavigationBar: hideBottomNav
? null
: AppBottomNavBar(
currentIndex: _currentIndex(context),
onTap: (i) => _onTap(context, i),
),
),
);
},
);
}
}