From d22d04b3eb729f256859ea1c6c3330ab4fee0a5d Mon Sep 17 00:00:00 2001 From: franzap <_@franzap.com> Date: Thu, 2 Apr 2026 22:22:39 -0300 Subject: [PATCH] Minor fixes --- lib/screens/app_stack_screen.dart | 9 +++++ .../android_package_manager.dart | 15 ++++--- .../package_manager/package_manager.dart | 10 ++--- lib/widgets/batch_progress_banner.dart | 17 +++++++- lib/widgets/install_alert_dialog.dart | 39 ++++++++++++++----- 5 files changed, 65 insertions(+), 25 deletions(-) diff --git a/lib/screens/app_stack_screen.dart b/lib/screens/app_stack_screen.dart index 0c0f8a0..f047b8a 100644 --- a/lib/screens/app_stack_screen.dart +++ b/lib/screens/app_stack_screen.dart @@ -293,6 +293,15 @@ class _StackHeader extends StatelessWidget { ), ], ), + if (stack.description case final description?) ...[ + const SizedBox(height: 12), + Text( + description, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ], ], ); } diff --git a/lib/services/package_manager/android_package_manager.dart b/lib/services/package_manager/android_package_manager.dart index 3162c40..4e69cec 100644 --- a/lib/services/package_manager/android_package_manager.dart +++ b/lib/services/package_manager/android_package_manager.dart @@ -751,14 +751,13 @@ final class AndroidPackageManager extends PackageManager { final installedPkg = packages[appId]; final targetVc = op.target.versionCode; final installedVc = installedPkg?.versionCode; - final targetV = op.target.version; - final installedV = installedPkg?.version; + // Only use versionCode comparison — version string matching could + // give false positives when the same string maps to different builds. final completed = - (targetVc != null && - installedVc != null && - installedVc >= targetVc) || - (installedV != null && installedV == targetV); + targetVc != null && + installedVc != null && + installedVc >= targetVc; // Only clear if we can establish completion reliably. // Transition to Completed (not clearOperation) so the op stays until @@ -766,7 +765,7 @@ final class AndroidPackageManager extends PackageManager { if (completed) { debugPrint( '[PackageManager] Sync: completing operation for $appId ' - '(installedVc=$installedVc, targetVc=$targetVc, installedV=$installedV, targetV=$targetV)', + '(installedVc=$installedVc, targetVc=$targetVc)', ); // Sync fallback: state.installed was already overwritten with native // data above (line 723), so we must NOT call _updateInstalledPackage @@ -778,7 +777,7 @@ final class AndroidPackageManager extends PackageManager { } else { debugPrint( '[PackageManager] Sync: keeping operation for $appId ' - '(installedVc=$installedVc, targetVc=$targetVc, installedV=$installedV, targetV=$targetV)', + '(installedVc=$installedVc, targetVc=$targetVc)', ); } } diff --git a/lib/services/package_manager/package_manager.dart b/lib/services/package_manager/package_manager.dart index c2527a5..1d33b0e 100644 --- a/lib/services/package_manager/package_manager.dart +++ b/lib/services/package_manager/package_manager.dart @@ -564,7 +564,9 @@ abstract class PackageManager extends StateNotifier { await _performInstall(appId, op.target, op.filePath); } - /// Retry install from InstallCancelled state + /// Retry install from InstallCancelled state. + /// Routes through the install queue to respect the single-active-install + /// constraint (Android PackageInstaller limitation). Future retryInstall(String appId) async { final op = getOperation(appId); if (op is! InstallCancelled) return; @@ -581,11 +583,7 @@ abstract class PackageManager extends StateNotifier { return; } - // Exit InstallCancelled immediately so UI shows progress and native - // install events are no longer ignored by AndroidPackageManager. - setOperation(appId, Installing(target: op.target, filePath: op.filePath)); - - await _performInstall(appId, op.target, op.filePath); + _addToInstallQueue(appId, op.target, op.filePath); } /// Dismiss error and clean up diff --git a/lib/widgets/batch_progress_banner.dart b/lib/widgets/batch_progress_banner.dart index 674b8da..89f4397 100644 --- a/lib/widgets/batch_progress_banner.dart +++ b/lib/widgets/batch_progress_banner.dart @@ -23,8 +23,23 @@ class UpdateAllRow extends ConsumerWidget { child: TextButton.icon( onPressed: () async { final pm = ref.read(packageManagerProvider.notifier); + final pmState = ref.read(packageManagerProvider); final items = allUpdates - .where((app) => app.installable != null) + .where((app) { + final target = app.installable; + if (target == null) return false; + // Skip apps with cert mismatch — they will fail during + // install and waste bandwidth. + final installedHash = + pmState.installed[app.identifier]?.signatureHash ?? ''; + final targetHashes = target.certificateHashes; + if (installedHash.isNotEmpty && + targetHashes.isNotEmpty && + !targetHashes.contains(installedHash)) { + return false; + } + return true; + }) .map( (app) => ( appId: app.identifier, diff --git a/lib/widgets/install_alert_dialog.dart b/lib/widgets/install_alert_dialog.dart index f23c220..26324f1 100644 --- a/lib/widgets/install_alert_dialog.dart +++ b/lib/widgets/install_alert_dialog.dart @@ -33,7 +33,24 @@ class InstallAlertDialog extends HookConsumerWidget { _ => null, }; if (publisher == null) { - return const SizedBox.shrink(); + return const BaseDialog( + title: BaseDialogTitle('Trust this app?'), + content: BaseDialogContent( + children: [ + Center( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 24), + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ), + ), + ], + ), + actions: [], + ); } final trustedSignerNotifier = useState(false); final signedInPubkey = ref.watch(Signer.activePubkeyProvider); @@ -55,15 +72,17 @@ class InstallAlertDialog extends HookConsumerWidget { oneLine: false, size: baseTextSize, ), - Gap(10), - DownloadTextContainer( - beforeText: - '${app.name ?? app.identifier} will be installed from its original release location:', - oneLine: false, - showFullUrl: true, - url: app.installable!.urls.first, - size: baseTextSize, - ), + if (app.installable?.urls.firstOrNull != null) ...[ + Gap(10), + DownloadTextContainer( + beforeText: + '${app.name ?? app.identifier} will be installed from its original release location:', + oneLine: false, + showFullUrl: true, + url: app.installable!.urls.first, + size: baseTextSize, + ), + ], ] else ...[ canPersistTrust ? RelevantWhoFollowContainer(app: app, size: baseTextSize)