mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-08-19 13:09:30 +02:00
Compare commits
6 Commits
v0.11.25-b
...
v0.11.28-b
Author | SHA1 | Date | |
---|---|---|---|
|
b62475de87 | ||
|
334ac8d3d6 | ||
|
9193788356 | ||
|
8f75ddd43f | ||
|
a2edc86bfa | ||
|
0804e680b2 |
@@ -222,6 +222,7 @@
|
||||
"versionDetection": "Versionserkennung",
|
||||
"standardVersionDetection": "Standardversionserkennung",
|
||||
"groupByCategory": "Group by Category",
|
||||
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
|
||||
"removeAppQuestion": {
|
||||
"one": "App entfernen?",
|
||||
"other": "App entfernen?"
|
||||
|
@@ -222,6 +222,7 @@
|
||||
"versionDetection": "Version Detection",
|
||||
"standardVersionDetection": "Standard version detection",
|
||||
"groupByCategory": "Group by Category",
|
||||
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
|
||||
"removeAppQuestion": {
|
||||
"one": "Remove App?",
|
||||
"other": "Remove Apps?"
|
||||
|
@@ -222,6 +222,7 @@
|
||||
"versionDetection": "تشخیص نسخه",
|
||||
"standardVersionDetection": "تشخیص نسخه استاندارد",
|
||||
"groupByCategory": "Group by Category",
|
||||
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
|
||||
"removeAppQuestion": {
|
||||
"one": "برنامه حذف شود؟",
|
||||
"other": "برنامه ها حذف شوند؟"
|
||||
|
@@ -222,6 +222,7 @@
|
||||
"versionDetection": "Détection des versions",
|
||||
"standardVersionDetection": "Détection de version standard",
|
||||
"groupByCategory": "Group by Category",
|
||||
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
|
||||
"removeAppQuestion": {
|
||||
"one": "Supprimer l'application ?",
|
||||
"other": "Supprimer les applications ?"
|
||||
|
@@ -221,6 +221,7 @@
|
||||
"versionDetection": "Verzió érzékelés",
|
||||
"standardVersionDetection": "Alapért. verzió érzékelés",
|
||||
"groupByCategory": "Csoportosítás Kategória alapján",
|
||||
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
|
||||
"removeAppQuestion": {
|
||||
"one": "Eltávolítja az alkalmazást?",
|
||||
"other": "Eltávolítja az alkalmazást?"
|
||||
|
@@ -222,6 +222,7 @@
|
||||
"versionDetection": "Rilevamento di versione",
|
||||
"standardVersionDetection": "Rilevamento di versione standard",
|
||||
"groupByCategory": "Group by Category",
|
||||
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
|
||||
"removeAppQuestion": {
|
||||
"one": "Rimuovere l'App?",
|
||||
"other": "Rimuovere le App?"
|
||||
|
@@ -222,6 +222,7 @@
|
||||
"versionDetection": "バージョン検出",
|
||||
"standardVersionDetection": "標準のバージョン検出",
|
||||
"groupByCategory": "Group by Category",
|
||||
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
|
||||
"removeAppQuestion": {
|
||||
"one": "アプリを削除しますか?",
|
||||
"other": "アプリを削除しますか?"
|
||||
|
@@ -222,6 +222,7 @@
|
||||
"versionDetection": "Version Detection",
|
||||
"standardVersionDetection": "Standard version detection",
|
||||
"groupByCategory": "Group by Category",
|
||||
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
|
||||
"removeAppQuestion": {
|
||||
"one": "删除应用?",
|
||||
"other": "删除应用?"
|
||||
|
@@ -267,7 +267,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
||||
formInputs[r][e] = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(widget.items[r][e].label),
|
||||
Flexible(child: Text(widget.items[r][e].label)),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Switch(
|
||||
value: values[widget.items[r][e].key],
|
||||
onChanged: (value) {
|
||||
|
@@ -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.11.25';
|
||||
const String currentVersion = '0.11.28';
|
||||
const String currentReleaseTag =
|
||||
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
||||
|
||||
|
@@ -38,7 +38,7 @@ class _AppPageState extends State<AppPage> {
|
||||
bool areDownloadsRunning = appsProvider.areDownloadsRunning();
|
||||
|
||||
var sourceProvider = SourceProvider();
|
||||
AppInMemory? app = appsProvider.apps[widget.appId];
|
||||
AppInMemory? app = appsProvider.apps[widget.appId]?.deepCopy();
|
||||
var source = app != null ? sourceProvider.getSource(app.app.url) : null;
|
||||
if (!areDownloadsRunning && prevApp == null && app != null) {
|
||||
prevApp = app;
|
||||
|
@@ -29,13 +29,13 @@ class AppsPageState extends State<AppsPage> {
|
||||
final AppsFilter neutralFilter = AppsFilter();
|
||||
var updatesOnlyFilter =
|
||||
AppsFilter(includeUptodate: false, includeNonInstalled: false);
|
||||
Set<App> selectedApps = {};
|
||||
Set<String> selectedAppIds = {};
|
||||
DateTime? refreshingSince;
|
||||
|
||||
clearSelected() {
|
||||
if (selectedApps.isNotEmpty) {
|
||||
if (selectedAppIds.isNotEmpty) {
|
||||
setState(() {
|
||||
selectedApps.clear();
|
||||
selectedAppIds.clear();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -43,10 +43,10 @@ class AppsPageState extends State<AppsPage> {
|
||||
}
|
||||
|
||||
selectThese(List<App> apps) {
|
||||
if (selectedApps.isEmpty) {
|
||||
if (selectedAppIds.isEmpty) {
|
||||
setState(() {
|
||||
for (var a in apps) {
|
||||
selectedApps.add(a);
|
||||
selectedAppIds.add(a.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -57,20 +57,20 @@ class AppsPageState extends State<AppsPage> {
|
||||
var appsProvider = context.watch<AppsProvider>();
|
||||
var settingsProvider = context.watch<SettingsProvider>();
|
||||
var sourceProvider = SourceProvider();
|
||||
var listedApps = appsProvider.apps.values.toList();
|
||||
var listedApps = appsProvider.getAppValues().toList();
|
||||
var currentFilterIsUpdatesOnly =
|
||||
filter.isIdenticalTo(updatesOnlyFilter, settingsProvider);
|
||||
|
||||
selectedApps = selectedApps
|
||||
.where((element) => listedApps.map((e) => e.app).contains(element))
|
||||
selectedAppIds = selectedAppIds
|
||||
.where((element) => listedApps.map((e) => e.app.id).contains(element))
|
||||
.toSet();
|
||||
|
||||
toggleAppSelected(App app) {
|
||||
setState(() {
|
||||
if (selectedApps.contains(app)) {
|
||||
selectedApps.remove(app);
|
||||
if (selectedAppIds.map((e) => e).contains(app.id)) {
|
||||
selectedAppIds.removeWhere((a) => a == app.id);
|
||||
} else {
|
||||
selectedApps.add(app);
|
||||
selectedAppIds.add(app.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -143,15 +143,15 @@ class AppsPageState extends State<AppsPage> {
|
||||
var existingUpdates = appsProvider.findExistingUpdates(installedOnly: true);
|
||||
|
||||
var existingUpdateIdsAllOrSelected = existingUpdates
|
||||
.where((element) => selectedApps.isEmpty
|
||||
.where((element) => selectedAppIds.isEmpty
|
||||
? listedApps.where((a) => a.app.id == element).isNotEmpty
|
||||
: selectedApps.map((e) => e.id).contains(element))
|
||||
: selectedAppIds.map((e) => e).contains(element))
|
||||
.toList();
|
||||
var newInstallIdsAllOrSelected = appsProvider
|
||||
.findExistingUpdates(nonInstalledOnly: true)
|
||||
.where((element) => selectedApps.isEmpty
|
||||
.where((element) => selectedAppIds.isEmpty
|
||||
? listedApps.where((a) => a.app.id == element).isNotEmpty
|
||||
: selectedApps.map((e) => e.id).contains(element))
|
||||
: selectedAppIds.map((e) => e).contains(element))
|
||||
.toList();
|
||||
|
||||
List<String> trackOnlyUpdateIdsAllOrSelected = [];
|
||||
@@ -212,6 +212,11 @@ class AppsPageState extends State<AppsPage> {
|
||||
: -1;
|
||||
});
|
||||
|
||||
Set<App> selectedApps = listedApps
|
||||
.map((e) => e.app)
|
||||
.where((a) => selectedAppIds.contains(a.id))
|
||||
.toSet();
|
||||
|
||||
showChangeLogDialog(
|
||||
String? changesUrl, AppSource appSource, String changeLog, int index) {
|
||||
showDialog(
|
||||
@@ -288,7 +293,8 @@ class AppsPageState extends State<AppsPage> {
|
||||
if (refreshingSince != null)
|
||||
SliverToBoxAdapter(
|
||||
child: LinearProgressIndicator(
|
||||
value: appsProvider.apps.values
|
||||
value: appsProvider
|
||||
.getAppValues()
|
||||
.where((element) => !(element.app.lastUpdateCheck
|
||||
?.isBefore(refreshingSince!) ??
|
||||
true))
|
||||
@@ -467,7 +473,8 @@ class AppsPageState extends State<AppsPage> {
|
||||
.colorScheme
|
||||
.primary
|
||||
.withOpacity(listedApps[index].app.pinned ? 0.2 : 0.1),
|
||||
selected: selectedApps.contains(listedApps[index].app),
|
||||
selected:
|
||||
selectedAppIds.map((e) => e).contains(listedApps[index].app.id),
|
||||
onLongPress: () {
|
||||
toggleAppSelected(listedApps[index].app);
|
||||
},
|
||||
@@ -497,7 +504,7 @@ class AppsPageState extends State<AppsPage> {
|
||||
]))
|
||||
: trailingRow,
|
||||
onTap: () {
|
||||
if (selectedApps.isNotEmpty) {
|
||||
if (selectedAppIds.isNotEmpty) {
|
||||
toggleAppSelected(listedApps[index].app);
|
||||
} else {
|
||||
Navigator.push(
|
||||
@@ -534,7 +541,7 @@ class AppsPageState extends State<AppsPage> {
|
||||
}
|
||||
|
||||
getSelectAllButton() {
|
||||
return selectedApps.isEmpty
|
||||
return selectedAppIds.isEmpty
|
||||
? TextButton.icon(
|
||||
style: const ButtonStyle(visualDensity: VisualDensity.compact),
|
||||
onPressed: () {
|
||||
@@ -548,17 +555,17 @@ class AppsPageState extends State<AppsPage> {
|
||||
: TextButton.icon(
|
||||
style: const ButtonStyle(visualDensity: VisualDensity.compact),
|
||||
onPressed: () {
|
||||
selectedApps.isEmpty
|
||||
selectedAppIds.isEmpty
|
||||
? selectThese(listedApps.map((e) => e.app).toList())
|
||||
: clearSelected();
|
||||
},
|
||||
icon: Icon(
|
||||
selectedApps.isEmpty
|
||||
selectedAppIds.isEmpty
|
||||
? Icons.select_all_outlined
|
||||
: Icons.deselect_outlined,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
label: Text(selectedApps.length.toString()));
|
||||
label: Text(selectedAppIds.length.toString()));
|
||||
}
|
||||
|
||||
getMassObtainFunction() {
|
||||
@@ -706,7 +713,7 @@ class AppsPageState extends State<AppsPage> {
|
||||
builder: (BuildContext ctx) {
|
||||
return AlertDialog(
|
||||
title: Text(tr('markXSelectedAppsAsUpdated',
|
||||
args: [selectedApps.length.toString()])),
|
||||
args: [selectedAppIds.length.toString()])),
|
||||
content: Text(
|
||||
tr('onlyWorksWithNonVersionDetectApps'),
|
||||
style: const TextStyle(
|
||||
@@ -760,7 +767,7 @@ class AppsPageState extends State<AppsPage> {
|
||||
items: const [],
|
||||
initValid: true,
|
||||
message: tr('installStatusOfXWillBeResetExplanation',
|
||||
args: [plural('app', selectedApps.length)]),
|
||||
args: [plural('app', selectedAppIds.length)]),
|
||||
);
|
||||
});
|
||||
if (values != null) {
|
||||
@@ -836,7 +843,7 @@ class AppsPageState extends State<AppsPage> {
|
||||
children: [
|
||||
IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: selectedApps.isEmpty
|
||||
onPressed: selectedAppIds.isEmpty
|
||||
? null
|
||||
: () {
|
||||
appsProvider.removeAppsWithModal(
|
||||
@@ -848,7 +855,7 @@ class AppsPageState extends State<AppsPage> {
|
||||
IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: getMassObtainFunction(),
|
||||
tooltip: selectedApps.isEmpty
|
||||
tooltip: selectedAppIds.isEmpty
|
||||
? tr('installUpdateApps')
|
||||
: tr('installUpdateSelectedApps'),
|
||||
icon: const Icon(
|
||||
@@ -856,13 +863,13 @@ class AppsPageState extends State<AppsPage> {
|
||||
)),
|
||||
IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: selectedApps.isEmpty ? null : launchCategorizeDialog(),
|
||||
onPressed: selectedAppIds.isEmpty ? null : launchCategorizeDialog(),
|
||||
tooltip: tr('categorize'),
|
||||
icon: const Icon(Icons.category_outlined),
|
||||
),
|
||||
IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: selectedApps.isEmpty ? null : showMoreOptionsDialog,
|
||||
onPressed: selectedAppIds.isEmpty ? null : showMoreOptionsDialog,
|
||||
tooltip: tr('more'),
|
||||
icon: const Icon(Icons.more_horiz),
|
||||
),
|
||||
|
@@ -34,6 +34,8 @@ class AppInMemory {
|
||||
AppInfo? installedInfo;
|
||||
|
||||
AppInMemory(this.app, this.downloadProgress, this.installedInfo);
|
||||
AppInMemory deepCopy() =>
|
||||
AppInMemory(app.deepCopy(), downloadProgress, installedInfo);
|
||||
}
|
||||
|
||||
class DownloadedApk {
|
||||
@@ -97,6 +99,8 @@ class AppsProvider with ChangeNotifier {
|
||||
late Stream<FGBGType>? foregroundStream;
|
||||
late StreamSubscription<FGBGType>? foregroundSubscription;
|
||||
|
||||
Iterable<AppInMemory> getAppValues() => apps.values.map((a) => a.deepCopy());
|
||||
|
||||
AppsProvider() {
|
||||
// Subscribe to changes in the app foreground status
|
||||
foregroundStream = FGBGEvents.stream.asBroadcastStream();
|
||||
@@ -165,11 +169,10 @@ class AppsProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
try {
|
||||
var fileName =
|
||||
'${app.id}-${app.latestVersion}-${app.preferredApkIndex}.apk';
|
||||
String downloadUrl = await SourceProvider()
|
||||
.getSource(app.url)
|
||||
.apkUrlPrefetchModifier(app.apkUrls[app.preferredApkIndex].value);
|
||||
var fileName = '${app.id}-${downloadUrl.hashCode}.apk';
|
||||
var notif = DownloadNotification(app.name, 100);
|
||||
notificationsProvider?.cancel(notif.id);
|
||||
int? prevProg;
|
||||
@@ -205,7 +208,7 @@ class AppsProvider with ChangeNotifier {
|
||||
var originalAppId = app.id;
|
||||
app.id = newInfo.packageName;
|
||||
downloadedFile = downloadedFile.renameSync(
|
||||
'${downloadedFile.parent.path}/${app.id}-${app.latestVersion}-${app.preferredApkIndex}.apk');
|
||||
'${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.apk');
|
||||
if (apps[originalAppId] != null) {
|
||||
await removeApps([originalAppId]);
|
||||
await saveApps([app]);
|
||||
@@ -668,7 +671,8 @@ class AppsProvider with ChangeNotifier {
|
||||
bool onlyIfExists = true}) async {
|
||||
attemptToCorrectInstallStatus =
|
||||
attemptToCorrectInstallStatus && (await doesInstalledAppsPluginWork());
|
||||
for (var app in apps) {
|
||||
for (var a in apps) {
|
||||
var app = a.deepCopy();
|
||||
AppInfo? info = await getInstalledInfo(app.id);
|
||||
app.name = info?.name ?? app.name;
|
||||
if (app.additionalSettings['appName']?.toString().isNotEmpty == true) {
|
||||
|
@@ -164,7 +164,8 @@ class SettingsProvider with ChangeNotifier {
|
||||
|
||||
void setCategories(Map<String, int> cats, {AppsProvider? appsProvider}) {
|
||||
if (appsProvider != null) {
|
||||
List<App> changedApps = appsProvider.apps.values
|
||||
List<App> changedApps = appsProvider
|
||||
.getAppValues()
|
||||
.map((a) {
|
||||
var n1 = a.app.categories.length;
|
||||
a.app.categories.removeWhere((c) => !cats.keys.contains(c));
|
||||
|
@@ -3,6 +3,7 @@
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:html/dom.dart';
|
||||
import 'package:http/http.dart';
|
||||
@@ -79,6 +80,22 @@ class App {
|
||||
return 'ID: $id URL: $url INSTALLED: $installedVersion LATEST: $latestVersion APK: $apkUrls PREFERREDAPK: $preferredApkIndex ADDITIONALSETTINGS: ${additionalSettings.toString()} LASTCHECK: ${lastUpdateCheck.toString()} PINNED $pinned';
|
||||
}
|
||||
|
||||
App deepCopy() => App(
|
||||
id,
|
||||
url,
|
||||
author,
|
||||
name,
|
||||
installedVersion,
|
||||
latestVersion,
|
||||
apkUrls,
|
||||
preferredApkIndex,
|
||||
Map.from(additionalSettings),
|
||||
lastUpdateCheck,
|
||||
pinned,
|
||||
categories: categories,
|
||||
changeLog: changeLog,
|
||||
releaseDate: releaseDate);
|
||||
|
||||
factory App.fromJson(Map<String, dynamic> json) {
|
||||
var source = SourceProvider().getSource(json['url']);
|
||||
var formItems = source.combinedAppSpecificSettingFormItems
|
||||
@@ -146,6 +163,11 @@ class App {
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
// Arch based APK filter option should be disabled if it previously did not exist
|
||||
if (json['additionalSettings'] != null &&
|
||||
jsonDecode(json['additionalSettings'])['autoApkFilterByArch'] == null) {
|
||||
additionalSettings['autoApkFilterByArch'] = false;
|
||||
}
|
||||
return App(
|
||||
json['id'] as String,
|
||||
json['url'] as String,
|
||||
@@ -294,6 +316,10 @@ class AppSource {
|
||||
}
|
||||
])
|
||||
],
|
||||
[
|
||||
GeneratedFormSwitch('autoApkFilterByArch',
|
||||
label: tr('autoApkFilterByArch'), defaultValue: true)
|
||||
],
|
||||
[GeneratedFormTextField('appName', label: tr('appName'), required: false)]
|
||||
];
|
||||
|
||||
@@ -442,6 +468,19 @@ class SourceProvider {
|
||||
if (apk.apkUrls.isEmpty && !trackOnly) {
|
||||
throw NoAPKError();
|
||||
}
|
||||
if (apk.apkUrls.length > 1 &&
|
||||
additionalSettings['autoApkFilterByArch'] == true) {
|
||||
var abis = (await DeviceInfoPlugin().androidInfo).supportedAbis;
|
||||
for (var abi in abis) {
|
||||
var urls2 = apk.apkUrls
|
||||
.where((element) => RegExp('.*$abi.*').hasMatch(element.key))
|
||||
.toList();
|
||||
if (urls2.isNotEmpty && urls2.length < apk.apkUrls.length) {
|
||||
apk.apkUrls = urls2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
String apkVersion = apk.version.replaceAll('/', '-');
|
||||
var name = currentApp != null ? currentApp.name.trim() : '';
|
||||
name = name.isNotEmpty
|
||||
|
@@ -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.11.25+147 # When changing this, update the tag in main() accordingly
|
||||
version: 0.11.28+150 # When changing this, update the tag in main() accordingly
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.2 <3.0.0'
|
||||
|
Reference in New Issue
Block a user