mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-22 18:33:45 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			421 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			421 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'dart:io';
 | |
| 
 | |
| import 'package:flutter/material.dart';
 | |
| import 'package:flutter/services.dart';
 | |
| import 'package:obtainium/pages/home.dart';
 | |
| import 'package:obtainium/providers/apps_provider.dart';
 | |
| import 'package:obtainium/providers/logs_provider.dart';
 | |
| import 'package:obtainium/providers/native_provider.dart';
 | |
| import 'package:obtainium/providers/notifications_provider.dart';
 | |
| import 'package:obtainium/providers/settings_provider.dart';
 | |
| import 'package:obtainium/providers/source_provider.dart';
 | |
| import 'package:provider/provider.dart';
 | |
| import 'package:dynamic_color/dynamic_color.dart';
 | |
| import 'package:device_info_plus/device_info_plus.dart';
 | |
| import 'package:background_fetch/background_fetch.dart';
 | |
| import 'package:easy_localization/easy_localization.dart';
 | |
| // ignore: implementation_imports
 | |
| import 'package:easy_localization/src/easy_localization_controller.dart';
 | |
| // ignore: implementation_imports
 | |
| import 'package:easy_localization/src/localization.dart';
 | |
| import 'package:flutter_foreground_task/flutter_foreground_task.dart';
 | |
| 
 | |
| List<MapEntry<Locale, String>> supportedLocales = const [
 | |
|   MapEntry(Locale('en'), 'English'),
 | |
|   MapEntry(Locale('zh'), '简体中文'),
 | |
|   MapEntry(Locale('zh', 'Hant_TW'), '臺灣話'),
 | |
|   MapEntry(Locale('it'), 'Italiano'),
 | |
|   MapEntry(Locale('ja'), '日本語'),
 | |
|   MapEntry(Locale('hu'), 'Magyar'),
 | |
|   MapEntry(Locale('de'), 'Deutsch'),
 | |
|   MapEntry(Locale('fa'), 'فارسی'),
 | |
|   MapEntry(Locale('fr'), 'Français'),
 | |
|   MapEntry(Locale('es'), 'Español'),
 | |
|   MapEntry(Locale('pl'), 'Polski'),
 | |
|   MapEntry(Locale('ru'), 'Русский'),
 | |
|   MapEntry(Locale('bs'), 'Bosanski'),
 | |
|   MapEntry(Locale('pt'), 'Português'),
 | |
|   MapEntry(Locale('pt', 'BR'), 'Brasileiro'),
 | |
|   MapEntry(Locale('cs'), 'Česky'),
 | |
|   MapEntry(Locale('sv'), 'Svenska'),
 | |
|   MapEntry(Locale('nl'), 'Nederlands'),
 | |
|   MapEntry(Locale('vi'), 'Tiếng Việt'),
 | |
|   MapEntry(Locale('tr'), 'Türkçe'),
 | |
|   MapEntry(Locale('uk'), 'Українська'),
 | |
|   MapEntry(Locale('da'), 'Dansk'),
 | |
|   MapEntry(
 | |
|     Locale('en', 'EO'),
 | |
|     'Esperanto',
 | |
|   ), // https://github.com/aissat/easy_localization/issues/220#issuecomment-846035493
 | |
|   MapEntry(Locale('in'), 'Bahasa Indonesia'),
 | |
|   MapEntry(Locale('ko'), '한국어'),
 | |
|   MapEntry(Locale('ca'), 'Català'),
 | |
|   MapEntry(Locale('ar'), 'العربية'),
 | |
|   MapEntry(Locale('ml'), 'മലയാളം'),
 | |
| ];
 | |
| const fallbackLocale = Locale('en');
 | |
| const localeDir = 'assets/translations';
 | |
| var fdroid = false;
 | |
| 
 | |
| final globalNavigatorKey = GlobalKey<NavigatorState>();
 | |
| 
 | |
| Future<void> loadTranslations() async {
 | |
|   // See easy_localization/issues/210
 | |
|   await EasyLocalizationController.initEasyLocation();
 | |
|   var s = SettingsProvider();
 | |
|   await s.initializeSettings();
 | |
|   var forceLocale = s.forcedLocale;
 | |
|   final controller = EasyLocalizationController(
 | |
|     saveLocale: true,
 | |
|     forceLocale: forceLocale,
 | |
|     fallbackLocale: fallbackLocale,
 | |
|     supportedLocales: supportedLocales.map((e) => e.key).toList(),
 | |
|     assetLoader: const RootBundleAssetLoader(),
 | |
|     useOnlyLangCode: false,
 | |
|     useFallbackTranslations: true,
 | |
|     path: localeDir,
 | |
|     onLoadError: (FlutterError e) {
 | |
|       throw e;
 | |
|     },
 | |
|   );
 | |
|   await controller.loadTranslations();
 | |
|   Localization.load(
 | |
|     controller.locale,
 | |
|     translations: controller.translations,
 | |
|     fallbackTranslations: controller.fallbackTranslations,
 | |
|   );
 | |
| }
 | |
