Merge pull request #560 from ImranR98/dev

XAPK Bugfixes #541, HTML User-Agent #545, Better APK Cleanup #551, Search UI Improvements #550
This commit is contained in:
Imran Remtulla
2023-05-12 18:02:21 -04:00
committed by GitHub
9 changed files with 111 additions and 55 deletions

View File

@@ -89,6 +89,13 @@ class HTML extends AppSource {
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
String sourceSpecificStandardizeURL(String url) {
return url;

View File

@@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports
import 'package:easy_localization/src/localization.dart';
const String currentVersion = '0.13.1';
const String currentVersion = '0.13.2';
const String currentReleaseTag =
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES

View File

@@ -283,6 +283,9 @@ class _AddAppPageState extends State<AddAppPage> {
}
si++;
}
if (res.isEmpty) {
throw ObtainiumError(tr('noResults'));
}
List<String>? selectedUrls = res.isEmpty
? []
// ignore: use_build_context_synchronously
@@ -377,13 +380,15 @@ class _AddAppPageState extends State<AddAppPage> {
const SizedBox(
width: 16,
),
ElevatedButton(
onPressed: searchQuery.isEmpty || doingSomething
? null
: () {
runSearch();
},
child: Text(tr('search')))
searching
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: searchQuery.isEmpty || doingSomething
? null
: () {
runSearch();
},
child: Text(tr('search')))
],
);

View File

@@ -444,7 +444,9 @@ class _AppPageState extends State<AppPage> {
Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 0, 0),
child: LinearProgressIndicator(
value: app!.downloadProgress! / 100))
value: app!.downloadProgress! >= 0
? app.downloadProgress! / 100
: null))
],
));

View File

