worldhopper/lib/screens/shell/main_shell_screen.dart
Felipe M. 1074e3808d
feat: basic navigation
feat: navigation store

feat: implement OPDS entry caching system

Add comprehensive caching for OPDS publications with local image storage and Dublin Core Terms metadata extraction. This enables offline viewing, fixes Library screen to show book titles and covers, and improves overall UX.

- Add publications table with full metadata (title, authors, series, publisher, ISBN, language)
- Implement local image cache service for covers and thumbnails
- Extract DCTerms metadata (series, publisher, language, ISBN) from OPDS feeds
- Link reading progress to cached publications
- Update Library screen to display cached publication data and covers
- Cache publications automatically when starting to read
- Fix navigation stack issues by using context.push() instead of context.go()
- Add explicit back button to EPUB reader with proper PopScope handling
- Implement UNIQUE constraint on reading_progress to prevent duplicates
- Save reading progress on screen dispose for both EPUB and image readers

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-28 18:24:07 +01:00

78 lines
2.3 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:worldhopper/providers/navigation_provider.dart';
/// Shell widget that provides the bottom navigation bar
/// and handles tab switching for the main app sections
class MainShellScreen extends ConsumerStatefulWidget {
final StatefulNavigationShell navigationShell;
const MainShellScreen({
super.key,
required this.navigationShell,
});
@override
ConsumerState<MainShellScreen> createState() => _MainShellScreenState();
}
class _MainShellScreenState extends ConsumerState<MainShellScreen> {
bool _initialized = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (!_initialized) {
_initialized = true;
// Load saved tab index and navigate to it
final savedIndex = ref.read(navigationStateProvider);
if (savedIndex != widget.navigationShell.currentIndex) {
// Delay navigation to allow widget tree to build
WidgetsBinding.instance.addPostFrameCallback((_) {
widget.navigationShell.goBranch(savedIndex, initialLocation: true);
});
}
}
}
void _onDestinationSelected(int index) {
// Persist the tab change
ref.read(navigationStateProvider.notifier).setTabIndex(index);
// Navigate to the selected tab
widget.navigationShell.goBranch(
index,
initialLocation: index == widget.navigationShell.currentIndex,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: widget.navigationShell,
bottomNavigationBar: NavigationBar(
selectedIndex: widget.navigationShell.currentIndex,
onDestinationSelected: _onDestinationSelected,
destinations: const [
NavigationDestination(
icon: Icon(Icons.auto_stories_outlined),
selectedIcon: Icon(Icons.auto_stories),
label: 'Library',
),
NavigationDestination(
icon: Icon(Icons.dns_outlined),
selectedIcon: Icon(Icons.dns),
label: 'Servers',
),
NavigationDestination(
icon: Icon(Icons.settings_outlined),
selectedIcon: Icon(Icons.settings),
label: 'Settings',
),
],
),
);
}
}