mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-08-18 20:49:30 +02:00
1. Apps that don't have "standard" versioning formats now automatically stop using version detection. This will prevent users from having to learn about this feature and enable it manually. - For such Apps, the "standard" version detection option is greyed out. 2. The Mullvad Source recently broke due to a slight change in their website design. This is now fixed. - Mullvad also now provides an in-app changelog via their official GitHub repo. 3. Code has been refactored for readability (specifically the version detection code and UI code for most screens). 4. Minor UI tweaks and bugfixes.
288 lines
10 KiB
Dart
288 lines
10 KiB
Dart
import 'dart:io';
|
|
import 'dart:math';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:obtainium/custom_errors.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/notifications_provider.dart';
|
|
import 'package:obtainium/providers/settings_provider.dart';
|
|
import 'package:obtainium/providers/source_provider.dart';
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:dynamic_color/dynamic_color.dart';
|
|
import 'package:device_info_plus/device_info_plus.dart';
|
|
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.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';
|
|
|
|
const String currentVersion = '0.11.16';
|
|
const String currentReleaseTag =
|
|
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
|
|
|
const int bgUpdateCheckAlarmId = 666;
|
|
|
|
const supportedLocales = [
|
|
Locale('en'),
|
|
Locale('zh'),
|
|
Locale('it'),
|
|
Locale('ja'),
|
|
Locale('hu'),
|
|
Locale('de'),
|
|
Locale('fa'),
|
|
Locale('fr')
|
|
];
|
|
const fallbackLocale = Locale('en');
|
|
const localeDir = 'assets/translations';
|
|
|
|
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 != null ? Locale(forceLocale) : null,
|
|
fallbackLocale: fallbackLocale,
|
|
supportedLocales: supportedLocales,
|
|
assetLoader: const RootBundleAssetLoader(),
|
|
useOnlyLangCode: true,
|
|
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')
|
|
Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|
await EasyLocalization.ensureInitialized();
|
|
|
|
await loadTranslations();
|
|
|
|
LogsProvider logs = LogsProvider();
|
|
logs.add(tr('startedBgUpdateTask'));
|
|
int? ignoreAfterMicroseconds = params?['ignoreAfterMicroseconds'];
|
|
await AndroidAlarmManager.initialize();
|
|
DateTime? ignoreAfter = ignoreAfterMicroseconds != null
|
|
? DateTime.fromMicrosecondsSinceEpoch(ignoreAfterMicroseconds)
|
|
: null;
|
|
logs.add(tr('bgUpdateIgnoreAfterIs', args: [ignoreAfter.toString()]));
|
|
var notificationsProvider = NotificationsProvider();
|
|
await notificationsProvider.notify(checkingUpdatesNotification);
|
|
try {
|
|
var appsProvider = AppsProvider();
|
|
await notificationsProvider.cancel(ErrorCheckingUpdatesNotification('').id);
|
|
await appsProvider.loadApps();
|
|
List<String> existingUpdateIds =
|
|
appsProvider.findExistingUpdates(installedOnly: true);
|
|
DateTime nextIgnoreAfter = DateTime.now();
|
|
String? err;
|
|
try {
|
|
logs.add(tr('startedActualBGUpdateCheck'));
|
|
await appsProvider.checkUpdates(
|
|
ignoreAppsCheckedAfter: ignoreAfter, throwErrorsForRetry: true);
|
|
} catch (e) {
|
|
if (e is RateLimitError || e is SocketException) {
|
|
var remainingMinutes = e is RateLimitError ? e.remainingMinutes : 15;
|
|
logs.add(plural('bgUpdateGotErrorRetryInMinutes', remainingMinutes,
|
|
args: [e.toString(), remainingMinutes.toString()]));
|
|
AndroidAlarmManager.oneShot(Duration(minutes: remainingMinutes),
|
|
Random().nextInt(pow(2, 31) as int), bgUpdateCheck, params: {
|
|
'ignoreAfterMicroseconds': nextIgnoreAfter.microsecondsSinceEpoch
|
|
});
|
|
} else {
|
|
err = e.toString();
|
|
}
|
|
}
|
|
List<App> newUpdates = appsProvider
|
|
.findExistingUpdates(installedOnly: true)
|
|
.where((id) => !existingUpdateIds.contains(id))
|
|
.map((e) => appsProvider.apps[e]!.app)
|
|
.toList();
|
|
|
|
// TODO: This silent update code doesn't work yet
|
|
// List<String> silentlyUpdated = await appsProvider
|
|
// .downloadAndInstallLatestApp(
|
|
// [...newUpdates.map((e) => e.id), ...existingUpdateIds], null);
|
|
// if (silentlyUpdated.isNotEmpty) {
|
|
// newUpdates = newUpdates
|
|
// .where((element) => !silentlyUpdated.contains(element.id))
|
|
// .toList();
|
|
// notificationsProvider.notify(
|
|
// SilentUpdateNotification(
|
|
// silentlyUpdated.map((e) => appsProvider.apps[e]!.app).toList()),
|
|
// cancelExisting: true);
|
|
// }
|
|
logs.add(
|
|
plural('bgCheckFoundUpdatesWillNotifyIfNeeded', newUpdates.length));
|
|
if (newUpdates.isNotEmpty) {
|
|
notificationsProvider.notify(UpdateNotification(newUpdates));
|
|
}
|
|
if (err != null) {
|
|
throw err;
|
|
}
|
|
} catch (e) {
|
|
notificationsProvider
|
|
.notify(ErrorCheckingUpdatesNotification(e.toString()));
|
|
} finally {
|
|
logs.add(tr('bgUpdateTaskFinished'));
|
|
await notificationsProvider.cancel(checkingUpdatesNotification.id);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
await AndroidAlarmManager.initialize();
|
|
runApp(MultiProvider(
|
|
providers: [
|
|
ChangeNotifierProvider(create: (context) => AppsProvider()),
|
|
ChangeNotifierProvider(create: (context) => SettingsProvider()),
|
|
Provider(create: (context) => NotificationsProvider()),
|
|
Provider(create: (context) => LogsProvider())
|
|
],
|
|
child: EasyLocalization(
|
|
supportedLocales: supportedLocales,
|
|
path: localeDir,
|
|
fallbackLocale: fallbackLocale,
|
|
useOnlyLangCode: true,
|
|
child: const Obtainium()),
|
|
));
|
|
}
|
|
|
|
var defaultThemeColour = Colors.deepPurple;
|
|
|
|
class Obtainium extends StatefulWidget {
|
|
const Obtainium({super.key});
|
|
|
|
@override
|
|
State<Obtainium> createState() => _ObtainiumState();
|
|
}
|
|
|
|
class _ObtainiumState extends State<Obtainium> {
|
|
var existingUpdateInterval = -1;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
|
|
AppsProvider appsProvider = context.read<AppsProvider>();
|
|
LogsProvider logs = context.read<LogsProvider>();
|
|
|
|
if (settingsProvider.prefs == null) {
|
|
settingsProvider.initializeSettings();
|
|
} else {
|
|
bool isFirstRun = settingsProvider.checkAndFlipFirstRun();
|
|
if (isFirstRun) {
|
|
logs.add(tr('firstRun'));
|
|
// If this is the first run, ask for notification permissions and add Obtainium to the Apps list
|
|
Permission.notification.request();
|
|
appsProvider.saveApps([
|
|
App(
|
|
obtainiumId,
|
|
'https://github.com/ImranR98/Obtainium',
|
|
'ImranR98',
|
|
'Obtainium',
|
|
currentReleaseTag,
|
|
currentReleaseTag,
|
|
[],
|
|
0,
|
|
{'includePrereleases': true},
|
|
null,
|
|
false)
|
|
], onlyIfExists: false);
|
|
}
|
|
if (!supportedLocales
|
|
.map((e) => e.languageCode)
|
|
.contains(context.locale.languageCode) ||
|
|
settingsProvider.forcedLocale == null &&
|
|
context.deviceLocale.languageCode !=
|
|
context.locale.languageCode) {
|
|
settingsProvider.resetLocaleSafe(context);
|
|
}
|
|
// Register the background update task according to the user's setting
|
|
if (existingUpdateInterval != settingsProvider.updateInterval) {
|
|
if (existingUpdateInterval != -1) {
|
|
logs.add(tr('settingUpdateCheckIntervalTo',
|
|
args: [settingsProvider.updateInterval.toString()]));
|
|
}
|
|
existingUpdateInterval = settingsProvider.updateInterval;
|
|
if (existingUpdateInterval == 0) {
|
|
AndroidAlarmManager.cancel(bgUpdateCheckAlarmId);
|
|
} else {
|
|
AndroidAlarmManager.periodic(
|
|
Duration(minutes: existingUpdateInterval),
|
|
bgUpdateCheckAlarmId,
|
|
bgUpdateCheck,
|
|
rescheduleOnReboot: true,
|
|
wakeup: true);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 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.colour == ColourSettings.materialYou) {
|
|
lightColorScheme = lightDynamic.harmonized();
|
|
darkColorScheme = darkDynamic.harmonized();
|
|
} else {
|
|
lightColorScheme = ColorScheme.fromSeed(seedColor: defaultThemeColour);
|
|
darkColorScheme = ColorScheme.fromSeed(
|
|
seedColor: defaultThemeColour, brightness: Brightness.dark);
|
|
}
|
|
return MaterialApp(
|
|
title: 'Obtainium',
|
|
localizationsDelegates: context.localizationDelegates,
|
|
supportedLocales: context.supportedLocales,
|
|
locale: context.locale,
|
|
navigatorKey: globalNavigatorKey,
|
|
theme: ThemeData(
|
|
useMaterial3: true,
|
|
colorScheme: settingsProvider.theme == ThemeSettings.dark
|
|
? darkColorScheme
|
|
: lightColorScheme,
|
|
fontFamily: 'Metropolis'),
|
|
darkTheme: ThemeData(
|
|
useMaterial3: true,
|
|
colorScheme: settingsProvider.theme == ThemeSettings.light
|
|
? lightColorScheme
|
|
: darkColorScheme,
|
|
fontFamily: 'Metropolis'),
|
|
home: const HomePage());
|
|
});
|
|
}
|
|
}
|