| 
 | |
| @pragma('vm:entry-point')
 | |
| void backgroundFetchHeadlessTask(HeadlessTask task) async {
 | |
|   String taskId = task.taskId;
 | |
|   bool isTimeout = task.timeout;
 | |
|   if (isTimeout) {
 | |
|     print('BG update task timed out.');
 | |
|     BackgroundFetch.finish(taskId);
 | |
|     return;
 | |
|   }
 | |
|   await bgUpdateCheck(taskId, null);
 | |
|   BackgroundFetch.finish(taskId);
 | |
| }
 | |
| 
 | |
| @pragma('vm:entry-point')
 | |
| void startCallback() {
 | |
|   FlutterForegroundTask.setTaskHandler(MyTaskHandler());
 | |
| }
 | |
| 
 | |
| class MyTaskHandler extends TaskHandler {
 | |
|   static const String incrementCountCommand = 'incrementCount';
 | |
| 
 | |
|   @override
 | |
|   Future<void> onStart(DateTime timestamp, TaskStarter starter) async {
 | |
|     print('onStart(starter: ${starter.name})');
 | |
|     bgUpdateCheck('bg_check', null);
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void onRepeatEvent(DateTime timestamp) {
 | |
|     bgUpdateCheck('bg_check', null);
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   Future<void> onDestroy(DateTime timestamp, bool isTimeout) async {
 | |
|     print('Foreground service onDestroy(isTimeout: $isTimeout)');
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void onReceiveData(Object data) {}
 | |
| }
 | |
| 
 | |
| void main() async {
 | |
|   WidgetsFlutterBinding.ensureInitialized();
 | |
|   try {
 | |
|     ByteData data = await PlatformAssetBundle().load(
 | |
|       'assets/ca/lets-encrypt-r3.pem',
 | |
|     );
 | |
|     SecurityContext.defaultContext.setTrustedCertificatesBytes(
 | |
|       data.buffer.asUint8List(),
 | |
|     );
 | |
|   } catch (e) {
 | |
|     // Already added, do nothing (see #375)
 | |
|   }
 | |
|   await EasyLocalization.ensureInitialized();
 | |
|   if ((await DeviceInfoPlugin().androidInfo).version.sdkInt >= 29) {
 | |
|     SystemChrome.setSystemUIOverlayStyle(
 | |
|       const SystemUiOverlayStyle(systemNavigationBarColor: Colors.transparent),
 | |
|     );
 | |
|     SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
 | |
|   }
 | |
|   final np = NotificationsProvider();
 | |
|   await np.initialize();
 | |
|   FlutterForegroundTask.initCommunicationPort();
 | |
|   runApp(
 | |
|     MultiProvider(
 | |
|       providers: [
 | |
|         ChangeNotifierProvider(create: (context) => AppsProvider()),
 | |
|         ChangeNotifierProvider(create: (context) => SettingsProvider()),
 | |
|         Provider(create: (context) => np),
 | |
|         Provider(create: (context) => LogsProvider()),
 | |
|       ],
 | |
|       child: EasyLocalization(
 | |
|         supportedLocales: supportedLocales.map((e) => e.key).toList(),
 | |
|         path: localeDir,
 | |
|         fallbackLocale: fallbackLocale,
 | |
|         useOnlyLangCode: false,
 | |
|         child: const Obtainium(),
 | |
|       ),
 | |
|     ),
 | |
|   );
 | |
|   BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask);
 | |
| }
 | |
| 
 | |
| class Obtainium extends StatefulWidget {
 | |
|   const Obtainium({super.key});
 | |
| 
 | |
|   @override
 | |
|   State<Obtainium> createState() => _ObtainiumState();
 | |
| }
 | |
| 
 | |
| class _ObtainiumState extends State<Obtainium> {
 | |
|   var existingUpdateInterval = -1;
 | |
| 
 | |
|   @override
 | |
|   void initState() {
 | |
|     super.initState();
 | |
|     initPlatformState();
 | |
|     WidgetsBinding.instance.addPostFrameCallback((_) {
 | |
|       requestNonOptionalPermissions();
 | |
|       initForegroundService();
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   Future<void> requestNonOptionalPermissions() async {
 | |
|     final NotificationPermission notificationPermission =
 | |
|         await FlutterForegroundTask.checkNotificationPermission();
 | |
|     if (notificationPermission != NotificationPermission.granted) {
 | |
|       await FlutterForegroundTask.requestNotificationPermission();
 | |
|     }
 | |
|     if (!await FlutterForegroundTask.isIgnoringBatteryOptimizations) {
 | |
|       await FlutterForegroundTask.requestIgnoreBatteryOptimization();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void initForegroundService() {
 | |
|     FlutterForegroundTask.init(
 | |
|       androidNotificationOptions: AndroidNotificationOptions(
 | |
|         channelId: 'bg_update',
 | |
|         channelName: tr('foregroundService'),
 | |
|         channelDescription: tr('foregroundService'),
 | |
|         onlyAlertOnce: true,
 | |
|       ),
 | |
|       iosNotificationOptions: const IOSNotificationOptions(
 | |
|         showNotification: false,
 | |
|         playSound: false,
 | |
|       ),
 | |
|       foregroundTaskOptions: ForegroundTaskOptions(
 | |
|         eventAction: ForegroundTaskEventAction.repeat(900000),
 | |
|         autoRunOnBoot: true,
 | |
|         autoRunOnMyPackageReplaced: true,
 | |
|         allowWakeLock: true,
 | |
|         allowWifiLock: true,
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Future<ServiceRequestResult?> startForegroundService(bool restart) async {
 | |
|     if (await FlutterForegroundTask.isRunningService) {
 | |
|       if (restart) {
 | |
|         return FlutterForegroundTask.restartService();
 | |
|       }
 | |
|     } else {
 | |
|       return FlutterForegroundTask.startService(
 | |
|         serviceTypes: [ForegroundServiceTypes.specialUse],
 | |
|         serviceId: 666,
 | |
|         notificationTitle: tr('foregroundService'),
 | |
|         notificationText: tr('fgServiceNotice'),
 | |
|         notificationIcon: NotificationIcon(
 | |
|           metaDataName: 'dev.imranr.obtainium.service.NOTIFICATION_ICON',
 | |
|         ),
 | |
|         callback: startCallback,
 | |
|       );
 | |
|     }
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   stopForegroundService() async {
 | |
|     if (await FlutterForegroundTask.isRunningService) {
 | |
|       return FlutterForegroundTask.stopService();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // void onReceiveForegroundServiceData(Object data) {
 | |
|   //   print('onReceiveTaskData: $data');
 | |
|   // }
 | |
| 
 | |
|   @override
 | |
|   void dispose() {
 | |
|     // Remove a callback to receive data sent from the TaskHandler.
 | |
|     // FlutterForegroundTask.removeTaskDataCallback(onReceiveForegroundServiceData);
 | |
|     super.dispose();
 | |
|   }
 | |
| 
 | |
|   Future<void> initPlatformState() async {
 | |
|     await BackgroundFetch.configure(
 | |
|       BackgroundFetchConfig(
 | |
|         minimumFetchInterval: 15,
 | |
|         stopOnTerminate: false,
 | |
|         startOnBoot: true,
 | |
|         enableHeadless: true,
 | |
|         requiresBatteryNotLow: false,
 | |
|         requiresCharging: false,
 | |
|         requiresStorageNotLow: false,
 | |
|         requiresDeviceIdle: false,
 | |
|         requiredNetworkType: NetworkType.ANY,
 | |
|       ),
 | |
|       (String taskId) async {
 | |
|         await bgUpdateCheck(taskId, null);
 | |
|         BackgroundFetch.finish(taskId);
 | |
|       },
 | |
|       (String taskId) async {
 | |
|         context.read<LogsProvider>().add('BG update task timed out.');
 | |
|         BackgroundFetch.finish(taskId);
 | |
|       },
 | |
|     );
 | |
|     if (!mounted) return;
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     SettingsProvider settingsProvider = context.watch<SettingsProvider>();
 | |
|     AppsProvider appsProvider = context.read<AppsProvider>();
 | |
|     LogsProvider logs = context.read<LogsProvider>();
 | |
|     NotificationsProvider notifs = context.read<NotificationsProvider>();
 | |
|     if (settingsProvider.updateInterval == 0) {
 | |
|       stopForegroundService();
 | |
|       BackgroundFetch.stop();
 | |
|     } else {
 | |
|       if (settingsProvider.useFGService) {
 | |
|         BackgroundFetch.stop();
 | |
|         startForegroundService(false);
 | |
|       } else {
 | |
|         stopForegroundService();
 | |
|         BackgroundFetch.start();
 | |
|       }
 | |
|     }
 | |
|     if (settingsProvider.prefs == null) {
 | |
|       settingsProvider.initializeSettings();
 | |
|     } else {
 | |
|       bool isFirstRun = settingsProvider.checkAndFlipFirstRun();
 | |
|       if (isFirstRun) {
 | |
|         logs.add('This is the first ever run of Obtainium.');
 | |
|         // If this is the first run, add Obtainium to the Apps list
 | |
|         if (!fdroid) {
 | |
|           getInstalledInfo(obtainiumId)
 | |
|               .then((value) {
 | |
|                 if (value?.versionName != null) {
 | |
|                   appsProvider.saveApps([
 | |
|                     App(
 | |
|                       obtainiumId,
 | |
|                       obtainiumUrl,
 | |
|                       'ImranR98',
 | |
|                       'Obtainium',
 | |
|                       value!.versionName,
 | |
|                       value.versionName!,
 | |
|                       [],
 | |
|                       0,
 | |
|                       {
 | |
|                         'versionDetection': true,
 | |
|                         'apkFilterRegEx': 'fdroid',
 | |
|                         'invertAPKFilter': true,
 | |
|                       },
 | |
|                       null,
 | |
|                       false,
 | |
|                     ),
 | |
|                   ], onlyIfExists: false);
 | |
|                 }
 | |
|               })
 | |
|               .catchError((err) {
 | |
|                 print(err);
 | |
|               });
 | |
|         }
 | |
|       }
 | |
|       if (!supportedLocales.map((e) => e.key).contains(context.locale) ||
 | |
|           (settingsProvider.forcedLocale == null &&
 | |
|               context.deviceLocale != context.locale)) {
 | |
|         settingsProvider.resetLocaleSafe(context);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     WidgetsBinding.instance.addPostFrameCallback((_) {
 | |
|       notifs.checkLaunchByNotif();
 | |
|     });
 | |
| 
 | |
|     return WithForegroundTask(
 | |
|       child: DynamicColorBuilder(
 | |
|         builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
 | |
|           // Decide on a colour/brightness scheme based on OS and user settings
 | |
|           ColorScheme lightColorScheme;
 | |
|           ColorScheme darkColorScheme;
 | |
|           if (lightDynamic != null &&
 | |
|               darkDynamic != null &&
 | |
|               settingsProvider.useMaterialYou) {
 | |
|             lightColorScheme = lightDynamic.harmonized();
 | |
|             darkColorScheme = darkDynamic.harmonized();
 | |
|           } else {
 | |
|             lightColorScheme = ColorScheme.fromSeed(
 | |
|               seedColor: settingsProvider.themeColor,
 | |
|             );
 | |
|             darkColorScheme = ColorScheme.fromSeed(
 | |
|               seedColor: settingsProvider.themeColor,
 | |
|               brightness: Brightness.dark,
 | |
|             );
 | |
|           }
 | |
| 
 | |
|           // set the background and surface colors to pure black in the amoled theme
 | |
|           if (settingsProvider.useBlackTheme) {
 | |
|             darkColorScheme = darkColorScheme
 | |
|                 .copyWith(surface: Colors.black)
 | |
|                 .harmonized();
 | |
|           }
 | |
| 
 | |
|           if (settingsProvider.useSystemFont) NativeFeatures.loadSystemFont();
 | |
| 
 | |
|           return MaterialApp(
 | |
|             title: 'Obtainium',
 | |
|             localizationsDelegates: context.localizationDelegates,
 | |
|             supportedLocales: context.supportedLocales,
 | |
|             locale: context.locale,
 | |
|             navigatorKey: globalNavigatorKey,
 | |
|             debugShowCheckedModeBanner: false,
 | |
|             theme: ThemeData(
 | |
|               useMaterial3: true,
 | |
|               colorScheme: settingsProvider.theme == ThemeSettings.dark
 | |
|                   ? darkColorScheme
 | |
|                   : lightColorScheme,
 | |
|               fontFamily: settingsProvider.useSystemFont
 | |
|                   ? 'SystemFont'
 | |
|                   : 'Montserrat',
 | |
|             ),
 | |
|             darkTheme: ThemeData(
 | |
|               useMaterial3: true,
 | |
|               colorScheme: settingsProvider.theme == ThemeSettings.light
 | |
|                   ? lightColorScheme
 | |
|                   : darkColorScheme,
 | |
|               fontFamily: settingsProvider.useSystemFont
 | |
|                   ? 'SystemFont'
 | |
|                   : 'Montserrat',
 | |
|             ),
 | |
|             home: Shortcuts(
 | |
|               shortcuts: <LogicalKeySet, Intent>{
 | |
|                 LogicalKeySet(LogicalKeyboardKey.select):
 | |
|                     const ActivateIntent(),
 | |
|               },
 | |
|               child: const HomePage(),
 | |
|             ),
 | |
|           );
 | |
|         },
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| }
 |