mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-12 21:06:43 +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(),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|