mirror of
https://github.com/zapstore/zapstore.git
synced 2026-10-05 20:48:24 +00:00
Sync installed packages on navigation to updates
This commit is contained in:
+27
-1
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:models/models.dart';
|
||||
@@ -8,6 +10,7 @@ import 'package:zapstore/screens/user_screen.dart';
|
||||
import 'package:zapstore/screens/search_screen.dart';
|
||||
import 'package:zapstore/screens/updates_screen.dart';
|
||||
import 'package:zapstore/screens/profile_screen.dart';
|
||||
import 'package:zapstore/services/package_manager/package_manager.dart';
|
||||
|
||||
/// Root paths for each navigation branch (used for back navigation handling)
|
||||
const kBranchRoots = ['/search', '/updates', '/profile'];
|
||||
@@ -70,7 +73,9 @@ GoRoute _userRoute() {
|
||||
}
|
||||
|
||||
final routerProvider = Provider<GoRouter>((ref) {
|
||||
return GoRouter(
|
||||
String? previousPath;
|
||||
|
||||
final router = GoRouter(
|
||||
initialLocation: '/search',
|
||||
routes: [
|
||||
// Top-level route for market:// intents
|
||||
@@ -124,4 +129,25 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// Listen for route changes to trigger actions
|
||||
void onRouteChange() {
|
||||
final currentPath = router.routerDelegate.currentConfiguration.uri.path;
|
||||
final isUpdatesRoute = currentPath.startsWith('/updates');
|
||||
final wasUpdatesRoute = previousPath?.startsWith('/updates') ?? false;
|
||||
|
||||
// Sync installed packages when navigating TO the updates branch
|
||||
if (isUpdatesRoute && !wasUpdatesRoute) {
|
||||
unawaited(
|
||||
ref.read(packageManagerProvider.notifier).syncInstalledPackages(),
|
||||
);
|
||||
}
|
||||
|
||||
previousPath = currentPath;
|
||||
}
|
||||
|
||||
router.routerDelegate.addListener(onRouteChange);
|
||||
ref.onDispose(() => router.routerDelegate.removeListener(onRouteChange));
|
||||
|
||||
return router;
|
||||
});
|
||||
|
||||
@@ -61,7 +61,7 @@ class _ConnectionStatusIndicator extends ConsumerWidget {
|
||||
|
||||
final statusColor = hasActiveConnection ? Colors.green : Colors.grey;
|
||||
final statusText = hasActiveConnection
|
||||
? 'Connected · Checking for updates'
|
||||
? 'Connected · Streaming updates'
|
||||
: 'Offline';
|
||||
|
||||
return Container(
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
# FEAT-001 — Package Manager
|
||||
|
||||
## Goal
|
||||
|
||||
Single source of truth for installed packages and active install operations.
|
||||
Manages the complete lifecycle: download → verify → install, with pause/resume/cancel support.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Managing non-APK file types
|
||||
- Auto-updating without user awareness
|
||||
- Installing from sources other than Nostr-published releases
|
||||
|
||||
## User-Visible Behavior
|
||||
|
||||
### Download Phase
|
||||
|
||||
- User taps "Install" → download begins, progress shown
|
||||
- User can pause/resume/cancel active downloads
|
||||
- Multiple downloads queue automatically (max 3 concurrent)
|
||||
- "Update All" queues all updates immediately with visual feedback
|
||||
|
||||
### Verification Phase
|
||||
|
||||
- After download completes, hash verification runs
|
||||
- Verification state is visible (not hidden)
|
||||
- Hash mismatch blocks install with clear error
|
||||
|
||||
### Permission Phase
|
||||
|
||||
- If "Install unknown apps" permission not granted, user is prompted
|
||||
- Permission state is explicit in UI
|
||||
- Once granted, all waiting installs advance automatically
|
||||
|
||||
### Install Phase
|
||||
|
||||
- Native Android install dialog shown
|
||||
- One install dialog at a time (serialized)
|
||||
- If user dismisses dialog, install shows "Tap to retry" state
|
||||
- Success updates installed list immediately (no stale UI)
|
||||
|
||||
### Failure States
|
||||
|
||||
- Download failed → clear error, can retry
|
||||
- Hash mismatch → error, cannot proceed
|
||||
- Certificate mismatch → offer "Uninstall and reinstall" option
|
||||
- Permission denied → guidance to enable in Settings
|
||||
|
||||
## State Machine
|
||||
|
||||
Operations follow this sealed class hierarchy (`install_operation.dart`):
|
||||
|
||||
```
|
||||
DownloadQueued → Downloading ↔ DownloadPaused
|
||||
↓
|
||||
Verifying
|
||||
↓
|
||||
AwaitingPermission (if needed)
|
||||
↓
|
||||
ReadyToInstall
|
||||
↓
|
||||
Installing → AwaitingUserAction (if dismissed)
|
||||
↓
|
||||
[cleared] or OperationFailed
|
||||
```
|
||||
|
||||
State transitions are unidirectional except Downloading ↔ DownloadPaused.
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- Network drops mid-download → download pauses or fails gracefully, can retry
|
||||
- App backgrounded during install → install completes, UI updates on return
|
||||
- 404 from origin server → automatic CDN fallback before failing
|
||||
- Stale operations (>7 days) → garbage collected on app restart
|
||||
- Android package DB race condition → state updated from target metadata, not sync
|
||||
|
||||
## Invariants
|
||||
|
||||
These are non-negotiable. Violations mean the implementation is broken.
|
||||
|
||||
1. **UI never blocks** — `install()` returns immediately; events drive state via EventChannel
|
||||
2. **One install dialog at a time** — Android PackageInstaller limitation, enforced by serialization
|
||||
3. **Hash verification before install** — Native side verifies before install session opens
|
||||
4. **Permission flow is explicit** — `AwaitingPermission` state exists for UI feedback
|
||||
5. **Downloaded files are cleaned up** — Deleted after success or dismissal
|
||||
6. **No polling** — All state changes via callbacks/events, never periodic checks
|
||||
|
||||
## Integration Boundaries
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ PackageManager (Dart) │
|
||||
│ - State machine owner │
|
||||
│ - Download management (background_downloader) │
|
||||
│ - Orchestrates flow │
|
||||
└─────────────────────────┬───────────────────────────────────┘
|
||||
│ MethodChannel / EventChannel
|
||||
┌─────────────────────────▼───────────────────────────────────┐
|
||||
│ AndroidPackageManagerPlugin (Kotlin) │
|
||||
│ - Hash verification │
|
||||
│ - PackageInstaller session │
|
||||
│ - Permission checks │
|
||||
│ - Emits: verifying/started/success/failed/cancelled │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] User can download, pause, resume, cancel downloads
|
||||
- [ ] User can install apps with proper verification
|
||||
- [ ] Multiple downloads queue correctly (max 3 concurrent)
|
||||
- [ ] Install failures show actionable error messages
|
||||
- [ ] Certificate mismatch offers force-update option
|
||||
- [ ] UI remains responsive throughout all operations
|
||||
- [ ] No operations block the UI thread
|
||||
|
||||
## Files
|
||||
|
||||
- `lib/services/package_manager/package_manager.dart` — Base class, state machine
|
||||
- `lib/services/package_manager/install_operation.dart` — State definitions
|
||||
- `lib/services/package_manager/android_package_manager.dart` — Android implementation
|
||||
- `android/.../AndroidPackageManagerPlugin.kt` — Native side
|
||||
Reference in New Issue
Block a user