import 'dart:async'; import 'dart:io'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; import 'package:provider/provider.dart'; import 'package:simplecloudnotifier/main_messaging.dart'; import 'package:simplecloudnotifier/main_utils.dart'; import 'package:simplecloudnotifier/components/layout/nav_layout.dart'; import 'package:simplecloudnotifier/state/app_settings.dart'; import 'package:simplecloudnotifier/state/app_bar_state.dart'; import 'package:simplecloudnotifier/state/app_theme.dart'; import 'package:simplecloudnotifier/state/application_log.dart'; import 'package:simplecloudnotifier/state/globals.dart'; import 'package:simplecloudnotifier/state/app_auth.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:simplecloudnotifier/utils/navi.dart'; import 'package:toastification/toastification.dart'; import 'firebase_options.dart'; void main() async { print('[INIT] Application starting...'); print('[INIT] Ensure WidgetsFlutterBinding...'); WidgetsFlutterBinding.ensureInitialized(); print('[INIT] Init Globals...'); await Globals().init(); print('[INIT] Init Hive...'); await initHive(); print('[INIT] Register Hive-Adapters'); await registerHiveAdapter(); print('[INIT] Open Hive-Boxes'); await openHiveBoxes(true); print('[INIT] Load AppAuth...'); final appAuth = AppAuth(); // ensure UserAccount is loaded if (appAuth.isAuth()) { // load user+client in background () async { try { await appAuth.loadUser(); } catch (exc, trace) { ApplicationLog.error('Failed to load user (background load on startup): ' + exc.toString(), trace: trace); ApplicationLog.writeRawFailure('Failed to load user (background load on startup)', {'error': exc.toString(), 'trace': trace}); } try { await appAuth.loadClient(); } catch (exc, trace) { ApplicationLog.error('Failed to load user (background load on startup): ' + exc.toString(), trace: trace); ApplicationLog.writeRawFailure('Failed to load user (background load on startup)', {'error': exc.toString(), 'trace': trace}); } }(); } if (!Platform.isLinux) { print('[INIT] Init Firebase...'); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); print('[INIT] Request Notification permissions...'); await FirebaseMessaging.instance.requestPermission(provisional: true); FirebaseMessaging.instance.onTokenRefresh.listen((fcmToken) { try { setFirebaseToken(fcmToken); } catch (exc, trace) { ApplicationLog.error('Failed to set firebase token: ' + exc.toString(), trace: trace); } }).onError((dynamic err) { ApplicationLog.error('Failed to listen to token refresh events: ' + (err?.toString() ?? '')); }); try { print('[INIT] Query firebase token...'); final fcmToken = await FirebaseMessaging.instance.getToken(); if (fcmToken != null) { setFirebaseToken(fcmToken); } } catch (exc, trace) { ApplicationLog.error('Failed to get+set firebase token: ' + exc.toString(), trace: trace); } FirebaseMessaging.onBackgroundMessage(onBackgroundMessage); FirebaseMessaging.onMessage.listen(onForegroundMessage); } else { print('[INIT] Skip Firebase init (Platform == Linux)...'); } await appAuth.tryMigrateFromV1(); print('[INIT] Load Notifications...'); final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); final flutterLocalNotificationsPluginImpl = flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation(); if (flutterLocalNotificationsPluginImpl == null) { ApplicationLog.error('Failed to get AndroidFlutterLocalNotificationsPlugin', trace: StackTrace.current); } else { flutterLocalNotificationsPluginImpl.requestNotificationsPermission(); final initializationSettingsAndroid = AndroidInitializationSettings('ic_notification_white'); final initializationSettingsDarwin = DarwinInitializationSettings( requestAlertPermission: true, requestBadgePermission: true, requestSoundPermission: true, onDidReceiveLocalNotification: receiveLocalDarwinNotification, notificationCategories: getDarwinNotificationCategories(), ); final initializationSettingsLinux = LinuxInitializationSettings(defaultActionName: 'Open notification'); final initializationSettings = InitializationSettings( android: initializationSettingsAndroid, iOS: initializationSettingsDarwin, linux: initializationSettingsLinux, ); flutterLocalNotificationsPlugin.initialize( initializationSettings, onDidReceiveNotificationResponse: receiveLocalNotification, onDidReceiveBackgroundNotificationResponse: notificationTapBackground, ); final appLaunchNotification = await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails(); if (appLaunchNotification != null) { // Use has launched SCN by clicking on a local notifiaction, if it was a summary or message notifiaction open the corresponding screen // This is android only //TODO same on iOS, somehow?? ApplicationLog.info('App launched by notification: ${appLaunchNotification.notificationResponse?.id}'); handleNotificationClickAction(appLaunchNotification.notificationResponse?.payload, Duration(milliseconds: 600)); } } ApplicationLog.debug('[INIT] Application started'); Globals().appWidgetInitialized = true; runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => AppAuth(), lazy: false), ChangeNotifierProvider(create: (context) => AppTheme(), lazy: false), ChangeNotifierProvider(create: (context) => AppBarState(), lazy: false), ChangeNotifierProvider(create: (context) => AppSettings(), lazy: false), ], child: SCNApp(), ), ); } class SCNApp extends StatefulWidget { SCNApp({super.key}); static var materialKey = GlobalKey(); @override State createState() => _SCNAppState(); } class _SCNAppState extends State { StreamSubscription>? _purchaseSubscription; @override void initState() { if (Globals().clientType == 'IOS' || Globals().clientType == 'ANDROID') { _purchaseSubscription = InAppPurchase.instance.purchaseStream.listen( purchaseUpdated, onDone: () => _purchaseSubscription?.cancel(), onError: purchaseError, ); } super.initState(); } @override void dispose() { _purchaseSubscription?.cancel(); _purchaseSubscription = null; super.dispose(); } void purchaseUpdated(List purchaseDetailsList) { // see https://pub.dev/packages/in_app_purchase for (var purchaseDetails in purchaseDetailsList) { ApplicationLog.debug('Purchase ${purchaseDetails.productID} is ${purchaseDetails.status.toString()}'); //TODO } } void purchaseError(dynamic error) { // TODO handle error here. ApplicationLog.error('Purchase error: ${error.toString()}', trace: StackTrace.current); } @override Widget build(BuildContext context) { return ToastificationWrapper( config: ToastificationConfig( itemWidth: 440, marginBuilder: (context, alignment) => EdgeInsets.symmetric(vertical: 64), animationDuration: Duration(milliseconds: 200), ), child: Consumer( builder: (context, appTheme, child) => MaterialApp( navigatorKey: SCNApp.materialKey, title: 'SimpleCloudNotifier', navigatorObservers: [Navi.routeObserver, Navi.modalRouteObserver], theme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: appTheme.color.value, brightness: appTheme.darkMode ? Brightness.dark : Brightness.light, ), useMaterial3: true, ), home: SCNNavLayout(), ), ), ); } }