mirror of
https://github.com/zapstore/zapstore.git
synced 2026-10-05 12:38:24 +00:00
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.
87 lines
2.8 KiB
Dart
87 lines
2.8 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:path/path.dart' as path;
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:zapstore/services/package_manager/package_manager.dart';
|
|
|
|
class InstalledPackagesSnapshot {
|
|
static const _fileName = 'installed_packages_snapshot.json';
|
|
|
|
static Future<File> _file() async {
|
|
final dir = await getApplicationSupportDirectory();
|
|
return File(path.join(dir.path, _fileName));
|
|
}
|
|
|
|
static Future<void> save(Map<String, PackageInfo> installed) async {
|
|
try {
|
|
final file = await _file();
|
|
await Directory(path.dirname(file.path)).create(recursive: true);
|
|
final list = installed.values
|
|
.map(
|
|
(p) => <String, dynamic>{
|
|
'appId': p.appId,
|
|
'name': p.name,
|
|
'version': p.version,
|
|
'versionCode': p.versionCode,
|
|
'signatureHash': p.signatureHash,
|
|
'canInstallSilently': p.canInstallSilently,
|
|
},
|
|
)
|
|
.toList(growable: false);
|
|
final payload = jsonEncode({
|
|
'v': 1,
|
|
'savedAt': DateTime.now().millisecondsSinceEpoch,
|
|
'installed': list,
|
|
});
|
|
await file.writeAsString(payload, flush: true);
|
|
} catch (e) {
|
|
// Best-effort snapshot only.
|
|
if (kDebugMode) {
|
|
debugPrint('[InstalledPackagesSnapshot] Save failed: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
static Future<Map<String, PackageInfo>> load() async {
|
|
try {
|
|
final file = await _file();
|
|
if (!await file.exists()) return {};
|
|
|
|
final raw = await file.readAsString();
|
|
final decoded = jsonDecode(raw);
|
|
if (decoded is! Map) return {};
|
|
final version = decoded['v'];
|
|
if (version != null && version is! int && kDebugMode) {
|
|
debugPrint('[InstalledPackagesSnapshot] Unknown schema: $version');
|
|
}
|
|
final installed = decoded['installed'];
|
|
if (installed is! List) return {};
|
|
|
|
final result = <String, PackageInfo>{};
|
|
for (final item in installed) {
|
|
if (item is! Map) continue;
|
|
final map = Map<String, dynamic>.from(item);
|
|
final appId = map['appId'] as String?;
|
|
if (appId == null || appId.isEmpty) continue;
|
|
result[appId] = PackageInfo(
|
|
appId: appId,
|
|
name: map['name'] as String?,
|
|
version: (map['version'] as String?) ?? '0.0.0',
|
|
versionCode: map['versionCode'] as int?,
|
|
signatureHash: (map['signatureHash'] as String?) ?? '',
|
|
installTime: null,
|
|
canInstallSilently: (map['canInstallSilently'] as bool?) ?? false,
|
|
);
|
|
}
|
|
return result;
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
debugPrint('[InstalledPackagesSnapshot] Load failed: $e');
|
|
}
|
|
return {};
|
|
}
|
|
}
|
|
}
|