mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-08-20 05:19:28 +02:00
Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0e2a0b65ec | ||
|
5b79f399d1 | ||
|
2961d5ac17 | ||
|
4af4160aaa | ||
|
327f73cc9e | ||
|
e82170fec6 | ||
|
8922b1c048 | ||
|
e9550c6ff0 | ||
|
890c3682c4 | ||
|
a2c38968e1 | ||
|
a9c3ee4c54 |
@@ -7,7 +7,7 @@ Get Android app updates straight from the source.
|
|||||||
Obtainium allows you to install and update apps directly from their releases pages, and receive notifications when new releases are made available.
|
Obtainium allows you to install and update apps directly from their releases pages, and receive notifications when new releases are made available.
|
||||||
|
|
||||||
More info:
|
More info:
|
||||||
- [Obtainium Wiki](https://github.com/ImranR98/Obtainium/wiki)
|
- [Obtainium Wiki](https://wiki.obtainium.imranr.dev/) ([repository](https://github.com/ImranR98/Obtainium-Wiki))
|
||||||
- [AppVerifier](https://github.com/soupslurpr/AppVerifier) - App verification tool (recommended, integrates with Obtainium)
|
- [AppVerifier](https://github.com/soupslurpr/AppVerifier) - App verification tool (recommended, integrates with Obtainium)
|
||||||
- [apps.obtainium.imranr.dev](https://apps.obtainium.imranr.dev/) - Crowdsourced app configurations ([repository](https://github.com/ImranR98/apps.obtainium.imranr.dev))
|
- [apps.obtainium.imranr.dev](https://apps.obtainium.imranr.dev/) - Crowdsourced app configurations ([repository](https://github.com/ImranR98/apps.obtainium.imranr.dev))
|
||||||
- [Side Of Burritos - You should use this instead of F-Droid | How to use app RSS feed](https://youtu.be/FFz57zNR_M0) - Original motivation for this app
|
- [Side Of Burritos - You should use this instead of F-Droid | How to use app RSS feed](https://youtu.be/FFz57zNR_M0) - Original motivation for this app
|
||||||
|
@@ -114,7 +114,7 @@
|
|||||||
"light": "Hell",
|
"light": "Hell",
|
||||||
"followSystem": "System folgen",
|
"followSystem": "System folgen",
|
||||||
"followSystemThemeExplanation": "Das Folgen des Systemthemes ist unter Android < 10 nur mit Hilfe von Drittanbieterapps möglich",
|
"followSystemThemeExplanation": "Das Folgen des Systemthemes ist unter Android < 10 nur mit Hilfe von Drittanbieterapps möglich",
|
||||||
"useBlackTheme": "Pure Black Dark Theme verwenden",
|
"useBlackTheme": "Rein schwarzen Hintergrund verwenden",
|
||||||
"appSortBy": "App sortieren nach",
|
"appSortBy": "App sortieren nach",
|
||||||
"authorName": "Autor/Name",
|
"authorName": "Autor/Name",
|
||||||
"nameAuthor": "Name/Autor",
|
"nameAuthor": "Name/Autor",
|
||||||
|
@@ -213,7 +213,7 @@
|
|||||||
"releaseDateAsVersion": "Használja a kiadás dátumát verzió-karakterláncként",
|
"releaseDateAsVersion": "Használja a kiadás dátumát verzió-karakterláncként",
|
||||||
"releaseTitleAsVersion": "Használja a kiadás címét verzió-karakterláncként",
|
"releaseTitleAsVersion": "Használja a kiadás címét verzió-karakterláncként",
|
||||||
"releaseDateAsVersionExplanation": "Ezt a beállítást csak olyan alkalmazásoknál szabad használni, ahol a verzió-érzékelés nem működik megfelelően, de elérhető a kiadás dátuma.",
|
"releaseDateAsVersionExplanation": "Ezt a beállítást csak olyan alkalmazásoknál szabad használni, ahol a verzió-érzékelés nem működik megfelelően, de elérhető a kiadás dátuma.",
|
||||||
"changes": "Változásnapló",
|
"changes": "Változáslista",
|
||||||
"releaseDate": "Kiadás dátuma",
|
"releaseDate": "Kiadás dátuma",
|
||||||
"importFromURLsInFile": "Importálás fájlban található webcímből (pl. OPML)",
|
"importFromURLsInFile": "Importálás fájlban található webcímből (pl. OPML)",
|
||||||
"versionDetectionExplanation": "A verzió-karakterlánc egyeztetése az rendszer által érzékelt verzióval",
|
"versionDetectionExplanation": "A verzió-karakterlánc egyeztetése az rendszer által érzékelt verzióval",
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<p>Obtainium allows you to install and update Apps directly from their releases pages, and receive notifications when new releases are made available.</p>
|
<p>Obtainium allows you to install and update Apps directly from their releases pages, and receive notifications when new releases are made available.</p>
|
||||||
<p>Read the <a href="https://github.com/ImranR98/Obtainium/wiki">Wiki</a></p>
|
<p>Read the <a href="https://wiki.obtainium.imranr.dev/">Wiki</a></p>
|
||||||
<p>
|
<p>
|
||||||
<b>Currently supported App sources:</b>
|
<b>Currently supported App sources:</b>
|
||||||
</p>
|
</p>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<p>Obtainium позволяет вам устанавливать и обновлять приложения прямо с их объявлений о выпусках и получать уведомления о новых выпусках.</p>
|
<p>Obtainium позволяет вам устанавливать и обновлять приложения прямо с их объявлений о выпусках и получать уведомления о новых выпусках.</p>
|
||||||
<p>Для деталей читайте <a href="https://github.com/ImranR98/Obtainium/wiki">Вики</a></p>
|
<p>Для деталей читайте <a href="https://wiki.obtainium.imranr.dev/">Вики</a></p>
|
||||||
<p>
|
<p>
|
||||||
<b>Поддерживаемые источники приложений:</b>
|
<b>Поддерживаемые источники приложений:</b>
|
||||||
</p>
|
</p>
|
||||||
|
@@ -5,6 +5,8 @@ import 'package:html/parser.dart';
|
|||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:obtainium/components/generated_form.dart';
|
import 'package:obtainium/components/generated_form.dart';
|
||||||
import 'package:obtainium/custom_errors.dart';
|
import 'package:obtainium/custom_errors.dart';
|
||||||
|
import 'package:obtainium/providers/apps_provider.dart';
|
||||||
|
import 'package:obtainium/providers/settings_provider.dart';
|
||||||
import 'package:obtainium/providers/source_provider.dart';
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
|
||||||
class APKMirror extends AppSource {
|
class APKMirror extends AppSource {
|
||||||
@@ -31,6 +33,16 @@ class APKMirror extends AppSource {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, String>?> getRequestHeaders(
|
||||||
|
Map<String, dynamic> additionalSettings,
|
||||||
|
{bool forAPKDownload = false}) async {
|
||||||
|
return {
|
||||||
|
"User-Agent":
|
||||||
|
"Obtainium/${(await getInstalledInfo(obtainiumId))?.versionName ?? '1.0.0'}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||||
RegExp standardUrlRegEx = RegExp(
|
RegExp standardUrlRegEx = RegExp(
|
||||||
|
@@ -105,11 +105,7 @@ class APKPure extends AppSource {
|
|||||||
.map((e) => e.text.trim())
|
.map((e) => e.text.trim())
|
||||||
.map((t) => t == 'APKs' ? 'APK' : t) ??
|
.map((t) => t == 'APKs' ? 'APK' : t) ??
|
||||||
[];
|
[];
|
||||||
String type = types.isEmpty
|
String type = types.isEmpty ? 'APK' : types.first;
|
||||||
? 'APK'
|
|
||||||
: types.length == 1
|
|
||||||
? types.first
|
|
||||||
: types.last;
|
|
||||||
String? dateString = apkInfo
|
String? dateString = apkInfo
|
||||||
?.querySelector('div.info-bottom span.time')
|
?.querySelector('div.info-bottom span.time')
|
||||||
?.text
|
?.text
|
||||||
|
@@ -886,7 +886,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
launchUrlString('${settingsProvider.sourceUrl}/wiki',
|
launchUrlString('https://wiki.obtainium.imranr.dev/',
|
||||||
mode: LaunchMode.externalApplication);
|
mode: LaunchMode.externalApplication);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.help_outline_rounded),
|
icon: const Icon(Icons.help_outline_rounded),
|
||||||
|
@@ -516,11 +516,29 @@ class AppsProvider with ChangeNotifier {
|
|||||||
.listSync()
|
.listSync()
|
||||||
.where((e) => e.path.toLowerCase().endsWith('.apk'))
|
.where((e) => e.path.toLowerCase().endsWith('.apk'))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
FileSystemEntity? temp;
|
||||||
|
apks.removeWhere((element) {
|
||||||
|
bool res = element.uri.pathSegments.last.startsWith(app.id);
|
||||||
|
if (res) {
|
||||||
|
temp = element;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
if (temp != null) {
|
||||||
|
apks = [
|
||||||
|
temp!,
|
||||||
|
...apks,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < apks.length; i++) {
|
for (var i = 0; i < apks.length; i++) {
|
||||||
try {
|
try {
|
||||||
newInfo = await pm.getPackageArchiveInfo(
|
newInfo =
|
||||||
archiveFilePath: apks.first.path);
|
await pm.getPackageArchiveInfo(archiveFilePath: apks[i].path);
|
||||||
break;
|
if (newInfo != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (i == apks.length - 1) {
|
if (i == apks.length - 1) {
|
||||||
rethrow;
|
rethrow;
|
||||||
@@ -644,28 +662,47 @@ class AppsProvider with ChangeNotifier {
|
|||||||
var somethingInstalled = false;
|
var somethingInstalled = false;
|
||||||
try {
|
try {
|
||||||
MultiAppMultiError errors = MultiAppMultiError();
|
MultiAppMultiError errors = MultiAppMultiError();
|
||||||
|
List<File> APKFiles = [];
|
||||||
for (var file in dir.extracted
|
for (var file in dir.extracted
|
||||||
.listSync(recursive: true, followLinks: false)
|
.listSync(recursive: true, followLinks: false)
|
||||||
.whereType<File>()) {
|
.whereType<File>()) {
|
||||||
if (file.path.toLowerCase().endsWith('.apk')) {
|
if (file.path.toLowerCase().endsWith('.apk')) {
|
||||||
try {
|
APKFiles.add(file);
|
||||||
somethingInstalled = somethingInstalled ||
|
|
||||||
await installApk(
|
|
||||||
DownloadedApk(dir.appId, file), firstTimeWithContext,
|
|
||||||
needsBGWorkaround: needsBGWorkaround,
|
|
||||||
shizukuPretendToBeGooglePlay: shizukuPretendToBeGooglePlay);
|
|
||||||
} catch (e) {
|
|
||||||
logs.add(
|
|
||||||
'Could not install APK from XAPK \'${file.path}\': ${e.toString()}');
|
|
||||||
errors.add(dir.appId, e, appName: apps[dir.appId]?.name);
|
|
||||||
}
|
|
||||||
} else if (file.path.toLowerCase().endsWith('.obb')) {
|
} else if (file.path.toLowerCase().endsWith('.obb')) {
|
||||||
await moveObbFile(file, dir.appId);
|
await moveObbFile(file, dir.appId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (somethingInstalled) {
|
|
||||||
|
File? temp;
|
||||||
|
APKFiles.removeWhere((element) {
|
||||||
|
bool res = element.uri.pathSegments.last.startsWith(dir.appId);
|
||||||
|
if (res) {
|
||||||
|
temp = element;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
if (temp != null) {
|
||||||
|
APKFiles = [
|
||||||
|
temp!,
|
||||||
|
...APKFiles,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await installApk(
|
||||||
|
DownloadedApk(dir.appId, APKFiles[0]), firstTimeWithContext,
|
||||||
|
needsBGWorkaround: needsBGWorkaround,
|
||||||
|
shizukuPretendToBeGooglePlay: shizukuPretendToBeGooglePlay,
|
||||||
|
additionalAPKs: APKFiles.sublist(1)
|
||||||
|
.map((a) => DownloadedApk(dir.appId, a))
|
||||||
|
.toList());
|
||||||
|
somethingInstalled = true;
|
||||||
dir.file.delete(recursive: true);
|
dir.file.delete(recursive: true);
|
||||||
} else if (errors.idsByErrorString.isNotEmpty) {
|
} catch (e) {
|
||||||
|
logs.add('Could not install APKs from XAPK: ${e.toString()}');
|
||||||
|
errors.add(dir.appId, e, appName: apps[dir.appId]?.name);
|
||||||
|
}
|
||||||
|
if (errors.idsByErrorString.isNotEmpty) {
|
||||||
throw errors;
|
throw errors;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -677,7 +714,8 @@ class AppsProvider with ChangeNotifier {
|
|||||||
Future<bool> installApk(
|
Future<bool> installApk(
|
||||||
DownloadedApk file, BuildContext? firstTimeWithContext,
|
DownloadedApk file, BuildContext? firstTimeWithContext,
|
||||||
{bool needsBGWorkaround = false,
|
{bool needsBGWorkaround = false,
|
||||||
bool shizukuPretendToBeGooglePlay = false}) async {
|
bool shizukuPretendToBeGooglePlay = false,
|
||||||
|
List<DownloadedApk> additionalAPKs = const []}) async {
|
||||||
if (firstTimeWithContext != null &&
|
if (firstTimeWithContext != null &&
|
||||||
settingsProvider.beforeNewInstallsShareToAppVerifier &&
|
settingsProvider.beforeNewInstallsShareToAppVerifier &&
|
||||||
(await getInstalledInfo('dev.soupslurpr.appverifier')) != null) {
|
(await getInstalledInfo('dev.soupslurpr.appverifier')) != null) {
|
||||||
@@ -693,6 +731,7 @@ class AppsProvider with ChangeNotifier {
|
|||||||
if (newInfo == null) {
|
if (newInfo == null) {
|
||||||
try {
|
try {
|
||||||
file.file.deleteSync(recursive: true);
|
file.file.deleteSync(recursive: true);
|
||||||
|
additionalAPKs.forEach((a) => a.file.deleteSync(recursive: true));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
//
|
//
|
||||||
} finally {
|
} finally {
|
||||||
@@ -720,8 +759,10 @@ class AppsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
int? code;
|
int? code;
|
||||||
if (!settingsProvider.useShizuku) {
|
if (!settingsProvider.useShizuku) {
|
||||||
code =
|
var allAPKs = [file.file.path];
|
||||||
await AndroidPackageInstaller.installApk(apkFilePath: file.file.path);
|
allAPKs.addAll(additionalAPKs.map((a) => a.file.path));
|
||||||
|
code = await AndroidPackageInstaller.installApk(
|
||||||
|
apkFilePath: allAPKs.join(','));
|
||||||
} else {
|
} else {
|
||||||
code = await ShizukuApkInstaller.installAPK(file.file.uri.toString(),
|
code = await ShizukuApkInstaller.installAPK(file.file.uri.toString(),
|
||||||
shizukuPretendToBeGooglePlay ? "com.android.vending" : "");
|
shizukuPretendToBeGooglePlay ? "com.android.vending" : "");
|
||||||
|
22
pubspec.lock
22
pubspec.lock
@@ -14,7 +14,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: main
|
ref: main
|
||||||
resolved-ref: ba2aa7a11edc2649d1d80c25ed9291521262f714
|
resolved-ref: bcad19e964d377da8816718032e5dbf6dd16ba3a
|
||||||
url: "https://github.com/ImranR98/android_package_installer"
|
url: "https://github.com/ImranR98/android_package_installer"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
@@ -449,10 +449,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_markdown
|
name: flutter_markdown
|
||||||
sha256: f0e599ba89c9946c8e051780f0ec99aba4ba15895e0380a7ab68f420046fc44e
|
sha256: "999a4e3cb3e1532a971c86d6c73a480264f6a687959d4887cb4e2990821827e4"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.4+1"
|
version: "0.7.4+2"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -731,10 +731,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_html
|
name: permission_handler_html
|
||||||
sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851
|
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.3+2"
|
version: "0.1.3+5"
|
||||||
permission_handler_platform_interface:
|
permission_handler_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -945,10 +945,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqflite_common
|
name: sqflite_common
|
||||||
sha256: "4468b24876d673418a7b7147e5a08a715b4998a7ae69227acafaab762e0e5490"
|
sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.4+5"
|
version: "2.5.4+6"
|
||||||
sqflite_darwin:
|
sqflite_darwin:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1145,10 +1145,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webview_flutter_android
|
name: webview_flutter_android
|
||||||
sha256: "86c2d01c37c4578ee46560109cf2e18fb271f0d080a796f09188d0952352e057"
|
sha256: "285cedfd9441267f6cca8843458620b5fda1af75b04f5818d0441acda5d7df19"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.2"
|
version: "4.1.0"
|
||||||
webview_flutter_platform_interface:
|
webview_flutter_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1161,10 +1161,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webview_flutter_wkwebview
|
name: webview_flutter_wkwebview
|
||||||
sha256: "3be297aa4ca78205abdd284cf55f168c35246c75b3079990ad8ba9d257681a30"
|
sha256: b7e92f129482460951d96ef9a46b49db34bd2e1621685de26e9eaafd9674e7eb
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.16.2"
|
version: "3.16.3"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@@ -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
|
# 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
|
# 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.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.1.31+2288
|
version: 1.1.32+2289
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
|
Reference in New Issue
Block a user