@@ -542,8 +542,12 @@ class AppsPageState extends State<AppsPage> {
? SizedBox(
width: 110,
child: Text(tr('percentProgress', args: [
listedApps[index].downloadProgress?.toInt().toString() ??
'100'
listedApps[index].downloadProgress! >= 0
? listedApps[index]
.downloadProgress!
.toInt()
.toString()
: tr('pleaseWait')
])))
: trailingRow,
onTap: () {

View File

@@ -122,25 +122,34 @@ class AppsProvider with ChangeNotifier {
// Load Apps into memory (in background, this is done later instead of in the constructor)
await loadApps();
// Delete any partial APKs
var cutoff = DateTime.now().subtract(const Duration(days: 7));
(await getExternalCacheDirectories())
?.first
.listSync()
.where((element) => element.path.endsWith('.apk.part'))
.where((element) =>
element.path.endsWith('.part') ||
element.statSync().modified.isBefore(cutoff))
.forEach((partialApk) {
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 {
var destDir = (await getExternalCacheDirectories())!.first.path;
var req = Request('GET', Uri.parse(url));
if (headers != null) {
req.headers.addAll(headers);
}
StreamedResponse response = await Client().send(req);
File downloadedFile = File('$destDir/$fileName');
var client = Client();
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)) {
File tempDownloadedFile = File('${downloadedFile.path}.part');
if (tempDownloadedFile.existsSync()) {
@@ -168,12 +177,14 @@ class AppsProvider with ChangeNotifier {
throw response.reasonPhrase ?? tr('unexpectedError');
}
tempDownloadedFile.renameSync(downloadedFile.path);
} else {
client.close();
}
return downloadedFile;
}
handleAPKIDChange(App app, PackageArchiveInfo newInfo, File downloadedFile,
String downloadUrl) async {
Future<File> handleAPKIDChange(App app, PackageArchiveInfo newInfo,
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
// The former case should be handled (give the App its real ID), the latter is a security issue
if (app.id != newInfo.packageName) {
@@ -184,12 +195,13 @@ class AppsProvider with ChangeNotifier {
var originalAppId = app.id;
app.id = newInfo.packageName;
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) {
await removeApps([originalAppId]);
await saveApps([app], onlyIfExists: !isTempId);
}
}
return downloadedFile;
}
Future<Object> downloadApp(App app, BuildContext? context) async {
@@ -205,11 +217,11 @@ class AppsProvider with ChangeNotifier {
.getSource(app.url, overrideSource: app.overrideSource);
String downloadUrl = await source.apkUrlPrefetchModifier(
app.apkUrls[app.preferredApkIndex].value, app.url);
var fileName = '${app.id}-${downloadUrl.hashCode}.apk';
var notif = DownloadNotification(app.finalName, 100);
notificationsProvider?.cancel(notif.id);
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) {
int? prog = progress?.ceil();
if (apps[app.id] != null) {
@@ -222,18 +234,20 @@ class AppsProvider with ChangeNotifier {
}
prevProg = prog;
});
PackageArchiveInfo? newInfo;
try {
newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path);
} catch (e) {
// Assume it's an XAPK
fileName = '${app.id}-${downloadUrl.hashCode}.xapk';
String newPath = '${downloadedFile.parent.path}/$fileName';
downloadedFile.renameSync(newPath);
downloadedFile = File(newPath);
// Set to 90 for remaining steps, will make null in 'finally'
if (apps[app.id] != null) {
apps[app.id]!.downloadProgress = -1;
notifyListeners();
notif = DownloadNotification(app.finalName, -1);
notificationsProvider?.notify(notif);
}
PackageArchiveInfo? newInfo;
var isAPK = downloadedFile.path.toLowerCase().endsWith('.apk');
Directory? xapkDir;
if (newInfo == null) {
if (isAPK) {
newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path);
} else {
// Assume XAPK
String xapkDirPath = '${downloadedFile.path}-dir';
unzipFile(downloadedFile.path, '${downloadedFile.path}-dir');
xapkDir = Directory(xapkDirPath);
@@ -243,20 +257,21 @@ class AppsProvider with ChangeNotifier {
.toList();
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
for (var file in downloadedFile.parent.listSync()) {
var fn = file.path.split('/').last;
if (fn.startsWith('${app.id}-') &&
fn.toLowerCase().endsWith(xapkDir == null ? '.apk' : '.xapk') &&
fn != downloadedFile.path.split('/').last) {
FileSystemEntity.isFileSync(file.path) &&
file.path != downloadedFile.path) {
file.delete();
}
}
if (xapkDir != null) {
return DownloadedXApkDir(app.id, downloadedFile, xapkDir);
} else {
if (isAPK) {
return DownloadedApk(app.id, downloadedFile);
} else {
return DownloadedXApkDir(app.id, downloadedFile, xapkDir!);
}
} finally {
notificationsProvider?.cancel(notifId);
@@ -324,18 +339,23 @@ class AppsProvider with ChangeNotifier {
Future<void> installXApkDir(DownloadedXApkDir dir,
{bool silent = false}) async {
try {
var somethingInstalled = false;
for (var apk in dir.extracted
.listSync()
.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 {
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
var newInfo = await PackageArchiveInfo.fromPath(file.file.path);
AppInfo? appInfo;
@@ -351,14 +371,17 @@ class AppsProvider with ChangeNotifier {
}
int? code =
await AndroidPackageInstaller.installApk(apkFilePath: file.file.path);
bool installed = false;
if (code != null && code != 0 && code != 3) {
throw InstallError(code);
} else if (code == 0) {
installed = true;
apps[file.appId]!.app.installedVersion =
apps[file.appId]!.app.latestVersion;
file.file.delete();
}
await saveApps([apps[file.appId]!.app]);
return installed;
}
void uninstallApp(String appId) async {
@@ -503,10 +526,17 @@ class AppsProvider with ChangeNotifier {
// ignore: use_build_context_synchronously
await waitForUserToReturnToForeground(context);
}
if (downloadedFile != null) {
await installApk(downloadedFile, silent: willBeSilent);
} else {
await installXApkDir(downloadedDir!, silent: willBeSilent);
apps[id]?.downloadProgress = -1;
notifyListeners();
try {
if (downloadedFile != null) {
await installApk(downloadedFile, silent: willBeSilent);
} else {
await installXApkDir(downloadedDir!, silent: willBeSilent);
}
} finally {
apps[id]?.downloadProgress = null;
notifyListeners();
}
installedIds.add(id);
} catch (e) {
@@ -759,11 +789,18 @@ class AppsProvider with ChangeNotifier {
}
Future<void> removeApps(List<String> appIds) async {
var apkFiles = (await getExternalCacheDirectories())?.first.listSync();
for (var appId in appIds) {
File file = File('${(await getAppsDir()).path}/$appId.json');
if (file.existsSync()) {
file.deleteSync();
}
apkFiles
?.where(
(element) => element.path.split('/').last.startsWith('$appId-'))
.forEach((element) {
element.delete();
});
if (apps.containsKey(appId)) {
apps.remove(appId);
}

View File

@@ -167,7 +167,8 @@ class NotificationsProvider {
progress: progPercent ?? 0,
maxProgress: 100,
showProgress: progPercent != null,
onlyAlertOnce: onlyAlertOnce)));
onlyAlertOnce: onlyAlertOnce,
indeterminate: progPercent != null && progPercent < 0)));
}
Future<void> notify(ObtainiumNotification notif,

View File

@@ -426,10 +426,10 @@ packages:
dependency: "direct main"
description:
name: path_provider
sha256: c7edf82217d4b2952b2129a61d3ad60f1075b9299e629e149a8d2e39c2e6aad4
sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2"
url: "https://pub.dev"
source: hosted
version: "2.0.14"
version: "2.0.15"
path_provider_android:
dependency: transitive
description:
@@ -442,10 +442,10 @@ packages:
dependency: transitive
description:
name: path_provider_foundation
sha256: ad4c4d011830462633f03eb34445a45345673dfd4faf1ab0b4735fbd93b19183
sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
version: "2.2.3"
path_provider_linux:
dependency: transitive
description:
@@ -578,10 +578,10 @@ packages:
dependency: "direct main"
description:
name: shared_preferences
sha256: "858aaa72d8f61637d64e776aca82e1c67e6d9ee07979123c5d17115031c1b13b"
sha256: "16d3fb6b3692ad244a695c0183fca18cf81fd4b821664394a781de42386bf022"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.1"
shared_preferences_android:
dependency: transitive
description:
@@ -594,10 +594,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "0c1c16c56c9708aa9c361541a6f0e5cc6fc12a3232d866a687a7b7db30032b07"
sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb
url: "https://pub.dev"
source: hosted
version: "2.2.1"
version: "2.2.2"
shared_preferences_linux:
dependency: transitive
description:
@@ -647,10 +647,10 @@ packages:
dependency: "direct main"
description:
name: sqflite
sha256: acf091c6e55c50d00b30b8532b2dd23e393cf775861665ebd0f15cdd6ebfb079
sha256: "3a82c9a216b46b88617e3714dd74227eaca20c501c4abcc213e56db26b9caa00"
url: "https://pub.dev"
source: hosted
version: "2.2.8+1"
version: "2.2.8+2"
sqflite_common:
dependency: transitive
description:

View File

@@ -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.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:
sdk: '>=2.18.2 <3.0.0'