246 lines
6.6 KiB
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;
|
|
});
|
|
}
|
|
}
|