inou-mobile/lib/main.dart

246 lines
6.6 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'core/theme.dart';
import 'core/config.dart';
import 'core/auth_gate.dart';
import 'features/webview/webview_screen.dart';
import 'features/input/input_screen.dart';
import 'features/settings/settings_screen.dart';
import 'features/family/family_screen.dart';
import 'features/auth/login_screen.dart';
import 'services/auth_service.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
// Set system UI overlay style for light theme
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
systemNavigationBarColor: AppTheme.surfaceColor,
systemNavigationBarIconBrightness: Brightness.dark,
));
runApp(const InouApp());
}
class InouApp extends StatefulWidget {
const InouApp({super.key});
// Navigator key for deep linking
static final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@override
State<InouApp> createState() => _InouAppState();
}
class _InouAppState extends State<InouApp> {
final AuthService _authService = AuthService();
bool _isInitialized = false;
@override
void initState() {
super.initState();
_initialize();
}
Future<void> _initialize() async {
await _authService.initialize();
setState(() => _isInitialized = true);
// Listen for auth changes
_authService.addListener(_onAuthChanged);
}
@override
void dispose() {
_authService.removeListener(_onAuthChanged);
super.dispose();
}
void _onAuthChanged() {
// Rebuild when auth state changes
if (mounted) setState(() {});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: InouApp.navigatorKey,
title: AppConfig.appName,
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: ThemeMode.light,
debugShowCheckedModeBanner: false,
home: _buildHome(),
onGenerateRoute: _onGenerateRoute,
);
}
Widget _buildHome() {
// Show loading while initializing
if (!_isInitialized) {
return const _SplashScreen();
}
// Show login if not authenticated
if (_authService.state != AuthState.loggedIn) {
return LoginScreen(
onLoginSuccess: () {
setState(() {}); // Rebuild to show main app
},
);
}
// Show main app wrapped with AuthGate for biometric re-auth
return const AuthGate(
child: MainScaffold(),
);
}
/// Handle deep links: inou.com/app/* should open in WebView
Route<dynamic>? _onGenerateRoute(RouteSettings settings) {
final uri = Uri.tryParse(settings.name ?? '');
if (uri != null && _isInouAppUrl(uri)) {
return MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(
title: const Text(AppConfig.appName),
backgroundColor: AppTheme.backgroundColor,
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.of(context).pop(),
),
),
body: WebViewScreen(
initialUrl: uri.toString(),
showAppBar: false,
),
),
settings: settings,
);
}
return null;
}
bool _isInouAppUrl(Uri uri) {
return uri.host == 'inou.com' && uri.path.startsWith('/app');
}
}
/// Simple splash screen shown during initialization
class _SplashScreen extends StatelessWidget {
const _SplashScreen();
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: InouColors.background,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Logo
Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: InouColors.accent,
borderRadius: BorderRadius.circular(24),
),
child: const Center(
child: Text(
'i',
style: TextStyle(
color: Colors.white,
fontSize: 60,
fontWeight: FontWeight.w300,
),
),
),
),
const SizedBox(height: 24),
const CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(InouColors.accent),
),
],
),
),
);
}
}
class MainScaffold extends StatefulWidget {
const MainScaffold({super.key});
@override
State<MainScaffold> createState() => MainScaffoldState();
}
class MainScaffoldState extends State<MainScaffold> {
int _currentIndex = 0;
final GlobalKey<WebViewScreenState> _webViewKey = GlobalKey<WebViewScreenState>();
/// Navigate the home WebView to a URL (called by FamilyScreen when a dossier is tapped)
void _openInWebView(String url) {
setState(() { _currentIndex = 0; });
// Give IndexedStack a frame to show the WebView before navigating
WidgetsBinding.instance.addPostFrameCallback((_) {
_webViewKey.currentState?.webViewState?.controller.loadRequest(Uri.parse(url));
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: [
WebViewScreen(key: _webViewKey),
const InputScreen(),
FamilyScreen(onOpenDossier: _openInWebView),
const SettingsScreen(),
],
),
bottomNavigationBar: NavigationBar(
selectedIndex: _currentIndex,
onDestinationSelected: (index) {
setState(() {
_currentIndex = index;
});
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.edit_outlined),
selectedIcon: Icon(Icons.edit),
label: 'Input',
),
NavigationDestination(
icon: Icon(Icons.people_outline),
selectedIcon: Icon(Icons.people),
label: 'Family',
),
NavigationDestination(
icon: Icon(Icons.settings_outlined),
selectedIcon: Icon(Icons.settings),
label: 'Settings',
),
],
),
);
}
void navigateToHome() {
setState(() {
_currentIndex = 0;
});
}
}