From 4527fb20b8d7cba780ec4929236b9310617752db Mon Sep 17 00:00:00 2001 From: franzap <_@franzap.com> Date: Sun, 13 Oct 2024 21:08:33 -0300 Subject: [PATCH] Fix install/status related issues --- lib/models/app.dart | 44 +++++++++++++++++++-------------- lib/models/local_app.dart | 40 +++++++++++------------------- lib/models/local_app.g.dart | 1 - lib/navigation/router.dart | 3 ++- lib/widgets/install_button.dart | 10 +++----- 5 files changed, 47 insertions(+), 51 deletions(-) diff --git a/lib/models/app.dart b/lib/models/app.dart index 1f444e9..352beec 100644 --- a/lib/models/app.dart +++ b/lib/models/app.dart @@ -54,7 +54,7 @@ class App extends BaseApp with DataModelMixin { .firstOrNull; } - bool get canInstall => localApp.value?.status == AppInstallStatus.installable; + bool get canInstall => localApp.value?.status == null; bool get canUpdate => localApp.value?.status == AppInstallStatus.updatable; bool get isUpdated => localApp.value?.status == AppInstallStatus.updated; @@ -65,7 +65,7 @@ class App extends BaseApp with DataModelMixin { final adapter = DataModel.adapterFor(this) as AppAdapter; final notifier = - adapter.ref.read(installationProgressProvider(id!.toString()).notifier); + adapter.ref.read(installationProgressProvider(id!).notifier); final installPermission = await Permission.requestInstallPackages.status; if (!installPermission.isGranted) { @@ -99,7 +99,7 @@ class App extends BaseApp with DataModelMixin { if (result['isSuccess']) { await file.delete(); await adapter.ref.localApps.localAppAdapter - .updateInstallStatus(appId: id?.toString()); + .refreshUpdateStatus(appId: identifier); notifier.state = IdleInstallProgress(); } else { const msg = 'Android rejected installation'; @@ -112,6 +112,7 @@ class App extends BaseApp with DataModelMixin { await installOnDevice(); } else { if (fileExists) { + // If file exists download was probably partial, remove await file.delete(); } @@ -180,24 +181,31 @@ mixin AppAdapter on Adapter { final releases = await ref.releases.findAll( params: {'#d': latestReleaseIdentifiers}, ); + // TODO: Deprecated, will be removed // Some developers without access to the latest zapstore-cli // have not published their apps with latest release identifiers // so load as usual - final oldApps = - apps.where((app) => !latestReleaseIdentifiers.contains(app.identifier)); - final oldReleases = await ref.releases.findAll( + final deprecatedApps = + apps.where((app) => app.linkedReplaceableEvents.isEmpty); + final deprecatedReleases = await ref.releases.findAll( params: { - '#a': oldApps.map((app) => app.getReplaceableEventLink().formatted) + '#a': + deprecatedApps.map((app) => app.getReplaceableEventLink().formatted) }, ); - final oldReleasesLatest = - oldReleases.sorted((a, b) => b.createdAt!.compareTo(a.createdAt!)); - final metadataIds = [...releases, ...oldReleases] - .map((r) => r.linkedEvents) - .nonNulls - .expand((_) => _); + final groupedDeprecatedReleases = + deprecatedReleases.groupListsBy((r) => r.app.value!); + for (final e in groupedDeprecatedReleases.entries) { + final mostRecentRelease = + e.value.sorted((a, b) => b.createdAt!.compareTo(a.createdAt!)).first; + releases.add(mostRecentRelease); + } + // End deprecated + + final metadataIds = + releases.map((r) => r.linkedEvents).nonNulls.expand((_) => _); final userIds = { for (final app in apps) app.signer.id, @@ -206,7 +214,7 @@ mixin AppAdapter on Adapter { // Metadata and users probably go to separate relays // so query in parallel - final rs = await Future.wait([ + await Future.wait([ ref.fileMetadata.findAll(params: { 'ids': metadataIds, '#m': [kAndroidMimeType], @@ -214,7 +222,7 @@ mixin AppAdapter on Adapter { }), ref.users.findAll(params: {'authors': userIds}), ]); - await ref.localApps.localAppAdapter.updateInstallStatus(); + await ref.localApps.localAppAdapter.refreshUpdateStatus(); return apps; } @@ -235,7 +243,7 @@ mixin AppAdapter on Adapter { params.remove('installed'); final apps = await fetchAppModels(params); // Once apps are loaded, check for installed status - await ref.localApps.localAppAdapter.updateInstallStatus(); + await ref.localApps.localAppAdapter.refreshUpdateStatus(); return apps; } } @@ -269,7 +277,7 @@ mixin AppAdapter on Adapter { return null; } await ref.localApps.localAppAdapter - .updateInstallStatus(appId: id.toString()); + .refreshUpdateStatus(appId: id.toString()); return apps.first; } @@ -348,7 +356,7 @@ class ErrorInstallProgress extends AppInstallProgress { } final installationProgressProvider = - StateProvider.family( + StateProvider.family( (_, arg) => IdleInstallProgress()); final appsToUpdateProvider = StateProvider((_) => 0); diff --git a/lib/models/local_app.dart b/lib/models/local_app.dart index 9dfa543..3587111 100644 --- a/lib/models/local_app.dart +++ b/lib/models/local_app.dart @@ -39,7 +39,7 @@ class LocalApp extends DataModel { } mixin LocalAppAdapter on Adapter { - Future updateInstallStatus({String? appId}) async { + Future refreshUpdateStatus({String? appId}) async { if (!Platform.isAndroid) { return; } @@ -55,18 +55,24 @@ mixin LocalAppAdapter on Adapter { : installedPackageInfos.map((i) => i.packageName).nonNulls; final localApps = findManyLocalByIds(ids); + final apps = ref.apps.appAdapter.findWhereIdInLocal(ids); for (final i in installedPackageInfos) { final appId = i.packageName!; - final localApp = localApps.firstWhereOrNull((app) => appId == app.id) ?? - LocalApp(id: i.packageName!); + final app = apps.firstWhereOrNull((a) => a.identifier == appId); + final installedVersion = i.versionName; final installedVersionCode = i.versionCode; - final app = ref.apps.appAdapter.findWhereIdInLocal([appId]).firstOrNull; final status = - determineInstallStatus(app, installedVersion, installedVersionCode); + determineUpdateStatus(app, installedVersion, installedVersionCode); + if (status == null) { + continue; + } + + final localApp = localApps.firstWhereOrNull((app) => appId == app.id) ?? + LocalApp(id: i.packageName!); localApp .copyWith( installedVersion: installedVersion, @@ -81,18 +87,16 @@ mixin LocalAppAdapter on Adapter { ref.read(appsToUpdateProvider.notifier).state = rs.first['c']; } - AppInstallStatus? determineInstallStatus( + AppInstallStatus? determineUpdateStatus( App? app, String? installedVersion, int? installedVersionCode) { if (app == null || app.releases.isEmpty || app.latestMetadata == null) { return null; } - if (installedVersion == null) { - return AppInstallStatus.installable; + if (installedVersion == null || installedVersionCode == null) { + return null; } var comp = 0; - if (app.latestMetadata!.versionCode != null && - installedVersionCode != null && - app.id != 'store.zap.app') { + if (app.latestMetadata!.versionCode != null && app.id != 'store.zap.app') { // Note: need to exclude zap.store because development versions always // carry a lower version code (e.g. 12) than published ones (e.g. 2012) comp = app.latestMetadata!.versionCode!.compareTo(installedVersionCode); @@ -107,24 +111,10 @@ mixin LocalAppAdapter on Adapter { // else it's a downgrade, which is not installable return AppInstallStatus.downgrade; } - - @override - LocalApp deserializeLocal(Map map, {String? key}) { - // TODO: implement deserializeLocal - return super.deserializeLocal(map, key: key); - } - - @override - Map serializeLocal(LocalApp model, - {bool withRelationships = true}) { - final z = super.serializeLocal(model, withRelationships: withRelationships); - return z; - } } enum AppInstallStatus { updated, updatable, - installable, downgrade, } diff --git a/lib/models/local_app.g.dart b/lib/models/local_app.g.dart index 228015c..ae78276 100644 --- a/lib/models/local_app.g.dart +++ b/lib/models/local_app.g.dart @@ -63,6 +63,5 @@ Map _$LocalAppToJson(LocalApp instance) => { const _$AppInstallStatusEnumMap = { AppInstallStatus.updated: 'updated', AppInstallStatus.updatable: 'updatable', - AppInstallStatus.installable: 'installable', AppInstallStatus.downgrade: 'downgrade', }; diff --git a/lib/navigation/router.dart b/lib/navigation/router.dart index b38224c..8a26c92 100644 --- a/lib/navigation/router.dart +++ b/lib/navigation/router.dart @@ -163,10 +163,11 @@ final dataLibrariesInitializer = FutureProvider((ref) async { ]); // Trigger app install status calculations + ref.localApps.localAppAdapter.refreshUpdateStatus(); // do not await _lifecycleListener = AppLifecycleListener( onStateChange: (state) async { if (state == AppLifecycleState.resumed) { - await ref.localApps.localAppAdapter.updateInstallStatus(); + await ref.localApps.localAppAdapter.refreshUpdateStatus(); } }, ); diff --git a/lib/widgets/install_button.dart b/lib/widgets/install_button.dart index dc99e76..5d5db8e 100644 --- a/lib/widgets/install_button.dart +++ b/lib/widgets/install_button.dart @@ -27,14 +27,14 @@ class InstallButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final progress = ref.watch(installationProgressProvider(app.identifier!)); + final progress = ref.watch(installationProgressProvider(app.id!)); final status = app.localApp.value?.status; return GestureDetector( onTap: switch (status) { AppInstallStatus.downgrade => null, AppInstallStatus.updated => () { - packageManager.openApp(app.id!.toString()); + packageManager.openApp(app.identifier!); }, _ => switch (progress) { IdleInstallProgress() => () { @@ -56,10 +56,8 @@ class InstallButton extends ConsumerWidget { ErrorInstallProgress(:final e) => () { // show error and reset state to idle context.showError((e as dynamic).message); - ref - .read(installationProgressProvider(app.id!.toString()) - .notifier) - .state = IdleInstallProgress(); + ref.read(installationProgressProvider(app.id!).notifier).state = + IdleInstallProgress(); }, _ => null, }