mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-13 21:36:42 +02:00
Compare commits
10 Commits
v0.8.4-bet
...
v0.8.8-bet
Author | SHA1 | Date | |
---|---|---|---|
f81f6374bb | |||
da8695834e | |||
c4ba1e9dbc | |||
49862ad2a6 | |||
1b892f4e0d | |||
a4555f07f9 | |||
73fbdd84f0 | |||
a1518480db | |||
fd3ee02e52 | |||
609366675d |
@ -13,9 +13,10 @@ Currently supported App sources:
|
||||
- [IzzyOnDroid](https://android.izzysoft.de/)
|
||||
- [Mullvad](https://mullvad.net/en/)
|
||||
- [Signal](https://signal.org/)
|
||||
- [APKMirror](https://apkmirror.com/) (Track-Only)
|
||||
|
||||
## Limitations
|
||||
- App installs are assumed to have succeeded; failures and cancelled installs cannot be detected.
|
||||
- App installs happen asynchronously and the success/failure of an install cannot be determined directly. This results in install statuses and versions sometimes being out of sync with the OS until the next launch or until the problem is manually corrected.
|
||||
- Auto (unattended) updates are unsupported due to a lack of any capable Flutter plugin.
|
||||
- For some sources, data is gathered using Web scraping and can easily break due to changes in website design. In such cases, more reliable methods may be unavailable.
|
||||
|
||||
|
@ -42,6 +42,7 @@
|
||||
"trackOnlyAppDescription": "The App will be tracked for updates, but Obtainium will not be able to download or install it.",
|
||||
"cancelled": "Cancelled",
|
||||
"appAlreadyAdded": "App already added",
|
||||
"alreadyUpToDateQuestion": "App Already up to Date?",
|
||||
"addApp": "Add App",
|
||||
"appSourceURL": "App Source URL",
|
||||
"error": "Error",
|
||||
@ -57,7 +58,7 @@
|
||||
"noAppsForFilter": "No Apps for Filter",
|
||||
"byX": "By {}",
|
||||
"percentProgress": "Progress: {}%",
|
||||
"pleaseWait": "Please Wait...",
|
||||
"pleaseWait": "Please Wait",
|
||||
"updateAvailable": "Update Available",
|
||||
"estimateInBracketsShort": "(Est.)",
|
||||
"notInstalled": "Not Installed",
|
||||
@ -73,7 +74,7 @@
|
||||
"changeX": "Change {}",
|
||||
"installUpdateApps": "Install/Update Apps",
|
||||
"installUpdateSelectedApps": "Install/Update Selected Apps",
|
||||
"onlyAppliesToInstalledAndOutdatedApps": "Only applies to installed but out of date Apps whose install status cannot be automatically detected.",
|
||||
"onlyWorksWithNonEVDApps": "Only works for Apps whose install status cannot be automatically detected (uncommon).",
|
||||
"markXSelectedAppsAsUpdated": "Mark {} Selected Apps as Updated?",
|
||||
"no": "No",
|
||||
"yes": "Yes",
|
||||
@ -206,11 +207,11 @@
|
||||
"other": "Cleared {n} logs (before = {before}, after = {after})"
|
||||
},
|
||||
"xAndNMoreUpdatesAvailable": {
|
||||
"one": "{} and {} more app have updated.",
|
||||
"one": "{} and 1 more app have updates.",
|
||||
"other": "{} and {} more apps have updates."
|
||||
},
|
||||
"xAndNMoreUpdatesInstalled": {
|
||||
"one": "{} and {} more app were updated.",
|
||||
"one": "{} and 1 more app were updated.",
|
||||
"other": "{} and {} more apps were updated."
|
||||
}
|
||||
}
|
@ -16,17 +16,49 @@ 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.8.4';
|
||||
const String currentVersion = '0.8.8';
|
||||
const String currentReleaseTag =
|
||||
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
||||
|
||||
const int bgUpdateCheckAlarmId = 666;
|
||||
|
||||
const supportedLocales = [Locale('en')];
|
||||
const fallbackLocale = Locale('en');
|
||||
const localeDir = 'assets/translations';
|
||||
|
||||
Future<void> loadTranslations() async {
|
||||
// See easy_localization/issues/210
|
||||
await EasyLocalizationController.initEasyLocation();
|
||||
final controller = EasyLocalizationController(
|
||||
saveLocale: true,
|
||||
fallbackLocale: fallbackLocale,
|
||||
supportedLocales: supportedLocales,
|
||||
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')
|
||||
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'];
|
||||
@ -38,7 +70,7 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
||||
var notificationsProvider = NotificationsProvider();
|
||||
await notificationsProvider.notify(checkingUpdatesNotification);
|
||||
try {
|
||||
var appsProvider = AppsProvider(forBGTask: true);
|
||||
var appsProvider = AppsProvider();
|
||||
await notificationsProvider.cancel(ErrorCheckingUpdatesNotification('').id);
|
||||
await appsProvider.loadApps();
|
||||
List<String> existingUpdateIds =
|
||||
@ -116,9 +148,9 @@ void main() async {
|
||||
Provider(create: (context) => LogsProvider())
|
||||
],
|
||||
child: EasyLocalization(
|
||||
supportedLocales: const [Locale('en')],
|
||||
path: 'assets/translations',
|
||||
fallbackLocale: const Locale('en'),
|
||||
supportedLocales: supportedLocales,
|
||||
path: localeDir,
|
||||
fallbackLocale: fallbackLocale,
|
||||
child: const Obtainium()),
|
||||
));
|
||||
}
|
||||
@ -162,7 +194,6 @@ class _ObtainiumState extends State<Obtainium> {
|
||||
['true'],
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
false)
|
||||
]);
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:obtainium/components/generated_form_modal.dart';
|
||||
@ -114,7 +115,7 @@ class _AppPageState extends State<AppPage> {
|
||||
height: 32,
|
||||
),
|
||||
Text(
|
||||
'Last Update Check: ${app?.app.lastUpdateCheck == null ? 'Never' : '\n${app?.app.lastUpdateCheck?.toLocal()}'}${app?.app.enhancedVersionDetection == true ? '\n\nThis App has enhanced version detection.' : ''}',
|
||||
'Last Update Check: ${app?.app.lastUpdateCheck == null ? 'Never' : '\n${app?.app.lastUpdateCheck?.toLocal()}'}',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontStyle: FontStyle.italic, fontSize: 12),
|
||||
@ -141,9 +142,7 @@ class _AppPageState extends State<AppPage> {
|
||||
children: [
|
||||
if (app?.app.installedVersion != null &&
|
||||
app?.app.trackOnly == false &&
|
||||
app?.app.installedVersion !=
|
||||
app?.app.latestVersion &&
|
||||
app?.app.enhancedVersionDetection != true)
|
||||
app?.app.installedVersion != app?.app.latestVersion)
|
||||
IconButton(
|
||||
onPressed: app?.downloadProgress != null
|
||||
? null
|
||||
@ -152,8 +151,15 @@ class _AppPageState extends State<AppPage> {
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return AlertDialog(
|
||||
title: const Text(
|
||||
'App Already up to Date?'),
|
||||
title: Text(tr(
|
||||
'alreadyUpToDateQuestion')),
|
||||
content: Text(
|
||||
tr('onlyWorksWithNonEVDApps'),
|
||||
style: const TextStyle(
|
||||
fontWeight:
|
||||
FontWeight.bold,
|
||||
fontStyle:
|
||||
FontStyle.italic)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
|
@ -253,48 +253,52 @@ class AppsPageState extends State<AppsPage> {
|
||||
fontWeight: sortedApps[index].app.pinned
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal)),
|
||||
trailing: sortedApps[index].downloadProgress != null
|
||||
? Text(tr('percentProgress', args: [
|
||||
sortedApps[index]
|
||||
.downloadProgress
|
||||
?.toInt()
|
||||
.toString() ??
|
||||
'100'
|
||||
]))
|
||||
: (Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
child: SizedBox(
|
||||
width: 80,
|
||||
trailing: SingleChildScrollView(
|
||||
reverse: true,
|
||||
child: sortedApps[index].downloadProgress != null
|
||||
? Text(tr('percentProgress', args: [
|
||||
sortedApps[index]
|
||||
.downloadProgress
|
||||
?.toInt()
|
||||
.toString() ??
|
||||
'100'
|
||||
]))
|
||||
: (Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 100,
|
||||
child: Text(
|
||||
'${sortedApps[index].app.installedVersion ?? tr('notInstalled')}${sortedApps[index].app.trackOnly == true ? ' ${tr('estimateInBrackets')}' : ''}',
|
||||
overflow: TextOverflow.fade,
|
||||
textAlign: TextAlign.end,
|
||||
))),
|
||||
sortedApps[index].app.installedVersion != null &&
|
||||
sortedApps[index].app.installedVersion !=
|
||||
sortedApps[index].app.latestVersion
|
||||
? GestureDetector(
|
||||
onTap: changesUrl == null
|
||||
? null
|
||||
: () {
|
||||
launchUrlString(changesUrl,
|
||||
mode: LaunchMode
|
||||
.externalApplication);
|
||||
},
|
||||
child: Text(
|
||||
'${tr('updateAvailable')}${sortedApps[index].app.trackOnly ? ' ${tr('estimateInBracketsShort')}' : ''}',
|
||||
style: TextStyle(
|
||||
fontStyle: FontStyle.italic,
|
||||
decoration: changesUrl == null
|
||||
? TextDecoration.none
|
||||
: TextDecoration.underline),
|
||||
))
|
||||
: const SizedBox(),
|
||||
],
|
||||
)),
|
||||
)),
|
||||
sortedApps[index].app.installedVersion != null &&
|
||||
sortedApps[index].app.installedVersion !=
|
||||
sortedApps[index].app.latestVersion
|
||||
? GestureDetector(
|
||||
onTap: changesUrl == null
|
||||
? null
|
||||
: () {
|
||||
launchUrlString(changesUrl,
|
||||
mode: LaunchMode
|
||||
.externalApplication);
|
||||
},
|
||||
child: appsProvider.areDownloadsRunning()
|
||||
? Text(tr('pleaseWait'))
|
||||
: Text(
|
||||
'${tr('updateAvailable')}${sortedApps[index].app.trackOnly ? ' ${tr('estimateInBracketsShort')}' : ''}',
|
||||
style: TextStyle(
|
||||
fontStyle: FontStyle.italic,
|
||||
decoration: changesUrl == null
|
||||
? TextDecoration.none
|
||||
: TextDecoration
|
||||
.underline),
|
||||
))
|
||||
: const SizedBox(),
|
||||
],
|
||||
))),
|
||||
onTap: () {
|
||||
if (selectedApps.isNotEmpty) {
|
||||
toggleAppSelected(sortedApps[index].app);
|
||||
@ -509,7 +513,14 @@ class AppsPageState extends State<AppsPage> {
|
||||
.toString()
|
||||
])),
|
||||
content: Text(
|
||||
tr('onlyAppliesToInstalledAndOutdatedApps')),
|
||||
tr('onlyWorksWithNonEVDApps'),
|
||||
style: const TextStyle(
|
||||
fontWeight:
|
||||
FontWeight
|
||||
.bold,
|
||||
fontStyle:
|
||||
FontStyle.italic),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed:
|
||||
@ -526,8 +537,8 @@ class AppsPageState extends State<AppsPage> {
|
||||
.selectionClick();
|
||||
appsProvider
|
||||
.saveApps(selectedApps.map((a) {
|
||||
if (a.installedVersion != null &&
|
||||
!a.enhancedVersionDetection) {
|
||||
if (a.installedVersion !=
|
||||
null) {
|
||||
a.installedVersion = a.latestVersion;
|
||||
}
|
||||
return a;
|
||||
|
@ -37,12 +37,42 @@ class DownloadedApk {
|
||||
DownloadedApk(this.appId, this.file);
|
||||
}
|
||||
|
||||
List<String> generateStandardVersionRegExStrings() {
|
||||
// TODO: Look into RegEx for non-Latin characters / non-Arabic numerals
|
||||
var basics = [
|
||||
'[0-9]+',
|
||||
'[0-9]+\\.[0-9]+',
|
||||
'[0-9]+\\.[0-9]+\\.[0-9]+',
|
||||
'[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+'
|
||||
];
|
||||
var preSuffixes = ['-', '\\+'];
|
||||
var suffixes = ['alpha', 'beta', 'ose'];
|
||||
var finals = ['\\+[0-9]+', '[0-9]+'];
|
||||
List<String> results = [];
|
||||
for (var b in basics) {
|
||||
results.add(b);
|
||||
for (var p in preSuffixes) {
|
||||
for (var s in suffixes) {
|
||||
results.add('$b$s');
|
||||
results.add('$b$p$s');
|
||||
for (var f in finals) {
|
||||
results.add('$b$s$f');
|
||||
results.add('$b$p$s$f');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
List<String> standardVersionRegExStrings =
|
||||
generateStandardVersionRegExStrings();
|
||||
|
||||
class AppsProvider with ChangeNotifier {
|
||||
// In memory App state (should always be kept in sync with local storage versions)
|
||||
Map<String, AppInMemory> apps = {};
|
||||
bool loadingApps = false;
|
||||
bool gettingUpdates = false;
|
||||
bool forBGTask = false;
|
||||
LogsProvider logs = LogsProvider();
|
||||
|
||||
// Variables to keep track of the app foreground status (installs can't run in the background)
|
||||
@ -50,29 +80,26 @@ class AppsProvider with ChangeNotifier {
|
||||
late Stream<FGBGType>? foregroundStream;
|
||||
late StreamSubscription<FGBGType>? foregroundSubscription;
|
||||
|
||||
AppsProvider({this.forBGTask = false}) {
|
||||
// Many setup tasks should only be done in the foreground isolate
|
||||
if (!forBGTask) {
|
||||
// Subscribe to changes in the app foreground status
|
||||
foregroundStream = FGBGEvents.stream.asBroadcastStream();
|
||||
foregroundSubscription = foregroundStream?.listen((event) async {
|
||||
isForeground = event == FGBGType.foreground;
|
||||
if (isForeground) await loadApps();
|
||||
AppsProvider() {
|
||||
// Subscribe to changes in the app foreground status
|
||||
foregroundStream = FGBGEvents.stream.asBroadcastStream();
|
||||
foregroundSubscription = foregroundStream?.listen((event) async {
|
||||
isForeground = event == FGBGType.foreground;
|
||||
if (isForeground) await loadApps();
|
||||
});
|
||||
() async {
|
||||
// Load Apps into memory (in background, this is done later instead of in the constructor)
|
||||
await loadApps();
|
||||
// Delete existing APKs
|
||||
(await getExternalStorageDirectory())
|
||||
?.listSync()
|
||||
.where((element) =>
|
||||
element.path.endsWith('.apk') ||
|
||||
element.path.endsWith('.apk.part'))
|
||||
.forEach((apk) {
|
||||
apk.delete();
|
||||
});
|
||||
() async {
|
||||
// Load Apps into memory (in background, this is done later instead of in the constructor)
|
||||
await loadApps();
|
||||
// Delete existing APKs
|
||||
(await getExternalStorageDirectory())
|
||||
?.listSync()
|
||||
.where((element) =>
|
||||
element.path.endsWith('.apk') ||
|
||||
element.path.endsWith('.apk.part'))
|
||||
.forEach((apk) {
|
||||
apk.delete();
|
||||
});
|
||||
}();
|
||||
}
|
||||
}();
|
||||
}
|
||||
|
||||
downloadFile(String url, String fileName, Function? onProgress,
|
||||
@ -171,8 +198,8 @@ class AppsProvider with ChangeNotifier {
|
||||
|
||||
Future<bool> canInstallSilently(App app) async {
|
||||
return false;
|
||||
// TODO: Uncomment the below once silentupdates are ever figured out
|
||||
// // TODO: This is unreliable - try to get from OS in the future
|
||||
// TODO: Uncomment the below if silent updates are ever figured out
|
||||
// // NOTE: This is unreliable - try to get from OS in the future
|
||||
// if (app.apkUrls.length > 1) {
|
||||
// return false;
|
||||
// }
|
||||
@ -329,7 +356,8 @@ class AppsProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
// Move everything to the regular install list (since silent updates don't currently work) - TODO
|
||||
// Move everything to the regular install list (since silent updates don't currently work)
|
||||
// TODO: Remove this when silent updates work
|
||||
regularInstalls.addAll(silentUpdates);
|
||||
|
||||
// If Obtainium is being installed, it should be the last one
|
||||
@ -400,41 +428,92 @@ class AppsProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
// If the App says it is installed but installedInfo is null, set it to not installed
|
||||
// If the App says is is not installed but installedInfo exists, set it to the real installed version
|
||||
// If the internal version does not match the real one, sync them if the App supports enhanced version detection
|
||||
// Enhanced version detection will be true if the version extracted from source matches the standard version format
|
||||
// If there is any other mismatch between installedInfo and installedVersion, try reconciling them intelligently
|
||||
// If that fails, just set it to the actual version string (all we can do at that point)
|
||||
// Don't save changes, just return the object if changes were made (else null)
|
||||
// If in a background isolate, return null straight away as the required plugin won't work anyways
|
||||
App? getCorrectedInstallStatusAppIfPossible(App app, AppInfo? installedInfo) {
|
||||
if (forBGTask) {
|
||||
return null; // Can't correct in the background isolate
|
||||
}
|
||||
var modded = false;
|
||||
if (installedInfo == null &&
|
||||
app.installedVersion != null &&
|
||||
!app.trackOnly) {
|
||||
app.installedVersion = null;
|
||||
modded = true;
|
||||
} else if (installedInfo != null && app.installedVersion == null) {
|
||||
if (app.enhancedVersionDetection) {
|
||||
app.installedVersion = installedInfo.versionName;
|
||||
} else {
|
||||
if (app.latestVersion.contains(installedInfo.versionName!)) {
|
||||
app.installedVersion = app.latestVersion;
|
||||
} else {
|
||||
app.installedVersion = installedInfo.versionName;
|
||||
}
|
||||
}
|
||||
} else if (installedInfo?.versionName != null &&
|
||||
app.installedVersion == null) {
|
||||
app.installedVersion = installedInfo!.versionName;
|
||||
modded = true;
|
||||
} else if (installedInfo?.versionName != app.installedVersion &&
|
||||
app.enhancedVersionDetection &&
|
||||
!app.trackOnly) {
|
||||
app.installedVersion = installedInfo?.versionName;
|
||||
} else if (installedInfo?.versionName != null &&
|
||||
installedInfo!.versionName != app.installedVersion) {
|
||||
String? correctedInstalledVersion = reconcileRealAndInternalVersions(
|
||||
installedInfo.versionName!, app.installedVersion!);
|
||||
if (correctedInstalledVersion != null) {
|
||||
app.installedVersion = correctedInstalledVersion;
|
||||
modded = true;
|
||||
}
|
||||
}
|
||||
if (app.installedVersion != null &&
|
||||
app.installedVersion != app.latestVersion) {
|
||||
app.installedVersion = reconcileRealAndInternalVersions(
|
||||
app.installedVersion!, app.latestVersion,
|
||||
matchMode: true) ??
|
||||
app.installedVersion;
|
||||
modded = true;
|
||||
}
|
||||
return modded ? app : null;
|
||||
}
|
||||
|
||||
String? reconcileRealAndInternalVersions(
|
||||
String realVersion, String internalVersion,
|
||||
{bool matchMode = false}) {
|
||||
// 1. If one or both of these can't be converted to a "standard" format, return null (leave as is)
|
||||
// 2. If both have a "standard" format under which they are equal, return null (leave as is)
|
||||
// 3. If both have a "standard" format in common but are unequal, return realVersion (this means it was changed externally)
|
||||
// If in matchMode, the outcomes of rules 2 and 3 are reversed, and the "real" version is not matched strictly
|
||||
// Matchmode to be used when comparing internal install version and internal latest version
|
||||
|
||||
bool doStringsMatchUnderRegEx(
|
||||
String pattern, String value1, String value2) {
|
||||
var r = RegExp(pattern);
|
||||
var m1 = r.firstMatch(value1);
|
||||
var m2 = r.firstMatch(value2);
|
||||
return m1 != null && m2 != null
|
||||
? value1.substring(m1.start, m1.end) ==
|
||||
value2.substring(m2.start, m2.end)
|
||||
: false;
|
||||
}
|
||||
|
||||
Set<String> findStandardFormatsForVersion(String version, bool strict) {
|
||||
Set<String> results = {};
|
||||
for (var pattern in standardVersionRegExStrings) {
|
||||
if (RegExp('${strict ? '^' : ''}$pattern${strict ? '\$' : ''}')
|
||||
.hasMatch(version)) {
|
||||
results.add(pattern);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
var realStandardVersionFormats =
|
||||
findStandardFormatsForVersion(realVersion, true);
|
||||
var internalStandardVersionFormats =
|
||||
findStandardFormatsForVersion(internalVersion, false);
|
||||
var commonStandardFormats =
|
||||
realStandardVersionFormats.intersection(internalStandardVersionFormats);
|
||||
if (commonStandardFormats.isEmpty) {
|
||||
return null; // Incompatible; no "enhanced detection"
|
||||
}
|
||||
for (String pattern in commonStandardFormats) {
|
||||
if (doStringsMatchUnderRegEx(pattern, internalVersion, realVersion)) {
|
||||
return matchMode
|
||||
? internalVersion
|
||||
: null; // Enhanced detection says no change
|
||||
}
|
||||
}
|
||||
return matchMode
|
||||
? null
|
||||
: realVersion; // Enhanced detection says something changed
|
||||
}
|
||||
|
||||
Future<void> loadApps() async {
|
||||
while (loadingApps) {
|
||||
await Future.delayed(const Duration(microseconds: 1));
|
||||
@ -480,7 +559,7 @@ class AppsProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
if (modifiedApps.isNotEmpty) {
|
||||
await saveApps(modifiedApps);
|
||||
await saveApps(modifiedApps, attemptToCorrectInstallStatus: false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -600,7 +679,7 @@ class AppsProvider with ChangeNotifier {
|
||||
|
||||
Future<String> exportApps() async {
|
||||
Directory? exportDir = Directory('/storage/emulated/0/Download');
|
||||
String path = 'Downloads'; // TODO: Is this true on non-english phones?
|
||||
String path = 'Downloads'; // TODO: See if hardcoding this can be avoided
|
||||
if (!exportDir.existsSync()) {
|
||||
exportDir = await getExternalStorageDirectory();
|
||||
path = exportDir!.path;
|
||||
|
@ -13,11 +13,12 @@ class ObtainiumNotification {
|
||||
late String channelName;
|
||||
late String channelDescription;
|
||||
Importance importance;
|
||||
int? progPercent;
|
||||
bool onlyAlertOnce;
|
||||
|
||||
ObtainiumNotification(this.id, this.title, this.message, this.channelCode,
|
||||
this.channelName, this.channelDescription, this.importance,
|
||||
{this.onlyAlertOnce = false});
|
||||
{this.onlyAlertOnce = false, this.progPercent});
|
||||
}
|
||||
|
||||
class UpdateNotification extends ObtainiumNotification {
|
||||
@ -80,14 +81,13 @@ class DownloadNotification extends ObtainiumNotification {
|
||||
: super(
|
||||
appName.hashCode,
|
||||
'Downloading $appName',
|
||||
'$progPercent%',
|
||||
'',
|
||||
'APP_DOWNLOADING',
|
||||
'Downloading App',
|
||||
'Notifies the user of the progress in downloading an App',
|
||||
Importance.defaultImportance,
|
||||
onlyAlertOnce: true) {
|
||||
message = tr('percentProgress', args: [progPercent.toString()]);
|
||||
}
|
||||
Importance.low,
|
||||
onlyAlertOnce: true,
|
||||
progPercent: progPercent);
|
||||
}
|
||||
|
||||
final completeInstallationNotification = ObtainiumNotification(
|
||||
@ -174,5 +174,7 @@ class NotificationsProvider {
|
||||
{bool cancelExisting = false}) =>
|
||||
notifyRaw(notif.id, notif.title, notif.message, notif.channelCode,
|
||||
notif.channelName, notif.channelDescription, notif.importance,
|
||||
cancelExisting: cancelExisting, onlyAlertOnce: notif.onlyAlertOnce);
|
||||
cancelExisting: cancelExisting,
|
||||
onlyAlertOnce: notif.onlyAlertOnce,
|
||||
progPercent: notif.progPercent);
|
||||
}
|
||||
|
@ -27,15 +27,9 @@ class AppNames {
|
||||
|
||||
class APKDetails {
|
||||
late String version;
|
||||
late String versionFromSource;
|
||||
late bool isStandardVersion;
|
||||
late List<String> apkUrls;
|
||||
|
||||
APKDetails(this.versionFromSource, this.apkUrls) {
|
||||
var temp = extractStandardVersionName(versionFromSource);
|
||||
isStandardVersion = temp != null;
|
||||
version = temp ?? versionFromSource;
|
||||
}
|
||||
APKDetails(this.version, this.apkUrls);
|
||||
}
|
||||
|
||||
class App {
|
||||
@ -51,7 +45,6 @@ class App {
|
||||
late DateTime? lastUpdateCheck;
|
||||
bool pinned = false;
|
||||
bool trackOnly = false;
|
||||
bool enhancedVersionDetection = false;
|
||||
App(
|
||||
this.id,
|
||||
this.url,
|
||||
@ -64,8 +57,7 @@ class App {
|
||||
this.additionalData,
|
||||
this.lastUpdateCheck,
|
||||
this.pinned,
|
||||
this.trackOnly,
|
||||
this.enhancedVersionDetection);
|
||||
this.trackOnly);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
@ -94,8 +86,7 @@ class App {
|
||||
? null
|
||||
: DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']),
|
||||
json['pinned'] ?? false,
|
||||
json['trackOnly'] ?? false,
|
||||
json['enhancedVersionDetection'] ?? false);
|
||||
json['trackOnly'] ?? false);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
@ -109,8 +100,7 @@ class App {
|
||||
'additionalData': jsonEncode(additionalData),
|
||||
'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch,
|
||||
'pinned': pinned,
|
||||
'trackOnly': trackOnly,
|
||||
'enhancedVersionDetection': enhancedVersionDetection
|
||||
'trackOnly': trackOnly
|
||||
};
|
||||
}
|
||||
|
||||
@ -200,13 +190,6 @@ ObtainiumError getObtainiumHttpError(Response res) {
|
||||
tr('errorWithHttpStatusCode', args: [res.statusCode.toString()]));
|
||||
}
|
||||
|
||||
String? extractStandardVersionName(String version, {bool strict = false}) {
|
||||
var match =
|
||||
RegExp('${strict ? '^' : ''}[0-9]+(\\.[0-9]+)+${strict ? '\$' : ''}')
|
||||
.firstMatch(version);
|
||||
return match != null ? version.substring(match.start, match.end) : null;
|
||||
}
|
||||
|
||||
abstract class MassAppUrlSource {
|
||||
late String name;
|
||||
late List<String> requiredArgs;
|
||||
@ -265,7 +248,7 @@ class SourceProvider {
|
||||
}
|
||||
for (int i = 0; i < parts.length - 1; i++) {
|
||||
if (RegExp('.*[A-Z].*').hasMatch(parts[i])) {
|
||||
// TODO: RegEx won't work for non-eng chars
|
||||
// TODO: Look into RegEx for non-Latin characters
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -285,12 +268,6 @@ class SourceProvider {
|
||||
if (apk.apkUrls.isEmpty && !trackOnly) {
|
||||
throw NoAPKError();
|
||||
}
|
||||
bool enhancedVersionDetection = apk.isStandardVersion &&
|
||||
installedVersion != null &&
|
||||
extractStandardVersionName(installedVersion, strict: true) != null;
|
||||
if (!enhancedVersionDetection) {
|
||||
apk.version = apk.versionFromSource;
|
||||
}
|
||||
String apkVersion = apk.version.replaceAll('/', '-');
|
||||
return App(
|
||||
id ??
|
||||
@ -308,8 +285,7 @@ class SourceProvider {
|
||||
additionalData,
|
||||
DateTime.now(),
|
||||
pinned,
|
||||
trackOnly,
|
||||
enhancedVersionDetection);
|
||||
trackOnly);
|
||||
}
|
||||
|
||||
// Returns errors in [results, errors] instead of throwing them
|
||||
|
@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 0.8.4+67 # When changing this, update the tag in main() accordingly
|
||||
version: 0.8.8+71 # When changing this, update the tag in main() accordingly
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.2 <3.0.0'
|
||||
|
Reference in New Issue
Block a user