Files
zapstore/lib/widgets/batch_progress_banner.dart
Henrique Velloso 84906164bb feat: auto-backup installed apps on install/uninstall
Back up the list of installed apps to Nostr (encrypted AppStack) when
batch installs complete or apps are uninstalled. Toggle in profile, off
by default. Restore screen fetches backup and lets users reinstall apps.

Changes by file:

- installed_apps_backup_service: Listener for batch completion and
  installed-list changes; debounced backup (3s); restore-only batches
  ignored; timer cancelled on provider dispose; logs reduced to errors
  and completion only; null-aware assignment for baseline keys.

- secure_storage_service: Persistence for backup-enabled toggle (off
  by default).

- profile_screen: Toggle to enable/disable backup; entry point for
  restore flow.

- restore_installed_apps_screen: Restore UI with shared AppBar; lists
  apps to install and already installed; cards navigate to app detail.

- main_scaffold: Watches backup listener provider so it stays active.

- package_manager: Removed unused installSourceProvider; install source
  handling for restore operations.

- install_operation, android_package_manager, install_button,
  batch_progress_banner: InstallSource.restore support for restore flow.

- app_card: Factory constructors (forRestore, forDiscovery, etc.) to
  simplify restore and other screens.

- router, installed_packages_snapshot: Supporting wiring for the feature.
2026-02-26 18:27:41 -03:00

64 lines
2.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:models/models.dart';
import 'package:zapstore/services/package_manager/package_manager.dart';
import 'package:zapstore/theme.dart';
import 'package:zapstore/utils/extensions.dart';
/// "Update All" / "Install All" button row.
/// Progress and "All done" states are handled by sticky banners in _UpdatesListBody.
class UpdateAllRow extends ConsumerWidget {
const UpdateAllRow({
super.key,
required this.allUpdates,
this.label = 'Update All',
this.installSource = InstallSource.normal,
});
final List<App> allUpdates;
final String label;
final InstallSource installSource;
@override
Widget build(BuildContext context, WidgetRef ref) {
if (allUpdates.length < 2) {
return const SizedBox.shrink();
}
return Container(
margin: const EdgeInsets.fromLTRB(16, 0, 16, 8),
width: double.infinity,
child: TextButton.icon(
onPressed: () async {
final pm = ref.read(packageManagerProvider.notifier);
final items = allUpdates
.where((app) => app.latestFileMetadata != null)
.map(
(app) => (
appId: app.identifier,
target: app.latestFileMetadata!,
displayName: app.name,
source: installSource,
),
)
.toList();
await pm.queueDownloads(items);
},
icon: const Icon(Icons.download, size: 16, color: Colors.white),
label: Text(
'$label (${allUpdates.length})',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
style: TextButton.styleFrom(
backgroundColor: AppColors.darkPillBackground,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
),
),
);
}
}