mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-14 13:46:43 +02:00
Compare commits
8 Commits
v0.13.0-be
...
v0.13.1-be
Author | SHA1 | Date | |
---|---|---|---|
219b04aedb | |||
a0709856ef | |||
577642850f | |||
e1db024034 | |||
cc268aeeda | |||
d5f7eced8b | |||
cc3c4cc79f | |||
89b61884f1 |
@ -121,7 +121,7 @@
|
|||||||
"followSystem": "System folgen",
|
"followSystem": "System folgen",
|
||||||
"obtainium": "Obtainium",
|
"obtainium": "Obtainium",
|
||||||
"materialYou": "Material You",
|
"materialYou": "Material You",
|
||||||
"useBlackTheme": "Use pure black dark theme",
|
"useBlackTheme": "Use Pure Black Dark Theme",
|
||||||
"appSortBy": "App sortieren nach",
|
"appSortBy": "App sortieren nach",
|
||||||
"authorName": "Autor/Name",
|
"authorName": "Autor/Name",
|
||||||
"nameAuthor": "Name/Autor",
|
"nameAuthor": "Name/Autor",
|
||||||
@ -231,6 +231,7 @@
|
|||||||
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||||
|
"checkOnStart": "Check Once on Start",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "App entfernen?",
|
"one": "App entfernen?",
|
||||||
"other": "Apps entfernen?"
|
"other": "Apps entfernen?"
|
||||||
|
@ -121,7 +121,7 @@
|
|||||||
"followSystem": "Follow System",
|
"followSystem": "Follow System",
|
||||||
"obtainium": "Obtainium",
|
"obtainium": "Obtainium",
|
||||||
"materialYou": "Material You",
|
"materialYou": "Material You",
|
||||||
"useBlackTheme": "Use pure black dark theme",
|
"useBlackTheme": "Use Pure Black Dark Theme",
|
||||||
"appSortBy": "App Sort By",
|
"appSortBy": "App Sort By",
|
||||||
"authorName": "Author/Name",
|
"authorName": "Author/Name",
|
||||||
"nameAuthor": "Name/Author",
|
"nameAuthor": "Name/Author",
|
||||||
@ -231,6 +231,7 @@
|
|||||||
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||||
|
"checkOnStart": "Check Once on Start",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Remove App?",
|
"one": "Remove App?",
|
||||||
"other": "Remove Apps?"
|
"other": "Remove Apps?"
|
||||||
|
@ -231,6 +231,7 @@
|
|||||||
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||||
|
"checkOnStart": "Check Once on Start",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "¿Eliminar Aplicación?",
|
"one": "¿Eliminar Aplicación?",
|
||||||
"other": "¿Eliminar Aplicaciones?"
|
"other": "¿Eliminar Aplicaciones?"
|
||||||
|
@ -231,6 +231,7 @@
|
|||||||
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||||
|
"checkOnStart": "Check Once on Start",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "برنامه حذف شود؟",
|
"one": "برنامه حذف شود؟",
|
||||||
"other": "برنامه ها حذف شوند؟"
|
"other": "برنامه ها حذف شوند؟"
|
||||||
|
@ -121,7 +121,7 @@
|
|||||||
"followSystem": "Suivre le système",
|
"followSystem": "Suivre le système",
|
||||||
"obtainium": "Obtainium",
|
"obtainium": "Obtainium",
|
||||||
"materialYou": "Material You",
|
"materialYou": "Material You",
|
||||||
"useBlackTheme": "Use pure black dark theme",
|
"useBlackTheme": "Use Pure Black Dark Theme",
|
||||||
"appSortBy": "Applications triées par",
|
"appSortBy": "Applications triées par",
|
||||||
"authorName": "Auteur/Nom",
|
"authorName": "Auteur/Nom",
|
||||||
"nameAuthor": "Nom/Auteur",
|
"nameAuthor": "Nom/Auteur",
|
||||||
@ -231,6 +231,7 @@
|
|||||||
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||||
|
"checkOnStart": "Check Once on Start",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Supprimer l'application ?",
|
"one": "Supprimer l'application ?",
|
||||||
"other": "Supprimer les applications ?"
|
"other": "Supprimer les applications ?"
|
||||||
|
@ -230,6 +230,7 @@
|
|||||||
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||||
|
"checkOnStart": "Check Once on Start",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Eltávolítja az alkalmazást?",
|
"one": "Eltávolítja az alkalmazást?",
|
||||||
"other": "Eltávolítja az alkalmazást?"
|
"other": "Eltávolítja az alkalmazást?"
|
||||||
|
@ -121,7 +121,7 @@
|
|||||||
"followSystem": "Segui sistema",
|
"followSystem": "Segui sistema",
|
||||||
"obtainium": "Obtainium",
|
"obtainium": "Obtainium",
|
||||||
"materialYou": "Material You",
|
"materialYou": "Material You",
|
||||||
"useBlackTheme": "Use pure black dark theme",
|
"useBlackTheme": "Use Pure Black Dark Theme",
|
||||||
"appSortBy": "App ordinate per",
|
"appSortBy": "App ordinate per",
|
||||||
"authorName": "Autore/Nome",
|
"authorName": "Autore/Nome",
|
||||||
"nameAuthor": "Nome/Autore",
|
"nameAuthor": "Nome/Autore",
|
||||||
@ -231,6 +231,7 @@
|
|||||||
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||||
|
"checkOnStart": "Check Once on Start",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Rimuovere l'App?",
|
"one": "Rimuovere l'App?",
|
||||||
"other": "Rimuovere le App?"
|
"other": "Rimuovere le App?"
|
||||||
|
@ -227,10 +227,11 @@
|
|||||||
"dontShowAgain": "二度と表示しない",
|
"dontShowAgain": "二度と表示しない",
|
||||||
"dontShowTrackOnlyWarnings": "「追跡のみ」の警告を表示しない",
|
"dontShowTrackOnlyWarnings": "「追跡のみ」の警告を表示しない",
|
||||||
"dontShowAPKOriginWarnings": "APK Originの警告を表示しない",
|
"dontShowAPKOriginWarnings": "APK Originの警告を表示しない",
|
||||||
"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",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "アプリを削除しますか?",
|
"one": "アプリを削除しますか?",
|
||||||
"other": "アプリを削除しますか?"
|
"other": "アプリを削除しますか?"
|
||||||
|
@ -231,6 +231,7 @@
|
|||||||
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||||
|
"checkOnStart": "Check Once on Start",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "是否删除应用?",
|
"one": "是否删除应用?",
|
||||||
"other": "是否删除应用?"
|
"other": "是否删除应用?"
|
||||||
|
@ -57,9 +57,9 @@ class APKPure extends AppSource {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
String type = html.querySelector('a.info-tag')?.text.trim() ?? 'APK';
|
||||||
List<MapEntry<String, String>> apkUrls = [
|
List<MapEntry<String, String>> apkUrls = [
|
||||||
MapEntry('$appId.apk', 'https://d.$host/b/APK/$appId?version=latest')
|
MapEntry('$appId.apk', 'https://d.$host/b/$type/$appId?version=latest')
|
||||||
];
|
];
|
||||||
String author = html
|
String author = html
|
||||||
.querySelector('span.info-sdk')
|
.querySelector('span.info-sdk')
|
||||||
|
@ -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.0';
|
const String currentVersion = '0.13.1';
|
||||||
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
|
||||||
|
|
||||||
|
@ -159,9 +159,16 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
app.preferredApkIndex =
|
app.preferredApkIndex =
|
||||||
app.apkUrls.map((e) => e.value).toList().indexOf(apkUrl.value);
|
app.apkUrls.map((e) => e.value).toList().indexOf(apkUrl.value);
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
var downloadedApk = await appsProvider.downloadApp(
|
var downloadedArtifact = await appsProvider.downloadApp(
|
||||||
app, globalNavigatorKey.currentContext);
|
app, globalNavigatorKey.currentContext);
|
||||||
app.id = downloadedApk.appId;
|
DownloadedApk? downloadedFile;
|
||||||
|
DownloadedXApkDir? downloadedDir;
|
||||||
|
if (downloadedArtifact is DownloadedApk) {
|
||||||
|
downloadedFile = downloadedArtifact;
|
||||||
|
} else {
|
||||||
|
downloadedDir = downloadedArtifact as DownloadedXApkDir;
|
||||||
|
}
|
||||||
|
app.id = downloadedFile?.appId ?? downloadedDir!.appId;
|
||||||
}
|
}
|
||||||
if (appsProvider.apps.containsKey(app.id)) {
|
if (appsProvider.apps.containsKey(app.id)) {
|
||||||
throw ObtainiumError(tr('appAlreadyAdded'));
|
throw ObtainiumError(tr('appAlreadyAdded'));
|
||||||
|
@ -52,6 +52,9 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
|
||||||
|
GlobalKey<RefreshIndicatorState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var appsProvider = context.watch<AppsProvider>();
|
var appsProvider = context.watch<AppsProvider>();
|
||||||
@ -61,6 +64,27 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
var currentFilterIsUpdatesOnly =
|
var currentFilterIsUpdatesOnly =
|
||||||
filter.isIdenticalTo(updatesOnlyFilter, settingsProvider);
|
filter.isIdenticalTo(updatesOnlyFilter, settingsProvider);
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
HapticFeedback.lightImpact();
|
||||||
|
setState(() {
|
||||||
|
refreshingSince = DateTime.now();
|
||||||
|
});
|
||||||
|
return appsProvider.checkUpdates().catchError((e) {
|
||||||
|
showError(e, context);
|
||||||
|
}).whenComplete(() {
|
||||||
|
setState(() {
|
||||||
|
refreshingSince = null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!appsProvider.loadingApps &&
|
||||||
|
appsProvider.apps.isNotEmpty &&
|
||||||
|
settingsProvider.checkJustStarted() &&
|
||||||
|
settingsProvider.checkOnStart) {
|
||||||
|
_refreshIndicatorKey.currentState?.show();
|
||||||
|
}
|
||||||
|
|
||||||
selectedAppIds = selectedAppIds
|
selectedAppIds = selectedAppIds
|
||||||
.where((element) => listedApps.map((e) => e.app.id).contains(element))
|
.where((element) => listedApps.map((e) => e.app.id).contains(element))
|
||||||
.toSet();
|
.toSet();
|
||||||
@ -315,7 +339,7 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
?.isBefore(refreshingSince!) ??
|
?.isBefore(refreshingSince!) ??
|
||||||
true))
|
true))
|
||||||
.length /
|
.length /
|
||||||
appsProvider.apps.length,
|
(appsProvider.apps.isNotEmpty ? appsProvider.apps.length : 1),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
@ -515,10 +539,12 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
? FontWeight.bold
|
? FontWeight.bold
|
||||||
: FontWeight.normal)),
|
: FontWeight.normal)),
|
||||||
trailing: listedApps[index].downloadProgress != null
|
trailing: listedApps[index].downloadProgress != null
|
||||||
? Text(tr('percentProgress', args: [
|
? SizedBox(
|
||||||
|
width: 110,
|
||||||
|
child: Text(tr('percentProgress', args: [
|
||||||
listedApps[index].downloadProgress?.toInt().toString() ??
|
listedApps[index].downloadProgress?.toInt().toString() ??
|
||||||
'100'
|
'100'
|
||||||
]))
|
])))
|
||||||
: trailingRow,
|
: trailingRow,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (selectedAppIds.isNotEmpty) {
|
if (selectedAppIds.isNotEmpty) {
|
||||||
@ -1017,19 +1043,8 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
onRefresh: () {
|
key: _refreshIndicatorKey,
|
||||||
HapticFeedback.lightImpact();
|
onRefresh: refresh,
|
||||||
setState(() {
|
|
||||||
refreshingSince = DateTime.now();
|
|
||||||
});
|
|
||||||
return appsProvider.checkUpdates().catchError((e) {
|
|
||||||
showError(e, context);
|
|
||||||
}).whenComplete(() {
|
|
||||||
setState(() {
|
|
||||||
refreshingSince = null;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: CustomScrollView(slivers: <Widget>[
|
child: CustomScrollView(slivers: <Widget>[
|
||||||
CustomAppBar(title: tr('appsString')),
|
CustomAppBar(title: tr('appsString')),
|
||||||
...getLoadingWidgets(),
|
...getLoadingWidgets(),
|
||||||
|
@ -228,6 +228,18 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
color: Theme.of(context).colorScheme.primary),
|
color: Theme.of(context).colorScheme.primary),
|
||||||
),
|
),
|
||||||
intervalDropdown,
|
intervalDropdown,
|
||||||
|
height16,
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Flexible(child: Text(tr('checkOnStart'))),
|
||||||
|
Switch(
|
||||||
|
value: settingsProvider.checkOnStart,
|
||||||
|
onChanged: (value) {
|
||||||
|
settingsProvider.checkOnStart = value;
|
||||||
|
})
|
||||||
|
],
|
||||||
|
),
|
||||||
height32,
|
height32,
|
||||||
Text(
|
Text(
|
||||||
tr('sourceSpecific'),
|
tr('sourceSpecific'),
|
||||||
|
@ -27,6 +27,7 @@ import 'package:flutter_fgbg/flutter_fgbg.dart';
|
|||||||
import 'package:obtainium/providers/source_provider.dart';
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:android_intent_plus/android_intent.dart';
|
import 'package:android_intent_plus/android_intent.dart';
|
||||||
|
import 'package:archive/archive.dart';
|
||||||
|
|
||||||
class AppInMemory {
|
class AppInMemory {
|
||||||
late App app;
|
late App app;
|
||||||
@ -46,6 +47,13 @@ class DownloadedApk {
|
|||||||
DownloadedApk(this.appId, this.file);
|
DownloadedApk(this.appId, this.file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DownloadedXApkDir {
|
||||||
|
String appId;
|
||||||
|
File file;
|
||||||
|
Directory extracted;
|
||||||
|
DownloadedXApkDir(this.appId, this.file, this.extracted);
|
||||||
|
}
|
||||||
|
|
||||||
List<String> generateStandardVersionRegExStrings() {
|
List<String> generateStandardVersionRegExStrings() {
|
||||||
// TODO: Look into RegEx for non-Latin characters / non-Arabic numerals
|
// TODO: Look into RegEx for non-Latin characters / non-Arabic numerals
|
||||||
var basics = [
|
var basics = [
|
||||||
@ -164,7 +172,27 @@ class AppsProvider with ChangeNotifier {
|
|||||||
return downloadedFile;
|
return downloadedFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<DownloadedApk> downloadApp(App app, BuildContext? context) async {
|
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) {
|
||||||
|
var isTempId = SourceProvider().isTempId(app);
|
||||||
|
if (apps[app.id] != null && !isTempId) {
|
||||||
|
throw IDChangedError();
|
||||||
|
}
|
||||||
|
var originalAppId = app.id;
|
||||||
|
app.id = newInfo.packageName;
|
||||||
|
downloadedFile = downloadedFile.renameSync(
|
||||||
|
'${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.apk');
|
||||||
|
if (apps[originalAppId] != null) {
|
||||||
|
await removeApps([originalAppId]);
|
||||||
|
await saveApps([app], onlyIfExists: !isTempId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Object> downloadApp(App app, BuildContext? context) async {
|
||||||
NotificationsProvider? notificationsProvider =
|
NotificationsProvider? notificationsProvider =
|
||||||
context?.read<NotificationsProvider>();
|
context?.read<NotificationsProvider>();
|
||||||
var notifId = DownloadNotification(app.finalName, 0).id;
|
var notifId = DownloadNotification(app.finalName, 0).id;
|
||||||
@ -194,33 +222,42 @@ class AppsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
prevProg = prog;
|
prevProg = prog;
|
||||||
});
|
});
|
||||||
// If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed
|
PackageArchiveInfo? newInfo;
|
||||||
// The former case should be handled (give the App its real ID), the latter is a security issue
|
try {
|
||||||
var newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path);
|
newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path);
|
||||||
if (app.id != newInfo.packageName) {
|
} catch (e) {
|
||||||
var isTempId = SourceProvider().isTempId(app);
|
// Assume it's an XAPK
|
||||||
if (apps[app.id] != null && !isTempId) {
|
fileName = '${app.id}-${downloadUrl.hashCode}.xapk';
|
||||||
throw IDChangedError();
|
String newPath = '${downloadedFile.parent.path}/$fileName';
|
||||||
|
downloadedFile.renameSync(newPath);
|
||||||
|
downloadedFile = File(newPath);
|
||||||
}
|
}
|
||||||
var originalAppId = app.id;
|
Directory? xapkDir;
|
||||||
app.id = newInfo.packageName;
|
if (newInfo == null) {
|
||||||
downloadedFile = downloadedFile.renameSync(
|
String xapkDirPath = '${downloadedFile.path}-dir';
|
||||||
'${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.apk');
|
unzipFile(downloadedFile.path, '${downloadedFile.path}-dir');
|
||||||
if (apps[originalAppId] != null) {
|
xapkDir = Directory(xapkDirPath);
|
||||||
await removeApps([originalAppId]);
|
var apks = xapkDir
|
||||||
await saveApps([app], onlyIfExists: !isTempId);
|
.listSync()
|
||||||
|
.where((e) => e.path.toLowerCase().endsWith('.apk'))
|
||||||
|
.toList();
|
||||||
|
newInfo = await PackageArchiveInfo.fromPath(apks.first.path);
|
||||||
}
|
}
|
||||||
}
|
await handleAPKIDChange(app, newInfo, downloadedFile, downloadUrl);
|
||||||
// Delete older versions of the APK 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.endsWith('.apk') &&
|
fn.toLowerCase().endsWith(xapkDir == null ? '.apk' : '.xapk') &&
|
||||||
fn != downloadedFile.path.split('/').last) {
|
fn != downloadedFile.path.split('/').last) {
|
||||||
file.delete();
|
file.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (xapkDir != null) {
|
||||||
|
return DownloadedXApkDir(app.id, downloadedFile, xapkDir);
|
||||||
|
} else {
|
||||||
return DownloadedApk(app.id, downloadedFile);
|
return DownloadedApk(app.id, downloadedFile);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
notificationsProvider?.cancel(notifId);
|
notificationsProvider?.cancel(notifId);
|
||||||
if (apps[app.id] != null) {
|
if (apps[app.id] != null) {
|
||||||
@ -267,10 +304,37 @@ class AppsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unfortunately this 'await' does not actually wait for the APK to finish installing
|
void unzipFile(String filePath, String destinationPath) {
|
||||||
// So we only know that the install prompt was shown, but the user could still cancel w/o us knowing
|
final bytes = File(filePath).readAsBytesSync();
|
||||||
// If appropriate criteria are met, the update (never a fresh install) happens silently in the background
|
final archive = ZipDecoder().decodeBytes(bytes);
|
||||||
// But even then, we don't know if it actually succeeded
|
|
||||||
|
for (final file in archive) {
|
||||||
|
final filename = '$destinationPath/${file.name}';
|
||||||
|
if (file.isFile) {
|
||||||
|
final data = file.content as List<int>;
|
||||||
|
File(filename)
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsBytesSync(data);
|
||||||
|
} else {
|
||||||
|
Directory(filename).create(recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> installXApkDir(DownloadedXApkDir dir,
|
||||||
|
{bool silent = false}) async {
|
||||||
|
try {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
dir.file.delete();
|
||||||
|
} finally {
|
||||||
|
dir.extracted.delete(recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> installApk(DownloadedApk file, {bool silent = false}) async {
|
Future<void> 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);
|
||||||
@ -420,9 +484,16 @@ class AppsProvider with ChangeNotifier {
|
|||||||
for (var id in appsToInstall) {
|
for (var id in appsToInstall) {
|
||||||
try {
|
try {
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
var downloadedFile = await downloadApp(apps[id]!.app, context);
|
var downloadedArtifact = await downloadApp(apps[id]!.app, context);
|
||||||
bool willBeSilent =
|
DownloadedApk? downloadedFile;
|
||||||
await canInstallSilently(apps[downloadedFile.appId]!.app);
|
DownloadedXApkDir? downloadedDir;
|
||||||
|
if (downloadedArtifact is DownloadedApk) {
|
||||||
|
downloadedFile = downloadedArtifact;
|
||||||
|
} else {
|
||||||
|
downloadedDir = downloadedArtifact as DownloadedXApkDir;
|
||||||
|
}
|
||||||
|
bool willBeSilent = await canInstallSilently(
|
||||||
|
apps[downloadedFile?.appId ?? downloadedDir!.appId]!.app);
|
||||||
willBeSilent = false; // TODO: Remove this when silent updates work
|
willBeSilent = false; // TODO: Remove this when silent updates work
|
||||||
if (!(await settingsProvider?.getInstallPermission(enforce: false) ??
|
if (!(await settingsProvider?.getInstallPermission(enforce: false) ??
|
||||||
true)) {
|
true)) {
|
||||||
@ -432,7 +503,11 @@ class AppsProvider with ChangeNotifier {
|
|||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
await waitForUserToReturnToForeground(context);
|
await waitForUserToReturnToForeground(context);
|
||||||
}
|
}
|
||||||
|
if (downloadedFile != null) {
|
||||||
await installApk(downloadedFile, silent: willBeSilent);
|
await installApk(downloadedFile, silent: willBeSilent);
|
||||||
|
} else {
|
||||||
|
await installXApkDir(downloadedDir!, silent: willBeSilent);
|
||||||
|
}
|
||||||
installedIds.add(id);
|
installedIds.add(id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errors.add(id, e.toString());
|
errors.add(id, e.toString());
|
||||||
@ -734,7 +809,7 @@ class AppsProvider with ChangeNotifier {
|
|||||||
apps[i].installedVersion = null;
|
apps[i].installedVersion = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await saveApps(apps, attemptToCorrectInstallStatus: !remove);
|
await saveApps(apps, attemptToCorrectInstallStatus: false);
|
||||||
}
|
}
|
||||||
if (remove) {
|
if (remove) {
|
||||||
await removeApps(apps.map((e) => e.id).toList());
|
await removeApps(apps.map((e) => e.id).toList());
|
||||||
|
@ -35,6 +35,7 @@ List<int> updateIntervals = [15, 30, 60, 120, 180, 360, 720, 1440, 4320, 0]
|
|||||||
|
|
||||||
class SettingsProvider with ChangeNotifier {
|
class SettingsProvider with ChangeNotifier {
|
||||||
SharedPreferences? prefs;
|
SharedPreferences? prefs;
|
||||||
|
bool justStarted = true;
|
||||||
|
|
||||||
String sourceUrl = 'https://github.com/ImranR98/Obtainium';
|
String sourceUrl = 'https://github.com/ImranR98/Obtainium';
|
||||||
|
|
||||||
@ -92,6 +93,15 @@ class SettingsProvider with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get checkOnStart {
|
||||||
|
return prefs?.getBool('checkOnStart') ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set checkOnStart(bool checkOnStart) {
|
||||||
|
prefs?.setBool('checkOnStart', checkOnStart);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
SortColumnSettings get sortColumn {
|
SortColumnSettings get sortColumn {
|
||||||
return SortColumnSettings.values[
|
return SortColumnSettings.values[
|
||||||
prefs?.getInt('sortColumn') ?? SortColumnSettings.nameAuthor.index];
|
prefs?.getInt('sortColumn') ?? SortColumnSettings.nameAuthor.index];
|
||||||
@ -120,6 +130,14 @@ class SettingsProvider with ChangeNotifier {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool checkJustStarted() {
|
||||||
|
if (justStarted) {
|
||||||
|
justStarted = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> getInstallPermission({bool enforce = false}) async {
|
Future<bool> getInstallPermission({bool enforce = false}) async {
|
||||||
while (!(await Permission.requestInstallPackages.isGranted)) {
|
while (!(await Permission.requestInstallPackages.isGranted)) {
|
||||||
// Explicit request as InstallPlugin request sometimes bugged
|
// Explicit request as InstallPlugin request sometimes bugged
|
||||||
|
24
pubspec.lock
24
pubspec.lock
@ -34,6 +34,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.7"
|
version: "2.0.7"
|
||||||
|
archive:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: archive
|
||||||
|
sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.3.7"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -82,6 +90,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.0"
|
version: "1.17.0"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.1"
|
||||||
cross_file:
|
cross_file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -518,6 +534,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
|
pointycastle:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pointycastle
|
||||||
|
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.7.3"
|
||||||
process:
|
process:
|
||||||
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.0+164 # When changing this, update the tag in main() accordingly
|
version: 0.13.1+165 # 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'
|
||||||
@ -63,6 +63,7 @@ dependencies:
|
|||||||
easy_localization: ^3.0.1
|
easy_localization: ^3.0.1
|
||||||
android_intent_plus: ^3.1.5
|
android_intent_plus: ^3.1.5
|
||||||
flutter_markdown: ^0.6.14
|
flutter_markdown: ^0.6.14
|
||||||
|
archive: ^3.3.7
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
Reference in New Issue
Block a user