import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:outbag_app/backend/themes.dart'; import 'package:outbag_app/backend/user.dart'; import 'package:outbag_app/screens/room/edit.dart'; import 'package:outbag_app/screens/room/join.dart'; import 'package:outbag_app/screens/room/members.dart'; import 'package:outbag_app/screens/room/permissions.dart'; import 'package:outbag_app/screens/room/new.dart'; import 'package:outbag_app/screens/settings/main.dart'; import 'package:outbag_app/tools/fetch_wrapper.dart'; import 'package:provider/provider.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import './screens/home.dart'; import './screens/welcome.dart'; import './screens/room/main.dart'; import './screens/auth.dart'; import './backend/request.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); runApp(const OutbagApp()); } final GlobalKey _rootNavigatorKey = GlobalKey(debugLabel: 'root'); final GlobalKey _userShellNavigatorKey = GlobalKey(debugLabel: 'user'); class OutbagApp extends StatefulWidget { const OutbagApp({super.key}); @override State createState() => _OutbagAppState(); } class _OutbagAppState extends State { // assume user is logged in // unless not userdata is found // or the userdata turns out to be wrong User? user; AppTheme theme = AppTheme.auto; void loadTheme() async { // load theme try { final theme = await AppTheme.fromDisk(); setState(() { this.theme = theme; }); } catch (_) {} } void loadUser() async { // load user try { final user = await User.fromDisk(); setState(() { this.user = user; }); } catch (_) { // user unavailable // invalid credentials // log out setState(() { user = null; }); } } @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) async { loadTheme(); loadUser(); }); } Future fetchInfo(User user) async { AccountMeta? info; // try to obtain user account information // with existing details // NOTE: also functions as a way to verify ther data await doNetworkRequest(null, req: () => postWithCreadentials( target: user.server, path: 'getMyAccount', credentials: user, body: {}), onOK: (body) async { final i = AccountMeta.fromJSON(body['data']); info = i; }, onServerErr: (_) { info = null; setState(() { this.user = null; }); return true; }, onNetworkErr: () { info = null; // user is currently offline // approve login, // until user goes back offline // NOTE TODO: check user data once online return true; }); return info; } @override Widget build(BuildContext context) { return MultiProvider( providers: [ Provider.value(value: theme), ], child: MaterialApp.router( title: "Outbag", localizationsDelegates: const [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, AppLocalizations.delegate ], supportedLocales: AppLocalizations.supportedLocales, themeMode: theme.mode, theme: ThemeData(useMaterial3: true, brightness: Brightness.light), darkTheme: ThemeData(useMaterial3: true, brightness: Brightness.dark), routerConfig: GoRouter( navigatorKey: _rootNavigatorKey, initialLocation: '/', redirect: (context, state) async { if (user == null) { // prelogin if (!state.subloc.startsWith('/welcome')) { // prevent unauthorized user from accessing home return '/welcome'; } } else { // post login if (state.subloc.startsWith('/welcome')) { // prevent authorized user from accessing /welcome return '/'; } } return null; }, routes: [ // unauthorized routes GoRoute( name: 'welcome', path: '/welcome', builder: (context, state) => const WelcomePage(), routes: [ GoRoute( name: 'signin', path: 'signin', builder: (context, state) => AuthPage(mode: Mode.signin, refresh: loadUser), ), GoRoute( name: 'signup', path: 'signup', builder: (context, state) => AuthPage(mode: Mode.signup, refresh: loadUser), ), GoRoute( name: 'signup-ota', path: 'signup-ota', builder: (context, state) => AuthPage(mode: Mode.signupOTA, refresh: loadUser), ), ]), // authorized routes ShellRoute( navigatorKey: _userShellNavigatorKey, builder: (context, state, child) => Provider.value( value: user!, child: FutureProvider( initialData: null, child: child, create: (context)=>fetchInfo(context.read()), )), routes: [ GoRoute( path: '/', name: 'home', builder: (context, state) => const HomePage(), routes: [ GoRoute( name: 'settings', path: 'settings', builder: (context, state) => SettingsPage( refreshTheme: loadTheme, refreshUser: loadUser )), GoRoute( path: 'join-room', name: 'add-room', builder: (context, state) => const JoinRoomPage(), routes: [ GoRoute( path: 'new', name: 'new-room', builder: (context, state) => const NewRoomPage()), ]), GoRoute( name: 'room', path: 'r/:server/:id', builder: (context, state) => RoomPage( state.params['server'] ?? '', state.params['id'] ?? ''), routes: [ GoRoute( name: 'edit-room', path: 'edit', builder: (context, state) => EditRoomPage( state.params['server'] ?? '', state.params['id'] ?? '')), GoRoute( name: 'room-members', path: 'members', builder: (context, state) => ManageRoomMembersPage( state.params['server'] ?? '', state.params['id'] ?? '')), GoRoute( name: 'room-permissions', path: 'roles', builder: (context, state) => EditRoomPermissionSetPage( state.params['server'] ?? '', state.params['id'] ?? '')), ]) ]), ]), // routes that can be accessed // with and without an account // i.e the about screen GoRoute( path: '/about', name: 'about', builder: (context, state) => const Text('About')) ]), )); } }