mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-10-21 18:03:45 +02:00
Merge pull request #1643 from ImranR98/dev
- Bugfix: Include GitLab token in APK request (#1622) - Bugfix: Don't trim trailing slashes when parsing links (#1625) - Improve contrast of placeholder icon in dark mode (#1637) - Improve app loading times - Revert a previous change to background downloads - Add a "clear logs" button
This commit is contained in:
2
.flutter
2
.flutter
Submodule .flutter updated: 5dcb86f68f...a14f74ff3a
@@ -111,6 +111,14 @@ class GitLab extends AppSource {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
|
||||
Map<String, dynamic> additionalSettings) async {
|
||||
String? PAT = await getPATIfAny(hostChanged ? additionalSettings : {});
|
||||
String optionalAuth = (PAT != null) ? 'private_token=$PAT' : '';
|
||||
return '$apkUrl?$optionalAuth';
|
||||
}
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl,
|
||||
|
@@ -226,18 +226,26 @@ class _AppPageState extends State<AppPage> {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
app?.icon != null
|
||||
? Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
GestureDetector(
|
||||
child: Image.memory(
|
||||
app!.icon!,
|
||||
height: 150,
|
||||
gaplessPlayback: true,
|
||||
),
|
||||
onTap: () => pm.openApp(app.app.id),
|
||||
)
|
||||
])
|
||||
: Container(),
|
||||
FutureBuilder(
|
||||
future: appsProvider.updateAppIcon(app?.app.id),
|
||||
builder: (ctx, val) {
|
||||
return app?.icon != null
|
||||
? Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: app == null
|
||||
? null
|
||||
: () => pm.openApp(app.app.id),
|
||||
child: Image.memory(
|
||||
app!.icon!,
|
||||
height: 150,
|
||||
gaplessPlayback: true,
|
||||
),
|
||||
)
|
||||
])
|
||||
: Container();
|
||||
}),
|
||||
const SizedBox(
|
||||
height: 25,
|
||||
),
|
||||
|
@@ -354,7 +354,11 @@ class AppsPageState extends State<AppsPage> {
|
||||
SliverFillRemaining(
|
||||
child: Center(
|
||||
child: Text(
|
||||
appsProvider.apps.isEmpty ? tr('noApps') : tr('noAppsForFilter'),
|
||||
appsProvider.apps.isEmpty
|
||||
? appsProvider.loadingApps
|
||||
? tr('pleaseWait')
|
||||
: tr('noApps')
|
||||
: tr('noAppsForFilter'),
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
textAlign: TextAlign.center,
|
||||
))),
|
||||
@@ -402,29 +406,36 @@ class AppsPageState extends State<AppsPage> {
|
||||
}
|
||||
|
||||
getAppIcon(int appIndex) {
|
||||
return listedApps[appIndex].icon != null
|
||||
? Image.memory(
|
||||
listedApps[appIndex].icon!,
|
||||
gaplessPlayback: true,
|
||||
)
|
||||
: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Transform(
|
||||
alignment: Alignment.center,
|
||||
transform: Matrix4.rotationZ(0.31),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(15),
|
||||
child: Image(
|
||||
image: const AssetImage(
|
||||
'assets/graphics/icon_small.png'),
|
||||
color: Colors.white.withOpacity(0.3),
|
||||
colorBlendMode: BlendMode.modulate,
|
||||
gaplessPlayback: true,
|
||||
),
|
||||
)),
|
||||
]);
|
||||
return FutureBuilder(
|
||||
future: appsProvider.updateAppIcon(listedApps[appIndex].app.id),
|
||||
builder: (ctx, val) {
|
||||
return listedApps[appIndex].icon != null
|
||||
? Image.memory(
|
||||
listedApps[appIndex].icon!,
|
||||
gaplessPlayback: true,
|
||||
)
|
||||
: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Transform(
|
||||
alignment: Alignment.center,
|
||||
transform: Matrix4.rotationZ(0.31),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(15),
|
||||
child: Image(
|
||||
image: const AssetImage(
|
||||
'assets/graphics/icon_small.png'),
|
||||
color: Theme.of(context).brightness ==
|
||||
Brightness.dark
|
||||
? Colors.white.withOpacity(0.4)
|
||||
: Colors.white.withOpacity(0.3),
|
||||
colorBlendMode: BlendMode.modulate,
|
||||
gaplessPlayback: true,
|
||||
),
|
||||
)),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
getVersionText(int appIndex) {
|
||||
|
@@ -5,6 +5,7 @@ import 'package:flex_color_picker/flex_color_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:obtainium/components/custom_app_bar.dart';
|
||||
import 'package:obtainium/components/generated_form.dart';
|
||||
import 'package:obtainium/components/generated_form_modal.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/main.dart';
|
||||
import 'package:obtainium/providers/apps_provider.dart';
|
||||
@@ -945,6 +946,25 @@ class _LogsDialogState extends State<LogsDialog> {
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
var cont = (await showDialog<Map<String, dynamic>?>(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return GeneratedFormModal(
|
||||
title: tr('appLogs'),
|
||||
items: const [],
|
||||
initValid: true,
|
||||
message: tr('removeFromObtainium'),
|
||||
);
|
||||
})) !=
|
||||
null;
|
||||
if (cont) {
|
||||
logsProvider.clear();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
child: Text(tr('remove'))),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
|
@@ -329,6 +329,10 @@ Future<Map<String, String>> getHeaders(String url,
|
||||
return returnHeaders;
|
||||
}
|
||||
|
||||
Future<List<PackageInfo>> getAllInstalledInfo() async {
|
||||
return await pm.getInstalledPackages() ?? [];
|
||||
}
|
||||
|
||||
Future<PackageInfo?> getInstalledInfo(String? packageName,
|
||||
{bool printErr = true}) async {
|
||||
if (packageName != null) {
|
||||
@@ -364,7 +368,9 @@ class AppsProvider with ChangeNotifier {
|
||||
foregroundStream = FGBGEvents.stream.asBroadcastStream();
|
||||
foregroundSubscription = foregroundStream?.listen((event) async {
|
||||
isForeground = event == FGBGType.foreground;
|
||||
if (isForeground) loadApps();
|
||||
if (isForeground) {
|
||||
await loadApps();
|
||||
}
|
||||
});
|
||||
() async {
|
||||
await settingsProvider.initializeSettings();
|
||||
@@ -1160,17 +1166,6 @@ class AppsProvider with ChangeNotifier {
|
||||
: false;
|
||||
}
|
||||
|
||||
Future<void> updateInstallStatusInMemory(AppInMemory app) async {
|
||||
apps[app.app.id]?.installedInfo = await getInstalledInfo(app.app.id);
|
||||
apps[app.app.id]?.icon =
|
||||
await apps[app.app.id]?.installedInfo?.applicationInfo?.getAppIcon();
|
||||
apps[app.app.id]?.app.name = await (apps[app.app.id]
|
||||
?.installedInfo
|
||||
?.applicationInfo
|
||||
?.getAppLabel()) ??
|
||||
app.name;
|
||||
}
|
||||
|
||||
Future<void> loadApps({String? singleId}) async {
|
||||
while (loadingApps) {
|
||||
await Future.delayed(const Duration(microseconds: 1));
|
||||
@@ -1179,6 +1174,8 @@ class AppsProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
var sp = SourceProvider();
|
||||
List<List<String>> errors = [];
|
||||
var installedAppsData = await getAllInstalledInfo();
|
||||
List<String> removedAppIds = [];
|
||||
await Future.wait((await getAppsDir()) // Parse Apps from JSON
|
||||
.listSync()
|
||||
.map((item) async {
|
||||
@@ -1199,43 +1196,53 @@ class AppsProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
if (app != null) {
|
||||
// Save the app to the in-memory list without grabbing any OS info first
|
||||
apps.update(
|
||||
app.id,
|
||||
(value) => AppInMemory(
|
||||
app!, value.downloadProgress, value.installedInfo, value.icon),
|
||||
ifAbsent: () => AppInMemory(app!, null, null, null));
|
||||
notifyListeners();
|
||||
try {
|
||||
// Try getting the app's source to ensure no invalid apps get loaded
|
||||
sp.getSource(app.url, overrideSource: app.overrideSource);
|
||||
// If the app is installed, grab its OS data and reconcile install statuses
|
||||
PackageInfo? installedInfo;
|
||||
try {
|
||||
installedInfo =
|
||||
installedAppsData.firstWhere((i) => i.packageName == app!.id);
|
||||
} catch (e) {
|
||||
// If the app isn't installed the above throws an error
|
||||
}
|
||||
// Reconcile differences between the installed and recorded install info
|
||||
var moddedApp =
|
||||
getCorrectedInstallStatusAppIfPossible(app, installedInfo);
|
||||
if (moddedApp != null) {
|
||||
app = moddedApp;
|
||||
// Note the app ID if it was uninstalled externally
|
||||
if (moddedApp.installedVersion == null) {
|
||||
removedAppIds.add(moddedApp.id);
|
||||
}
|
||||
}
|
||||
// Update the app in memory with install info and corrections
|
||||
apps.update(
|
||||
app.id,
|
||||
(value) => AppInMemory(app!, value.downloadProgress,
|
||||
value.installedInfo, value.icon),
|
||||
ifAbsent: () => AppInMemory(app!, null, null, null));
|
||||
(value) => AppInMemory(
|
||||
app!, value.downloadProgress, installedInfo, value.icon),
|
||||
ifAbsent: () => AppInMemory(app!, null, installedInfo, null));
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
errors.add([app.id, app.finalName, e.toString()]);
|
||||
errors.add([app!.id, app.finalName, e.toString()]);
|
||||
}
|
||||
}
|
||||
}));
|
||||
notifyListeners();
|
||||
if (errors.isNotEmpty) {
|
||||
removeApps(errors.map((e) => e[0]).toList());
|
||||
NotificationsProvider().notify(
|
||||
AppsRemovedNotification(errors.map((e) => [e[1], e[2]]).toList()));
|
||||
}
|
||||
// Get install status and other OS info for each App (slow)
|
||||
List<App> modifiedApps = [];
|
||||
await Future.wait(apps.values.map((app) async {
|
||||
await updateInstallStatusInMemory(app);
|
||||
var moddedApp =
|
||||
getCorrectedInstallStatusAppIfPossible(app.app, app.installedInfo);
|
||||
if (moddedApp != null) {
|
||||
modifiedApps.add(moddedApp);
|
||||
}
|
||||
}));
|
||||
notifyListeners();
|
||||
// Reconcile version differences
|
||||
if (modifiedApps.isNotEmpty) {
|
||||
await saveApps(modifiedApps, attemptToCorrectInstallStatus: false);
|
||||
var removedAppIds = modifiedApps
|
||||
.where((a) => a.installedVersion == null)
|
||||
.map((e) => e.id)
|
||||
.toList();
|
||||
// After reconciliation, delete externally uninstalled Apps if needed
|
||||
// Delete externally uninstalled Apps if needed
|
||||
if (removedAppIds.isNotEmpty) {
|
||||
if (removedAppIds.isNotEmpty) {
|
||||
if (settingsProvider.removeOnExternalUninstall) {
|
||||
await removeApps(removedAppIds);
|
||||
@@ -1246,6 +1253,22 @@ class AppsProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> updateAppIcon(String? appId) async {
|
||||
if (apps[appId]?.icon == null) {
|
||||
var icon =
|
||||
(await apps[appId]?.installedInfo?.applicationInfo?.getAppIcon());
|
||||
if (icon != null) {
|
||||
apps.update(
|
||||
apps[appId]!.app.id,
|
||||
(value) => AppInMemory(apps[appId]!.app, value.downloadProgress,
|
||||
value.installedInfo, icon),
|
||||
ifAbsent: () => AppInMemory(
|
||||
apps[appId]!.app, null, apps[appId]?.installedInfo, icon));
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveApps(List<App> apps,
|
||||
{bool attemptToCorrectInstallStatus = true,
|
||||
bool onlyIfExists = true}) async {
|
||||
@@ -1941,8 +1964,7 @@ Future<void> bgUpdateCheck(String taskId, Map<String, dynamic>? params) async {
|
||||
await appsProvider.downloadAndInstallLatestApps(
|
||||
toInstall.map((e) => e.key).toList(), null,
|
||||
notificationsProvider: notificationsProvider,
|
||||
forceParallelDownloads: true,
|
||||
useExisting: false);
|
||||
forceParallelDownloads: true);
|
||||
} catch (e) {
|
||||
if (e is MultiAppMultiError) {
|
||||
e.idsByErrorString.forEach((key, value) {
|
||||
|
@@ -354,11 +354,13 @@ preStandardizeUrl(String url) {
|
||||
url.toLowerCase().indexOf('https://') != 0) {
|
||||
url = 'https://$url';
|
||||
}
|
||||
var trailingSlash = Uri.tryParse(url)?.path.endsWith('/') ?? false;
|
||||
url = url
|
||||
.split('/')
|
||||
.where((e) => e.isNotEmpty)
|
||||
.join('/')
|
||||
.replaceFirst(':/', '://');
|
||||
.split('/')
|
||||
.where((e) => e.isNotEmpty)
|
||||
.join('/')
|
||||
.replaceFirst(':/', '://') +
|
||||
(trailingSlash ? '/' : '');
|
||||
return url;
|
||||
}
|
||||
|
||||
@@ -523,8 +525,7 @@ abstract class AppSource {
|
||||
[GeneratedFormTextField('appName', label: tr('appName'), required: false)],
|
||||
[
|
||||
GeneratedFormSwitch('shizukuPretendToBeGooglePlay',
|
||||
label: tr('shizukuPretendToBeGooglePlay'),
|
||||
defaultValue: false)
|
||||
label: tr('shizukuPretendToBeGooglePlay'), defaultValue: false)
|
||||
],
|
||||
[
|
||||
GeneratedFormSwitch('exemptFromBackgroundUpdates',
|
||||
|
24
pubspec.lock
24
pubspec.lock
@@ -47,18 +47,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: app_links
|
||||
sha256: "8c6ef5ba9e26b720d4c9073826befb87df2ab5e7a81c22b6c3145080b5e736c9"
|
||||
sha256: "96e677810b83707ff5e10fac11e4839daa0ea4e0123c35864c092699165eb3db"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.2"
|
||||
version: "6.1.1"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: ecf4273855368121b1caed0d10d4513c7241dfc813f7d3c8933b36622ae9b265
|
||||
sha256: "6bd38d335f0954f5fad9c79e614604fbf03a0e5b975923dd001b6ea965ef5b4b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.5.1"
|
||||
version: "3.6.0"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -443,10 +443,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
|
||||
sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.7"
|
||||
version: "4.2.0"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -889,10 +889,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775"
|
||||
sha256: "17cd5e205ea615e2c6ea7a77323a11712dffa0720a8a90540db57a01347f9ad9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.1"
|
||||
version: "6.3.2"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -977,18 +977,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: webview_flutter
|
||||
sha256: "25e1b6e839e8cbfbd708abc6f85ed09d1727e24e08e08c6b8590d7c65c9a8932"
|
||||
sha256: "6869c8786d179f929144b4a1f86e09ac0eddfe475984951ea6c634774c16b522"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.7.0"
|
||||
version: "4.8.0"
|
||||
webview_flutter_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_android
|
||||
sha256: dad3313c9ead95517bb1cae5e1c9d20ba83729d5a59e5e83c0a2d66203f27f91
|
||||
sha256: "2282ba2320af34b2bd5320156c664d73f3f022341ed78847bc87723bf88c142f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.16.1"
|
||||
version: "3.16.2"
|
||||
webview_flutter_platform_interface:
|
||||
dependency: transitive
|
||||
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
|
||||
# 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: 1.1.9+2266
|
||||
version: 1.1.10+2267
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
Reference in New Issue
Block a user