Files
pantry-flutter/lib/main.dart

242 lines
7.7 KiB
Dart

import 'dart:async';
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'i18n.dart';
import 'services/auth_service.dart';
import 'services/background_notification_task.dart';
import 'services/locale_service.dart';
import 'services/category_service.dart';
import 'services/checklist_service.dart';
import 'services/house_service.dart';
import 'services/local_notifications_service.dart';
import 'services/note_service.dart';
import 'services/photo_service.dart';
import 'services/prefs_service.dart';
import 'services/share_intent_service.dart';
import 'services/theming_service.dart';
import 'views/home/home_view.dart';
import 'views/login/login_view.dart';
import 'views/notifications_intro/notifications_intro_view.dart';
final rootNavigatorKey = GlobalKey<NavigatorState>();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
if (kDebugMode) {
WakelockPlus.enable();
}
await AuthService.instance.loadCredentials();
await PrefsService.instance.load();
await LocalNotificationsService.instance.init();
if (AuthService.instance.isLoggedIn) {
await Future.wait([
ThemingService.instance.fetchTheme(),
HouseService.instance.cache.load(),
CategoryService.instance.cache.load(),
ChecklistService.instance.cache.load(),
PhotoService.instance.cache.load(),
NoteService.instance.cache.load(),
]);
// Kick off the periodic background poll if notifications are enabled.
if (PrefsService.instance.notificationsEnabled) {
unawaited(registerBackgroundNotificationPoll());
}
}
LocaleService.instance.apply();
unawaited(ShareIntentService.instance.init());
runApp(const PantryApp());
}
class PantryApp extends StatefulWidget {
const PantryApp({super.key});
@override
State<PantryApp> createState() => PantryAppState();
}
class _EscapePopWrapper extends StatelessWidget {
final Widget child;
const _EscapePopWrapper({required this.child});
@override
Widget build(BuildContext context) {
return Shortcuts(
shortcuts: const <ShortcutActivator, Intent>{
SingleActivator(LogicalKeyboardKey.escape): _PopRouteIntent(),
},
child: Actions(
actions: <Type, Action<Intent>>{_PopRouteIntent: _PopRouteAction()},
child: child,
),
);
}
}
class _PopRouteIntent extends Intent {
const _PopRouteIntent();
}
class _PopRouteAction extends Action<_PopRouteIntent> {
@override
Object? invoke(covariant _PopRouteIntent intent) {
final nav = rootNavigatorKey.currentState;
if (nav?.canPop() == true) {
nav!.maybePop();
}
return null;
}
}
class PantryAppState extends State<PantryApp> {
bool _isLoggedIn = AuthService.instance.isLoggedIn;
@override
void initState() {
super.initState();
LocaleService.instance.addListener(_rebuild);
ThemingService.instance.addListener(_rebuild);
}
@override
void dispose() {
LocaleService.instance.removeListener(_rebuild);
ThemingService.instance.removeListener(_rebuild);
super.dispose();
}
void _rebuild() {
if (mounted) setState(() {});
}
Future<void> _onLoginSuccess() async {
await ThemingService.instance.fetchTheme();
_isLoggedIn = true;
final nextRoute = PrefsService.instance.notificationsIntroSeen
? '/home'
: '/notifications-intro';
rootNavigatorKey.currentState?.pushReplacementNamed(nextRoute);
if (mounted) setState(() {});
}
void _onIntroDone() {
rootNavigatorKey.currentState?.pushReplacementNamed('/home');
}
Future<void> _onLogout() async {
await cancelBackgroundNotificationPoll();
await LocalNotificationsService.instance.cancelAll();
await AuthService.instance.logout();
ThemingService.instance.clear();
await Future.wait([
PrefsService.instance.clear(),
HouseService.instance.cache.clear(),
CategoryService.instance.cache.clear(),
ChecklistService.instance.cache.clear(),
PhotoService.instance.cache.clear(),
NoteService.instance.cache.clear(),
]);
_isLoggedIn = false;
rootNavigatorKey.currentState?.pushReplacementNamed('/login');
if (mounted) setState(() {});
}
@override
Widget build(BuildContext context) {
final color = ThemingService.instance.effectiveColor;
final locale = LocaleService.instance.effectiveLocale;
final isMacOS = !kIsWeb && Platform.isMacOS;
final isDesktopHost =
kIsWeb ||
(!kIsWeb &&
(Platform.isMacOS || Platform.isWindows || Platform.isLinux));
final appBarTheme = isMacOS ? const AppBarTheme(toolbarHeight: 66) : null;
return ChangeNotifierProvider<PrefsService>.value(
value: PrefsService.instance,
child: Directionality(
textDirection: LocaleService.instance.textDirection,
child: MaterialApp(
key: ValueKey(locale),
debugShowCheckedModeBanner: false,
navigatorKey: rootNavigatorKey,
locale: locale,
supportedLocales: supportedLocales,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
title: m.common.appTitle,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: color,
).copyWith(primary: color),
useMaterial3: true,
appBarTheme: appBarTheme,
popupMenuTheme: PopupMenuThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
elevation: 8,
position: PopupMenuPosition.under,
),
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: color,
brightness: Brightness.dark,
).copyWith(primary: color),
useMaterial3: true,
appBarTheme: appBarTheme,
popupMenuTheme: PopupMenuThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
elevation: 8,
position: PopupMenuPosition.under,
),
),
themeMode: ThemingService.instance.themeMode,
builder: (context, child) {
if (child == null) return const SizedBox.shrink();
if (!isDesktopHost) return child;
return _EscapePopWrapper(child: child);
},
onGenerateInitialRoutes: (initialRoute) => [
MaterialPageRoute(
builder: (_) => _isLoggedIn
? (PrefsService.instance.notificationsIntroSeen
? HomeView(onLogout: _onLogout)
: NotificationsIntroView(onDone: _onIntroDone))
: LoginView(onLoginSuccess: _onLoginSuccess),
),
],
onGenerateRoute: (settings) {
switch (settings.name) {
case '/home':
return MaterialPageRoute(
builder: (_) => HomeView(onLogout: _onLogout),
);
case '/notifications-intro':
return MaterialPageRoute(
builder: (_) => NotificationsIntroView(onDone: _onIntroDone),
);
case '/login':
default:
return MaterialPageRoute(
builder: (_) => LoginView(onLoginSuccess: _onLoginSuccess),
);
}
},
),
),
);
}
}