mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-16 22:56:44 +02:00
Compare commits
9 Commits
v0.13.1-be
...
v0.13.2-be
Author | SHA1 | Date | |
---|---|---|---|
0cd4385de7 | |||
0774b3ddc3 | |||
b60b1ed058 | |||
b196715d60 | |||
0673e90dff | |||
59cfa242fb | |||
65ab72ba90 | |||
408bca8951 | |||
480467492a |
@ -121,12 +121,12 @@
|
|||||||
"followSystem": "System folgen",
|
"followSystem": "System folgen",
|
||||||
"obtainium": "Obtainium",
|
"obtainium": "Obtainium",
|
||||||
"materialYou": "Material You",
|
"materialYou": "Material You",
|
||||||
"useBlackTheme": "Use Pure Black Dark Theme",
|
"useBlackTheme": "Verwende Pure Black Dark Theme",
|
||||||
"appSortBy": "App sortieren nach",
|
"appSortBy": "App sortieren nach",
|
||||||
"authorName": "Autor/Name",
|
"authorName": "Autor/Name",
|
||||||
"nameAuthor": "Name/Autor",
|
"nameAuthor": "Name/Autor",
|
||||||
"asAdded": "Wie hinzugefügt",
|
"asAdded": "Wie hinzugefügt",
|
||||||
"appSortOrder": "App Sortierung nach",
|
"appSortOrder": "App sortieren nach",
|
||||||
"ascending": "Aufsteigend",
|
"ascending": "Aufsteigend",
|
||||||
"descending": "Absteigend",
|
"descending": "Absteigend",
|
||||||
"bgUpdateCheckInterval": "Prüfintervall für Hintergrundaktualisierung",
|
"bgUpdateCheckInterval": "Prüfintervall für Hintergrundaktualisierung",
|
||||||
@ -207,7 +207,7 @@
|
|||||||
"addCategory": "Kategorie hinzufügen",
|
"addCategory": "Kategorie hinzufügen",
|
||||||
"label": "Bezeichnung",
|
"label": "Bezeichnung",
|
||||||
"language": "Sprache",
|
"language": "Sprache",
|
||||||
"copiedToClipboard": "Copied to Clipboard",
|
"copiedToClipboard": "In die Zwischenablage kopiert",
|
||||||
"storagePermissionDenied": "Speicherberechtigung verweigert",
|
"storagePermissionDenied": "Speicherberechtigung verweigert",
|
||||||
"selectedCategorizeWarning": "Dadurch werden alle bestehenden Kategorieeinstellungen für die ausgewählten Apps ersetzt.",
|
"selectedCategorizeWarning": "Dadurch werden alle bestehenden Kategorieeinstellungen für die ausgewählten Apps ersetzt.",
|
||||||
"filterAPKsByRegEx": "APKs nach regulärem Ausdruck filtern",
|
"filterAPKsByRegEx": "APKs nach regulärem Ausdruck filtern",
|
||||||
@ -218,7 +218,7 @@
|
|||||||
"releaseDateAsVersionExplanation": "Diese Option sollte nur für Apps verwendet werden, bei denen die Versionserkennung nicht korrekt funktioniert, aber ein Veröffentlichungsdatum verfügbar ist.",
|
"releaseDateAsVersionExplanation": "Diese Option sollte nur für Apps verwendet werden, bei denen die Versionserkennung nicht korrekt funktioniert, aber ein Veröffentlichungsdatum verfügbar ist.",
|
||||||
"changes": "Änderungen",
|
"changes": "Änderungen",
|
||||||
"releaseDate": "Veröffentlichungsdatum",
|
"releaseDate": "Veröffentlichungsdatum",
|
||||||
"importFromURLsInFile": "Importieren von URLs aus Datei ( z.B. OPML)",
|
"importFromURLsInFile": "Importieren von URLs aus Datei (z. B. OPML)",
|
||||||
"versionDetection": "Versionserkennung",
|
"versionDetection": "Versionserkennung",
|
||||||
"standardVersionDetection": "Standardversionserkennung",
|
"standardVersionDetection": "Standardversionserkennung",
|
||||||
"groupByCategory": "Nach Kategorie gruppieren",
|
"groupByCategory": "Nach Kategorie gruppieren",
|
||||||
@ -227,11 +227,11 @@
|
|||||||
"dontShowAgain": "Nicht noch einmal zeigen",
|
"dontShowAgain": "Nicht noch einmal zeigen",
|
||||||
"dontShowTrackOnlyWarnings": "Warnung für 'Nur Nachverfolgen' nicht anzeigen",
|
"dontShowTrackOnlyWarnings": "Warnung für 'Nur Nachverfolgen' nicht anzeigen",
|
||||||
"dontShowAPKOriginWarnings": "Warnung für APK-Herkunft nicht anzeigen",
|
"dontShowAPKOriginWarnings": "Warnung für APK-Herkunft nicht anzeigen",
|
||||||
"moveNonInstalledAppsToBottom": "Move Non-Installed Apps to Bottom of Apps View",
|
"moveNonInstalledAppsToBottom": "Nicht installierte Apps ans Ende der Apps Ansicht verschieben",
|
||||||
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
"gitlabPATLabel": "GitLab Personal Access Token (Aktiviert Suche)",
|
||||||
"about": "About",
|
"about": "Über",
|
||||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
"requiresCredentialsInSettings": "Benötigt zusätzliche Anmeldedaten (in den Einstellungen)",
|
||||||
"checkOnStart": "Check Once on Start",
|
"checkOnStart": "Überprüfe einmalig beim Start",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "App entfernen?",
|
"one": "App entfernen?",
|
||||||
"other": "Apps entfernen?"
|
"other": "Apps entfernen?"
|
||||||
@ -258,7 +258,7 @@
|
|||||||
},
|
},
|
||||||
"minute": {
|
"minute": {
|
||||||
"one": "{} Minute",
|
"one": "{} Minute",
|
||||||
"other": "{} Minutes"
|
"other": "{} Minuten"
|
||||||
},
|
},
|
||||||
"hour": {
|
"hour": {
|
||||||
"one": "{} Stunde",
|
"one": "{} Stunde",
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
"githubStarredRepos": "GitHub 已星标仓库",
|
"githubStarredRepos": "GitHub 已星标仓库",
|
||||||
"uname": "用户名",
|
"uname": "用户名",
|
||||||
"wrongArgNum": "参数数量错误",
|
"wrongArgNum": "参数数量错误",
|
||||||
"xIsTrackOnly": "{} 为“仅追踪”模式",
|
"xIsTrackOnly": "{}为“仅追踪”模式",
|
||||||
"source": "源代码",
|
"source": "源代码",
|
||||||
"app": "应用",
|
"app": "应用",
|
||||||
"appsFromSourceAreTrackOnly": "此来源的应用为“仅追踪”模式。",
|
"appsFromSourceAreTrackOnly": "此来源的应用为“仅追踪”模式。",
|
||||||
@ -50,8 +50,8 @@
|
|||||||
"search": "搜索",
|
"search": "搜索",
|
||||||
"additionalOptsFor": "{} 的更多选项",
|
"additionalOptsFor": "{} 的更多选项",
|
||||||
"supportedSourcesBelow": "支持的来源:",
|
"supportedSourcesBelow": "支持的来源:",
|
||||||
"trackOnlyInBrackets": "(仅追踪)",
|
"trackOnlyInBrackets": "(仅追踪)",
|
||||||
"searchableInBrackets": "(可搜索)",
|
"searchableInBrackets": "(可搜索)",
|
||||||
"appsString": "应用列表",
|
"appsString": "应用列表",
|
||||||
"noApps": "无应用",
|
"noApps": "无应用",
|
||||||
"noAppsForFilter": "没有符合条件的应用",
|
"noAppsForFilter": "没有符合条件的应用",
|
||||||
@ -59,9 +59,9 @@
|
|||||||
"percentProgress": "进度:{}%",
|
"percentProgress": "进度:{}%",
|
||||||
"pleaseWait": "请稍候",
|
"pleaseWait": "请稍候",
|
||||||
"updateAvailable": "更新可用",
|
"updateAvailable": "更新可用",
|
||||||
"estimateInBracketsShort": "(预计)",
|
"estimateInBracketsShort": "(推测)",
|
||||||
"notInstalled": "未安装",
|
"notInstalled": "未安装",
|
||||||
"estimateInBrackets": "(预计)",
|
"estimateInBrackets": "(推测)",
|
||||||
"selectAll": "全选",
|
"selectAll": "全选",
|
||||||
"deselectN": "取消选择 {}",
|
"deselectN": "取消选择 {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} 将从 Obtainium 中删除,但仍安装在您的设备中。",
|
"xWillBeRemovedButRemainInstalled": "{} 将从 Obtainium 中删除,但仍安装在您的设备中。",
|
||||||
@ -74,8 +74,8 @@
|
|||||||
"installUpdateApps": "安装/更新应用",
|
"installUpdateApps": "安装/更新应用",
|
||||||
"installUpdateSelectedApps": "安装/更新选中的应用",
|
"installUpdateSelectedApps": "安装/更新选中的应用",
|
||||||
"markXSelectedAppsAsUpdated": "是否将选中的 {} 个应用标记为已更新?",
|
"markXSelectedAppsAsUpdated": "是否将选中的 {} 个应用标记为已更新?",
|
||||||
"no": "不要",
|
"no": "否",
|
||||||
"yes": "好的",
|
"yes": "是",
|
||||||
"markSelectedAppsUpdated": "将选中的应用标记为已更新",
|
"markSelectedAppsUpdated": "将选中的应用标记为已更新",
|
||||||
"pinToTop": "置顶",
|
"pinToTop": "置顶",
|
||||||
"unpinFromTop": "取消置顶",
|
"unpinFromTop": "取消置顶",
|
||||||
@ -142,7 +142,7 @@
|
|||||||
"close": "关闭",
|
"close": "关闭",
|
||||||
"share": "分享",
|
"share": "分享",
|
||||||
"appNotFound": "未找到应用",
|
"appNotFound": "未找到应用",
|
||||||
"obtainiumExportHyphenatedLowercase": "obtainium-导出",
|
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
||||||
"pickAnAPK": "选择一个 APK 文件",
|
"pickAnAPK": "选择一个 APK 文件",
|
||||||
"appHasMoreThanOnePackage": "{} 有多个架构可用:",
|
"appHasMoreThanOnePackage": "{} 有多个架构可用:",
|
||||||
"deviceSupportsXArch": "您的设备支持 {} 架构。",
|
"deviceSupportsXArch": "您的设备支持 {} 架构。",
|
||||||
@ -172,13 +172,13 @@
|
|||||||
"versionCorrectionDisabled": "禁用版本号更正(插件似乎未起作用)",
|
"versionCorrectionDisabled": "禁用版本号更正(插件似乎未起作用)",
|
||||||
"unknown": "未知",
|
"unknown": "未知",
|
||||||
"none": "无",
|
"none": "无",
|
||||||
"never": "从不",
|
"never": "从未",
|
||||||
"latestVersionX": "最新版本:{}",
|
"latestVersionX": "最新版本:{}",
|
||||||
"installedVersionX": "当前版本:{}",
|
"installedVersionX": "当前版本:{}",
|
||||||
"lastUpdateCheckX": "上次更新检查:{}",
|
"lastUpdateCheckX": "上次更新检查:{}",
|
||||||
"remove": "删除",
|
"remove": "删除",
|
||||||
"yesMarkUpdated": "是的,标记为已更新",
|
"yesMarkUpdated": "是,标记为已更新",
|
||||||
"fdroid": "F-Droid Official",
|
"fdroid": "F-Droid 官方存储库",
|
||||||
"appIdOrName": "应用 ID 或名称",
|
"appIdOrName": "应用 ID 或名称",
|
||||||
"appWithIdOrNameNotFound": "未找到符合此 ID 或名称的应用",
|
"appWithIdOrNameNotFound": "未找到符合此 ID 或名称的应用",
|
||||||
"reposHaveMultipleApps": "存储库中可能包含多个应用",
|
"reposHaveMultipleApps": "存储库中可能包含多个应用",
|
||||||
@ -193,7 +193,7 @@
|
|||||||
"additionalOptions": "附加选项",
|
"additionalOptions": "附加选项",
|
||||||
"disableVersionDetection": "禁用版本检测",
|
"disableVersionDetection": "禁用版本检测",
|
||||||
"noVersionDetectionExplanation": "此选项应该仅用于无法进行版本检测的应用。",
|
"noVersionDetectionExplanation": "此选项应该仅用于无法进行版本检测的应用。",
|
||||||
"downloadingX": "正在下载 {}",
|
"downloadingX": "正在下载{}",
|
||||||
"downloadNotifDescription": "提示应用的下载进度",
|
"downloadNotifDescription": "提示应用的下载进度",
|
||||||
"noAPKFound": "未找到 APK 文件",
|
"noAPKFound": "未找到 APK 文件",
|
||||||
"noVersionDetection": "禁用版本检测",
|
"noVersionDetection": "禁用版本检测",
|
||||||
@ -222,16 +222,16 @@
|
|||||||
"versionDetection": "版本检测",
|
"versionDetection": "版本检测",
|
||||||
"standardVersionDetection": "常规版本检测",
|
"standardVersionDetection": "常规版本检测",
|
||||||
"groupByCategory": "按类别分组显示",
|
"groupByCategory": "按类别分组显示",
|
||||||
"autoApkFilterByArch": "如果可能,尝试按 CPU 架构筛选 APK 文件",
|
"autoApkFilterByArch": "如果可能,尝试按设备支持的 CPU 架构筛选 APK 文件",
|
||||||
"overrideSource": "Override Source",
|
"overrideSource": "覆盖来源",
|
||||||
"dontShowAgain": "Don't show this again",
|
"dontShowAgain": "不再显示",
|
||||||
"dontShowTrackOnlyWarnings": "Don't Show the 'Track-Only' Warning",
|
"dontShowTrackOnlyWarnings": "不显示“仅追踪”模式警告",
|
||||||
"dontShowAPKOriginWarnings": "Don't Show APK Origin Warnings",
|
"dontShowAPKOriginWarnings": "不显示 APK 文件来源警告",
|
||||||
"moveNonInstalledAppsToBottom": "Move Non-Installed Apps to Bottom of Apps View",
|
"moveNonInstalledAppsToBottom": "将未安装应用置底",
|
||||||
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
"gitlabPATLabel": "GitLab 个人访问令牌(用于搜索)",
|
||||||
"about": "About",
|
"about": "相关文档",
|
||||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
"requiresCredentialsInSettings": "此功能需要额外的凭据(在“设置”中添加)",
|
||||||
"checkOnStart": "Check Once on Start",
|
"checkOnStart": "启动时进行一次检查",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "是否删除应用?",
|
"one": "是否删除应用?",
|
||||||
"other": "是否删除应用?"
|
"other": "是否删除应用?"
|
||||||
|
@ -89,6 +89,13 @@ class HTML extends AppSource {
|
|||||||
overrideEligible = true;
|
overrideEligible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// TODO: implement requestHeaders choice, hardcoded for now
|
||||||
|
Map<String, String>? get requestHeaders => {
|
||||||
|
"User-Agent":
|
||||||
|
"Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0"
|
||||||
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
return url;
|
return url;
|
||||||
|
@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
|
|||||||
// ignore: implementation_imports
|
// ignore: implementation_imports
|
||||||
import 'package:easy_localization/src/localization.dart';
|
import 'package:easy_localization/src/localization.dart';
|
||||||
|
|
||||||
const String currentVersion = '0.13.1';
|
const String currentVersion = '0.13.2';
|
||||||
const String currentReleaseTag =
|
const String currentReleaseTag =
|
||||||
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
||||||
|
|
||||||
|
@ -283,6 +283,9 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
}
|
}
|
||||||
si++;
|
si++;
|
||||||
}
|
}
|
||||||
|
if (res.isEmpty) {
|
||||||
|
throw ObtainiumError(tr('noResults'));
|
||||||
|
}
|
||||||
List<String>? selectedUrls = res.isEmpty
|
List<String>? selectedUrls = res.isEmpty
|
||||||
? []
|
? []
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
@ -377,13 +380,15 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 16,
|
width: 16,
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
searching
|
||||||
onPressed: searchQuery.isEmpty || doingSomething
|
? const CircularProgressIndicator()
|
||||||
? null
|
: ElevatedButton(
|
||||||
: () {
|
onPressed: searchQuery.isEmpty || doingSomething
|
||||||
runSearch();
|
? null
|
||||||
},
|
: () {
|
||||||
child: Text(tr('search')))
|
runSearch();
|
||||||
|
},
|
||||||
|
child: Text(tr('search')))
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -444,7 +444,9 @@ class _AppPageState extends State<AppPage> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(0, 8, 0, 0),
|
padding: const EdgeInsets.fromLTRB(0, 8, 0, 0),
|
||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
value: app!.downloadProgress! / 100))
|
value: app!.downloadProgress! >= 0
|
||||||
|
? app.downloadProgress! / 100
|
||||||
|
: null))
|
||||||
],
|
],
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -542,8 +542,12 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
? SizedBox(
|
? SizedBox(
|
||||||
width: 110,
|
width: 110,
|
||||||
child: Text(tr('percentProgress', args: [
|
child: Text(tr('percentProgress', args: [
|
||||||
listedApps[index].downloadProgress?.toInt().toString() ??
|
listedApps[index].downloadProgress! >= 0
|
||||||
'100'
|
? listedApps[index]
|
||||||
|
.downloadProgress!
|
||||||
|
.toInt()
|
||||||
|
.toString()
|
||||||
|
: tr('pleaseWait')
|
||||||
])))
|
])))
|
||||||
: trailingRow,
|
: trailingRow,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
@ -122,25 +122,34 @@ class AppsProvider with ChangeNotifier {
|
|||||||
// Load Apps into memory (in background, this is done later instead of in the constructor)
|
// Load Apps into memory (in background, this is done later instead of in the constructor)
|
||||||
await loadApps();
|
await loadApps();
|
||||||
// Delete any partial APKs
|
// Delete any partial APKs
|
||||||
|
var cutoff = DateTime.now().subtract(const Duration(days: 7));
|
||||||
(await getExternalCacheDirectories())
|
(await getExternalCacheDirectories())
|
||||||
?.first
|
?.first
|
||||||
.listSync()
|
.listSync()
|
||||||
.where((element) => element.path.endsWith('.apk.part'))
|
.where((element) =>
|
||||||
|
element.path.endsWith('.part') ||
|
||||||
|
element.statSync().modified.isBefore(cutoff))
|
||||||
.forEach((partialApk) {
|
.forEach((partialApk) {
|
||||||
partialApk.delete();
|
partialApk.delete();
|
||||||
});
|
});
|
||||||
}();
|
}();
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadFile(String url, String fileName, Function? onProgress,
|
Future<File> downloadFile(
|
||||||
|
String url, String fileNameNoExt, Function? onProgress,
|
||||||
{bool useExisting = true, Map<String, String>? headers}) async {
|
{bool useExisting = true, Map<String, String>? headers}) async {
|
||||||
var destDir = (await getExternalCacheDirectories())!.first.path;
|
var destDir = (await getExternalCacheDirectories())!.first.path;
|
||||||
var req = Request('GET', Uri.parse(url));
|
var req = Request('GET', Uri.parse(url));
|
||||||
if (headers != null) {
|
if (headers != null) {
|
||||||
req.headers.addAll(headers);
|
req.headers.addAll(headers);
|
||||||
}
|
}
|
||||||
StreamedResponse response = await Client().send(req);
|
var client = Client();
|
||||||
File downloadedFile = File('$destDir/$fileName');
|
StreamedResponse response = await client.send(req);
|
||||||
|
var ext = response.headers['content-disposition']!.split('.').last;
|
||||||
|
if (ext.endsWith('"') || ext.endsWith("other")) {
|
||||||
|
ext = ext.substring(0, ext.length - 1);
|
||||||
|
}
|
||||||
|
File downloadedFile = File('$destDir/$fileNameNoExt.$ext');
|
||||||
if (!(downloadedFile.existsSync() && useExisting)) {
|
if (!(downloadedFile.existsSync() && useExisting)) {
|
||||||
File tempDownloadedFile = File('${downloadedFile.path}.part');
|
File tempDownloadedFile = File('${downloadedFile.path}.part');
|
||||||
if (tempDownloadedFile.existsSync()) {
|
if (tempDownloadedFile.existsSync()) {
|
||||||
@ -168,12 +177,14 @@ class AppsProvider with ChangeNotifier {
|
|||||||
throw response.reasonPhrase ?? tr('unexpectedError');
|
throw response.reasonPhrase ?? tr('unexpectedError');
|
||||||
}
|
}
|
||||||
tempDownloadedFile.renameSync(downloadedFile.path);
|
tempDownloadedFile.renameSync(downloadedFile.path);
|
||||||
|
} else {
|
||||||
|
client.close();
|
||||||
}
|
}
|
||||||
return downloadedFile;
|
return downloadedFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAPKIDChange(App app, PackageArchiveInfo newInfo, File downloadedFile,
|
Future<File> handleAPKIDChange(App app, PackageArchiveInfo newInfo,
|
||||||
String downloadUrl) async {
|
File downloadedFile, String downloadUrl) async {
|
||||||
// If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed
|
// If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed
|
||||||
// The former case should be handled (give the App its real ID), the latter is a security issue
|
// The former case should be handled (give the App its real ID), the latter is a security issue
|
||||||
if (app.id != newInfo.packageName) {
|
if (app.id != newInfo.packageName) {
|
||||||
@ -184,12 +195,13 @@ class AppsProvider with ChangeNotifier {
|
|||||||
var originalAppId = app.id;
|
var originalAppId = app.id;
|
||||||
app.id = newInfo.packageName;
|
app.id = newInfo.packageName;
|
||||||
downloadedFile = downloadedFile.renameSync(
|
downloadedFile = downloadedFile.renameSync(
|
||||||
'${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.apk');
|
'${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.${downloadedFile.path.split('.').last}');
|
||||||
if (apps[originalAppId] != null) {
|
if (apps[originalAppId] != null) {
|
||||||
await removeApps([originalAppId]);
|
await removeApps([originalAppId]);
|
||||||
await saveApps([app], onlyIfExists: !isTempId);
|
await saveApps([app], onlyIfExists: !isTempId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return downloadedFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Object> downloadApp(App app, BuildContext? context) async {
|
Future<Object> downloadApp(App app, BuildContext? context) async {
|
||||||
@ -205,11 +217,11 @@ class AppsProvider with ChangeNotifier {
|
|||||||
.getSource(app.url, overrideSource: app.overrideSource);
|
.getSource(app.url, overrideSource: app.overrideSource);
|
||||||
String downloadUrl = await source.apkUrlPrefetchModifier(
|
String downloadUrl = await source.apkUrlPrefetchModifier(
|
||||||
app.apkUrls[app.preferredApkIndex].value, app.url);
|
app.apkUrls[app.preferredApkIndex].value, app.url);
|
||||||
var fileName = '${app.id}-${downloadUrl.hashCode}.apk';
|
|
||||||
var notif = DownloadNotification(app.finalName, 100);
|
var notif = DownloadNotification(app.finalName, 100);
|
||||||
notificationsProvider?.cancel(notif.id);
|
notificationsProvider?.cancel(notif.id);
|
||||||
int? prevProg;
|
int? prevProg;
|
||||||
File downloadedFile = await downloadFile(downloadUrl, fileName,
|
var fileNameNoExt = '${app.id}-${downloadUrl.hashCode}';
|
||||||
|
var downloadedFile = await downloadFile(downloadUrl, fileNameNoExt,
|
||||||
headers: source.requestHeaders, (double? progress) {
|
headers: source.requestHeaders, (double? progress) {
|
||||||
int? prog = progress?.ceil();
|
int? prog = progress?.ceil();
|
||||||
if (apps[app.id] != null) {
|
if (apps[app.id] != null) {
|
||||||
@ -222,18 +234,20 @@ class AppsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
prevProg = prog;
|
prevProg = prog;
|
||||||
});
|
});
|
||||||
PackageArchiveInfo? newInfo;
|
// Set to 90 for remaining steps, will make null in 'finally'
|
||||||
try {
|
if (apps[app.id] != null) {
|
||||||
newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path);
|
apps[app.id]!.downloadProgress = -1;
|
||||||
} catch (e) {
|
notifyListeners();
|
||||||
// Assume it's an XAPK
|
notif = DownloadNotification(app.finalName, -1);
|
||||||
fileName = '${app.id}-${downloadUrl.hashCode}.xapk';
|
notificationsProvider?.notify(notif);
|
||||||
String newPath = '${downloadedFile.parent.path}/$fileName';
|
|
||||||
downloadedFile.renameSync(newPath);
|
|
||||||
downloadedFile = File(newPath);
|
|
||||||
}
|
}
|
||||||
|
PackageArchiveInfo? newInfo;
|
||||||
|
var isAPK = downloadedFile.path.toLowerCase().endsWith('.apk');
|
||||||
Directory? xapkDir;
|
Directory? xapkDir;
|
||||||
if (newInfo == null) {
|
if (isAPK) {
|
||||||
|
newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path);
|
||||||
|
} else {
|
||||||
|
// Assume XAPK
|
||||||
String xapkDirPath = '${downloadedFile.path}-dir';
|
String xapkDirPath = '${downloadedFile.path}-dir';
|
||||||
unzipFile(downloadedFile.path, '${downloadedFile.path}-dir');
|
unzipFile(downloadedFile.path, '${downloadedFile.path}-dir');
|
||||||
xapkDir = Directory(xapkDirPath);
|
xapkDir = Directory(xapkDirPath);
|
||||||
@ -243,20 +257,21 @@ class AppsProvider with ChangeNotifier {
|
|||||||
.toList();
|
.toList();
|
||||||
newInfo = await PackageArchiveInfo.fromPath(apks.first.path);
|
newInfo = await PackageArchiveInfo.fromPath(apks.first.path);
|
||||||
}
|
}
|
||||||
await handleAPKIDChange(app, newInfo, downloadedFile, downloadUrl);
|
downloadedFile =
|
||||||
|
await handleAPKIDChange(app, newInfo, downloadedFile, downloadUrl);
|
||||||
// Delete older versions of the file if any
|
// Delete older versions of the file if any
|
||||||
for (var file in downloadedFile.parent.listSync()) {
|
for (var file in downloadedFile.parent.listSync()) {
|
||||||
var fn = file.path.split('/').last;
|
var fn = file.path.split('/').last;
|
||||||
if (fn.startsWith('${app.id}-') &&
|
if (fn.startsWith('${app.id}-') &&
|
||||||
fn.toLowerCase().endsWith(xapkDir == null ? '.apk' : '.xapk') &&
|
FileSystemEntity.isFileSync(file.path) &&
|
||||||
fn != downloadedFile.path.split('/').last) {
|
file.path != downloadedFile.path) {
|
||||||
file.delete();
|
file.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (xapkDir != null) {
|
if (isAPK) {
|
||||||
return DownloadedXApkDir(app.id, downloadedFile, xapkDir);
|
|
||||||
} else {
|
|
||||||
return DownloadedApk(app.id, downloadedFile);
|
return DownloadedApk(app.id, downloadedFile);
|
||||||
|
} else {
|
||||||
|
return DownloadedXApkDir(app.id, downloadedFile, xapkDir!);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
notificationsProvider?.cancel(notifId);
|
notificationsProvider?.cancel(notifId);
|
||||||
@ -324,18 +339,23 @@ class AppsProvider with ChangeNotifier {
|
|||||||
Future<void> installXApkDir(DownloadedXApkDir dir,
|
Future<void> installXApkDir(DownloadedXApkDir dir,
|
||||||
{bool silent = false}) async {
|
{bool silent = false}) async {
|
||||||
try {
|
try {
|
||||||
|
var somethingInstalled = false;
|
||||||
for (var apk in dir.extracted
|
for (var apk in dir.extracted
|
||||||
.listSync()
|
.listSync()
|
||||||
.where((f) => f is File && f.path.toLowerCase().endsWith('.apk'))) {
|
.where((f) => f is File && f.path.toLowerCase().endsWith('.apk'))) {
|
||||||
await installApk(DownloadedApk(dir.appId, apk as File), silent: silent);
|
somethingInstalled = somethingInstalled ||
|
||||||
|
await installApk(DownloadedApk(dir.appId, apk as File),
|
||||||
|
silent: silent);
|
||||||
|
}
|
||||||
|
if (somethingInstalled) {
|
||||||
|
dir.file.delete();
|
||||||
}
|
}
|
||||||
dir.file.delete();
|
|
||||||
} finally {
|
} finally {
|
||||||
dir.extracted.delete(recursive: true);
|
dir.extracted.delete(recursive: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> installApk(DownloadedApk file, {bool silent = false}) async {
|
Future<bool> installApk(DownloadedApk file, {bool silent = false}) async {
|
||||||
// TODO: Use 'silent' when/if ever possible
|
// TODO: Use 'silent' when/if ever possible
|
||||||
var newInfo = await PackageArchiveInfo.fromPath(file.file.path);
|
var newInfo = await PackageArchiveInfo.fromPath(file.file.path);
|
||||||
AppInfo? appInfo;
|
AppInfo? appInfo;
|
||||||
@ -351,14 +371,17 @@ class AppsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
int? code =
|
int? code =
|
||||||
await AndroidPackageInstaller.installApk(apkFilePath: file.file.path);
|
await AndroidPackageInstaller.installApk(apkFilePath: file.file.path);
|
||||||
|
bool installed = false;
|
||||||
if (code != null && code != 0 && code != 3) {
|
if (code != null && code != 0 && code != 3) {
|
||||||
throw InstallError(code);
|
throw InstallError(code);
|
||||||
} else if (code == 0) {
|
} else if (code == 0) {
|
||||||
|
installed = true;
|
||||||
apps[file.appId]!.app.installedVersion =
|
apps[file.appId]!.app.installedVersion =
|
||||||
apps[file.appId]!.app.latestVersion;
|
apps[file.appId]!.app.latestVersion;
|
||||||
file.file.delete();
|
file.file.delete();
|
||||||
}
|
}
|
||||||
await saveApps([apps[file.appId]!.app]);
|
await saveApps([apps[file.appId]!.app]);
|
||||||
|
return installed;
|
||||||
}
|
}
|
||||||
|
|
||||||
void uninstallApp(String appId) async {
|
void uninstallApp(String appId) async {
|
||||||
@ -503,10 +526,17 @@ class AppsProvider with ChangeNotifier {
|
|||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
await waitForUserToReturnToForeground(context);
|
await waitForUserToReturnToForeground(context);
|
||||||
}
|
}
|
||||||
if (downloadedFile != null) {
|
apps[id]?.downloadProgress = -1;
|
||||||
await installApk(downloadedFile, silent: willBeSilent);
|
notifyListeners();
|
||||||
} else {
|
try {
|
||||||
await installXApkDir(downloadedDir!, silent: willBeSilent);
|
if (downloadedFile != null) {
|
||||||
|
await installApk(downloadedFile, silent: willBeSilent);
|
||||||
|
} else {
|
||||||
|
await installXApkDir(downloadedDir!, silent: willBeSilent);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
apps[id]?.downloadProgress = null;
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
installedIds.add(id);
|
installedIds.add(id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -759,11 +789,18 @@ class AppsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> removeApps(List<String> appIds) async {
|
Future<void> removeApps(List<String> appIds) async {
|
||||||
|
var apkFiles = (await getExternalCacheDirectories())?.first.listSync();
|
||||||
for (var appId in appIds) {
|
for (var appId in appIds) {
|
||||||
File file = File('${(await getAppsDir()).path}/$appId.json');
|
File file = File('${(await getAppsDir()).path}/$appId.json');
|
||||||
if (file.existsSync()) {
|
if (file.existsSync()) {
|
||||||
file.deleteSync();
|
file.deleteSync();
|
||||||
}
|
}
|
||||||
|
apkFiles
|
||||||
|
?.where(
|
||||||
|
(element) => element.path.split('/').last.startsWith('$appId-'))
|
||||||
|
.forEach((element) {
|
||||||
|
element.delete();
|
||||||
|
});
|
||||||
if (apps.containsKey(appId)) {
|
if (apps.containsKey(appId)) {
|
||||||
apps.remove(appId);
|
apps.remove(appId);
|
||||||
}
|
}
|
||||||
|
@ -167,7 +167,8 @@ class NotificationsProvider {
|
|||||||
progress: progPercent ?? 0,
|
progress: progPercent ?? 0,
|
||||||
maxProgress: 100,
|
maxProgress: 100,
|
||||||
showProgress: progPercent != null,
|
showProgress: progPercent != null,
|
||||||
onlyAlertOnce: onlyAlertOnce)));
|
onlyAlertOnce: onlyAlertOnce,
|
||||||
|
indeterminate: progPercent != null && progPercent < 0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> notify(ObtainiumNotification notif,
|
Future<void> notify(ObtainiumNotification notif,
|
||||||
|
20
pubspec.lock
20
pubspec.lock
@ -426,10 +426,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: c7edf82217d4b2952b2129a61d3ad60f1075b9299e629e149a8d2e39c2e6aad4
|
sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.14"
|
version: "2.0.15"
|
||||||
path_provider_android:
|
path_provider_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -442,10 +442,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_foundation
|
name: path_provider_foundation
|
||||||
sha256: ad4c4d011830462633f03eb34445a45345673dfd4faf1ab0b4735fbd93b19183
|
sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.2"
|
version: "2.2.3"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -578,10 +578,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: shared_preferences
|
name: shared_preferences
|
||||||
sha256: "858aaa72d8f61637d64e776aca82e1c67e6d9ee07979123c5d17115031c1b13b"
|
sha256: "16d3fb6b3692ad244a695c0183fca18cf81fd4b821664394a781de42386bf022"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.1"
|
||||||
shared_preferences_android:
|
shared_preferences_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -594,10 +594,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_foundation
|
name: shared_preferences_foundation
|
||||||
sha256: "0c1c16c56c9708aa9c361541a6f0e5cc6fc12a3232d866a687a7b7db30032b07"
|
sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.2"
|
||||||
shared_preferences_linux:
|
shared_preferences_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -647,10 +647,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sqflite
|
name: sqflite
|
||||||
sha256: acf091c6e55c50d00b30b8532b2dd23e393cf775861665ebd0f15cdd6ebfb079
|
sha256: "3a82c9a216b46b88617e3714dd74227eaca20c501c4abcc213e56db26b9caa00"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.8+1"
|
version: "2.2.8+2"
|
||||||
sqflite_common:
|
sqflite_common:
|
||||||
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: 0.13.1+165 # When changing this, update the tag in main() accordingly
|
version: 0.13.2+166 # When changing this, update the tag in main() accordingly
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.18.2 <3.0.0'
|
sdk: '>=2.18.2 <3.0.0'
|
||||||
|
Reference in New Issue
Block a user