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:
Imran
2024-05-24 16:27:18 -04:00
committed by GitHub
9 changed files with 164 additions and 94 deletions

View File

@@ -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 @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, String standardUrl,

View File

@@ -226,18 +226,26 @@ class _AppPageState extends State<AppPage> {
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
const SizedBox(height: 20), const SizedBox(height: 20),
app?.icon != null FutureBuilder(
? Row(mainAxisAlignment: MainAxisAlignment.center, children: [ future: appsProvider.updateAppIcon(app?.app.id),
GestureDetector( builder: (ctx, val) {
child: Image.memory( return app?.icon != null
app!.icon!, ? Row(
height: 150, mainAxisAlignment: MainAxisAlignment.center,
gaplessPlayback: true, children: [
), GestureDetector(
onTap: () => pm.openApp(app.app.id), onTap: app == null
) ? null
]) : () => pm.openApp(app.app.id),
: Container(), child: Image.memory(
app!.icon!,
height: 150,
gaplessPlayback: true,
),
)
])
: Container();
}),
const SizedBox( const SizedBox(
height: 25, height: 25,
), ),

View File

@@ -354,7 +354,11 @@ class AppsPageState extends State<AppsPage> {
SliverFillRemaining( SliverFillRemaining(
child: Center( child: Center(
child: Text( 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, style: Theme.of(context).textTheme.headlineMedium,
textAlign: TextAlign.center, textAlign: TextAlign.center,
))), ))),
@@ -402,29 +406,36 @@ class AppsPageState extends State<AppsPage> {
} }
getAppIcon(int appIndex) { getAppIcon(int appIndex) {
return listedApps[appIndex].icon != null return FutureBuilder(
? Image.memory( future: appsProvider.updateAppIcon(listedApps[appIndex].app.id),
listedApps[appIndex].icon!, builder: (ctx, val) {
gaplessPlayback: true, return listedApps[appIndex].icon != null
) ? Image.memory(
: Row( listedApps[appIndex].icon!,
mainAxisSize: MainAxisSize.min, gaplessPlayback: true,
mainAxisAlignment: MainAxisAlignment.center, )
children: [ : Row(
Transform( mainAxisSize: MainAxisSize.min,
alignment: Alignment.center, mainAxisAlignment: MainAxisAlignment.center,
transform: Matrix4.rotationZ(0.31), children: [
child: Padding( Transform(
padding: const EdgeInsets.all(15), alignment: Alignment.center,
child: Image( transform: Matrix4.rotationZ(0.31),
image: const AssetImage( child: Padding(
'assets/graphics/icon_small.png'), padding: const EdgeInsets.all(15),
color: Colors.white.withOpacity(0.3), child: Image(
colorBlendMode: BlendMode.modulate, image: const AssetImage(
gaplessPlayback: true, '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) { getVersionText(int appIndex) {

View File

@@ -5,6 +5,7 @@ import 'package:flex_color_picker/flex_color_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:obtainium/components/custom_app_bar.dart'; import 'package:obtainium/components/custom_app_bar.dart';
import 'package:obtainium/components/generated_form.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/custom_errors.dart';
import 'package:obtainium/main.dart'; import 'package:obtainium/main.dart';
import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/apps_provider.dart';
@@ -945,6 +946,25 @@ class _LogsDialogState extends State<LogsDialog> {
], ],
), ),
actions: [ 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( TextButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();

View File

@@ -329,6 +329,10 @@ Future<Map<String, String>> getHeaders(String url,
return returnHeaders; return returnHeaders;
} }
Future<List<PackageInfo>> getAllInstalledInfo() async {
return await pm.getInstalledPackages() ?? [];
}
Future<PackageInfo?> getInstalledInfo(String? packageName, Future<PackageInfo?> getInstalledInfo(String? packageName,
{bool printErr = true}) async { {bool printErr = true}) async {
if (packageName != null) { if (packageName != null) {
@@ -364,7 +368,9 @@ class AppsProvider with ChangeNotifier {
foregroundStream = FGBGEvents.stream.asBroadcastStream(); foregroundStream = FGBGEvents.stream.asBroadcastStream();
foregroundSubscription = foregroundStream?.listen((event) async { foregroundSubscription = foregroundStream?.listen((event) async {
isForeground = event == FGBGType.foreground; isForeground = event == FGBGType.foreground;
if (isForeground) loadApps(); if (isForeground) {
await loadApps();
}
}); });
() async { () async {
await settingsProvider.initializeSettings(); await settingsProvider.initializeSettings();
@@ -1160,17 +1166,6 @@ class AppsProvider with ChangeNotifier {
: false; : 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 { Future<void> loadApps({String? singleId}) async {
while (loadingApps) { while (loadingApps) {
await Future.delayed(const Duration(microseconds: 1)); await Future.delayed(const Duration(microseconds: 1));
@@ -1179,6 +1174,8 @@ class AppsProvider with ChangeNotifier {
notifyListeners(); notifyListeners();
var sp = SourceProvider(); var sp = SourceProvider();
List<List<String>> errors = []; List<List<String>> errors = [];
var installedAppsData = await getAllInstalledInfo();
List<String> removedAppIds = [];
await Future.wait((await getAppsDir()) // Parse Apps from JSON await Future.wait((await getAppsDir()) // Parse Apps from JSON
.listSync() .listSync()
.map((item) async { .map((item) async {
@@ -1199,43 +1196,53 @@ class AppsProvider with ChangeNotifier {
} }
} }
if (app != null) { 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 {
// Try getting the app's source to ensure no invalid apps get loaded
sp.getSource(app.url, overrideSource: app.overrideSource); 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( apps.update(
app.id, app.id,
(value) => AppInMemory(app!, value.downloadProgress, (value) => AppInMemory(
value.installedInfo, value.icon), app!, value.downloadProgress, installedInfo, value.icon),
ifAbsent: () => AppInMemory(app!, null, null, null)); ifAbsent: () => AppInMemory(app!, null, installedInfo, null));
notifyListeners();
} catch (e) { } catch (e) {
errors.add([app.id, app.finalName, e.toString()]); errors.add([app!.id, app.finalName, e.toString()]);
} }
} }
})); }));
notifyListeners();
if (errors.isNotEmpty) { if (errors.isNotEmpty) {
removeApps(errors.map((e) => e[0]).toList()); removeApps(errors.map((e) => e[0]).toList());
NotificationsProvider().notify( NotificationsProvider().notify(
AppsRemovedNotification(errors.map((e) => [e[1], e[2]]).toList())); AppsRemovedNotification(errors.map((e) => [e[1], e[2]]).toList()));
} }
// Get install status and other OS info for each App (slow) // Delete externally uninstalled Apps if needed
List<App> modifiedApps = []; if (removedAppIds.isNotEmpty) {
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
if (removedAppIds.isNotEmpty) { if (removedAppIds.isNotEmpty) {
if (settingsProvider.removeOnExternalUninstall) { if (settingsProvider.removeOnExternalUninstall) {
await removeApps(removedAppIds); await removeApps(removedAppIds);
@@ -1246,6 +1253,22 @@ class AppsProvider with ChangeNotifier {
notifyListeners(); 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, Future<void> saveApps(List<App> apps,
{bool attemptToCorrectInstallStatus = true, {bool attemptToCorrectInstallStatus = true,
bool onlyIfExists = true}) async { bool onlyIfExists = true}) async {
@@ -1941,8 +1964,7 @@ Future<void> bgUpdateCheck(String taskId, Map<String, dynamic>? params) async {
await appsProvider.downloadAndInstallLatestApps( await appsProvider.downloadAndInstallLatestApps(
toInstall.map((e) => e.key).toList(), null, toInstall.map((e) => e.key).toList(), null,
notificationsProvider: notificationsProvider, notificationsProvider: notificationsProvider,
forceParallelDownloads: true, forceParallelDownloads: true);
useExisting: false);
} catch (e) { } catch (e) {
if (e is MultiAppMultiError) { if (e is MultiAppMultiError) {
e.idsByErrorString.forEach((key, value) { e.idsByErrorString.forEach((key, value) {

View File

@@ -354,11 +354,13 @@ preStandardizeUrl(String url) {
url.toLowerCase().indexOf('https://') != 0) { url.toLowerCase().indexOf('https://') != 0) {
url = 'https://$url'; url = 'https://$url';
} }
var trailingSlash = Uri.tryParse(url)?.path.endsWith('/') ?? false;
url = url url = url
.split('/') .split('/')
.where((e) => e.isNotEmpty) .where((e) => e.isNotEmpty)
.join('/') .join('/')
.replaceFirst(':/', '://'); .replaceFirst(':/', '://') +
(trailingSlash ? '/' : '');
return url; return url;
} }
@@ -523,8 +525,7 @@ abstract class AppSource {
[GeneratedFormTextField('appName', label: tr('appName'), required: false)], [GeneratedFormTextField('appName', label: tr('appName'), required: false)],
[ [
GeneratedFormSwitch('shizukuPretendToBeGooglePlay', GeneratedFormSwitch('shizukuPretendToBeGooglePlay',
label: tr('shizukuPretendToBeGooglePlay'), label: tr('shizukuPretendToBeGooglePlay'), defaultValue: false)
defaultValue: false)
], ],
[ [
GeneratedFormSwitch('exemptFromBackgroundUpdates', GeneratedFormSwitch('exemptFromBackgroundUpdates',

View File

@@ -47,18 +47,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: app_links name: app_links
sha256: "8c6ef5ba9e26b720d4c9073826befb87df2ab5e7a81c22b6c3145080b5e736c9" sha256: "96e677810b83707ff5e10fac11e4839daa0ea4e0123c35864c092699165eb3db"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.2" version: "6.1.1"
archive: archive:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
sha256: ecf4273855368121b1caed0d10d4513c7241dfc813f7d3c8933b36622ae9b265 sha256: "6bd38d335f0954f5fad9c79e614604fbf03a0e5b975923dd001b6ea965ef5b4b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.5.1" version: "3.6.0"
args: args:
dependency: transitive dependency: transitive
description: description:
@@ -443,10 +443,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: image name: image
sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e" sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.7" version: "4.2.0"
intl: intl:
dependency: transitive dependency: transitive
description: description:
@@ -889,10 +889,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775" sha256: "17cd5e205ea615e2c6ea7a77323a11712dffa0720a8a90540db57a01347f9ad9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.1" version: "6.3.2"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
@@ -977,18 +977,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: webview_flutter name: webview_flutter
sha256: "25e1b6e839e8cbfbd708abc6f85ed09d1727e24e08e08c6b8590d7c65c9a8932" sha256: "6869c8786d179f929144b4a1f86e09ac0eddfe475984951ea6c634774c16b522"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.7.0" version: "4.8.0"
webview_flutter_android: webview_flutter_android:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_android name: webview_flutter_android
sha256: dad3313c9ead95517bb1cae5e1c9d20ba83729d5a59e5e83c0a2d66203f27f91 sha256: "2282ba2320af34b2bd5320156c664d73f3f022341ed78847bc87723bf88c142f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.16.1" version: "3.16.2"
webview_flutter_platform_interface: webview_flutter_platform_interface:
dependency: transitive dependency: transitive
description: 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 # 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: 1.1.9+2266 version: 1.1.10+2267
environment: environment:
sdk: '>=3.0.0 <4.0.0' sdk: '>=3.0.0 <4.0.0'