From 334ac8d3d661a27d79dc046b1f62fbe0fbeacc60 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Fri, 7 Apr 2023 01:54:14 -0400 Subject: [PATCH] Use app deep copy in places to avoid bugs --- lib/main.dart | 2 +- lib/pages/app.dart | 2 +- lib/pages/apps.dart | 63 +++++++++++++++------------- lib/providers/apps_provider.dart | 7 +++- lib/providers/settings_provider.dart | 3 +- lib/providers/source_provider.dart | 16 +++++++ pubspec.yaml | 2 +- 7 files changed, 62 insertions(+), 33 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index eeb10de..c64b04f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; // ignore: implementation_imports import 'package:easy_localization/src/localization.dart'; -const String currentVersion = '0.11.27'; +const String currentVersion = '0.11.28'; const String currentReleaseTag = 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES diff --git a/lib/pages/app.dart b/lib/pages/app.dart index 867e3f1..bd9e510 100644 --- a/lib/pages/app.dart +++ b/lib/pages/app.dart @@ -38,7 +38,7 @@ class _AppPageState extends State { bool areDownloadsRunning = appsProvider.areDownloadsRunning(); var sourceProvider = SourceProvider(); - AppInMemory? app = appsProvider.apps[widget.appId]; + AppInMemory? app = appsProvider.apps[widget.appId]?.deepCopy(); var source = app != null ? sourceProvider.getSource(app.app.url) : null; if (!areDownloadsRunning && prevApp == null && app != null) { prevApp = app; diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index 733e8b2..201df76 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -29,13 +29,13 @@ class AppsPageState extends State { final AppsFilter neutralFilter = AppsFilter(); var updatesOnlyFilter = AppsFilter(includeUptodate: false, includeNonInstalled: false); - Set selectedApps = {}; + Set selectedAppIds = {}; DateTime? refreshingSince; clearSelected() { - if (selectedApps.isNotEmpty) { + if (selectedAppIds.isNotEmpty) { setState(() { - selectedApps.clear(); + selectedAppIds.clear(); }); return true; } @@ -43,10 +43,10 @@ class AppsPageState extends State { } selectThese(List apps) { - if (selectedApps.isEmpty) { + if (selectedAppIds.isEmpty) { setState(() { for (var a in apps) { - selectedApps.add(a); + selectedAppIds.add(a.id); } }); } @@ -57,20 +57,20 @@ class AppsPageState extends State { var appsProvider = context.watch(); var settingsProvider = context.watch(); var sourceProvider = SourceProvider(); - var listedApps = appsProvider.apps.values.toList(); + var listedApps = appsProvider.getAppValues().toList(); var currentFilterIsUpdatesOnly = filter.isIdenticalTo(updatesOnlyFilter, settingsProvider); - selectedApps = selectedApps - .where((element) => listedApps.map((e) => e.app).contains(element)) + selectedAppIds = selectedAppIds + .where((element) => listedApps.map((e) => e.app.id).contains(element)) .toSet(); toggleAppSelected(App app) { setState(() { - if (selectedApps.contains(app)) { - selectedApps.remove(app); + if (selectedAppIds.map((e) => e).contains(app.id)) { + selectedAppIds.removeWhere((a) => a == app.id); } else { - selectedApps.add(app); + selectedAppIds.add(app.id); } }); } @@ -143,15 +143,15 @@ class AppsPageState extends State { var existingUpdates = appsProvider.findExistingUpdates(installedOnly: true); var existingUpdateIdsAllOrSelected = existingUpdates - .where((element) => selectedApps.isEmpty + .where((element) => selectedAppIds.isEmpty ? listedApps.where((a) => a.app.id == element).isNotEmpty - : selectedApps.map((e) => e.id).contains(element)) + : selectedAppIds.map((e) => e).contains(element)) .toList(); var newInstallIdsAllOrSelected = appsProvider .findExistingUpdates(nonInstalledOnly: true) - .where((element) => selectedApps.isEmpty + .where((element) => selectedAppIds.isEmpty ? listedApps.where((a) => a.app.id == element).isNotEmpty - : selectedApps.map((e) => e.id).contains(element)) + : selectedAppIds.map((e) => e).contains(element)) .toList(); List trackOnlyUpdateIdsAllOrSelected = []; @@ -212,6 +212,11 @@ class AppsPageState extends State { : -1; }); + Set selectedApps = listedApps + .map((e) => e.app) + .where((a) => selectedAppIds.contains(a.id)) + .toSet(); + showChangeLogDialog( String? changesUrl, AppSource appSource, String changeLog, int index) { showDialog( @@ -288,7 +293,8 @@ class AppsPageState extends State { if (refreshingSince != null) SliverToBoxAdapter( child: LinearProgressIndicator( - value: appsProvider.apps.values + value: appsProvider + .getAppValues() .where((element) => !(element.app.lastUpdateCheck ?.isBefore(refreshingSince!) ?? true)) @@ -467,7 +473,8 @@ class AppsPageState extends State { .colorScheme .primary .withOpacity(listedApps[index].app.pinned ? 0.2 : 0.1), - selected: selectedApps.contains(listedApps[index].app), + selected: + selectedAppIds.map((e) => e).contains(listedApps[index].app.id), onLongPress: () { toggleAppSelected(listedApps[index].app); }, @@ -497,7 +504,7 @@ class AppsPageState extends State { ])) : trailingRow, onTap: () { - if (selectedApps.isNotEmpty) { + if (selectedAppIds.isNotEmpty) { toggleAppSelected(listedApps[index].app); } else { Navigator.push( @@ -534,7 +541,7 @@ class AppsPageState extends State { } getSelectAllButton() { - return selectedApps.isEmpty + return selectedAppIds.isEmpty ? TextButton.icon( style: const ButtonStyle(visualDensity: VisualDensity.compact), onPressed: () { @@ -548,17 +555,17 @@ class AppsPageState extends State { : TextButton.icon( style: const ButtonStyle(visualDensity: VisualDensity.compact), onPressed: () { - selectedApps.isEmpty + selectedAppIds.isEmpty ? selectThese(listedApps.map((e) => e.app).toList()) : clearSelected(); }, icon: Icon( - selectedApps.isEmpty + selectedAppIds.isEmpty ? Icons.select_all_outlined : Icons.deselect_outlined, color: Theme.of(context).colorScheme.primary, ), - label: Text(selectedApps.length.toString())); + label: Text(selectedAppIds.length.toString())); } getMassObtainFunction() { @@ -706,7 +713,7 @@ class AppsPageState extends State { builder: (BuildContext ctx) { return AlertDialog( title: Text(tr('markXSelectedAppsAsUpdated', - args: [selectedApps.length.toString()])), + args: [selectedAppIds.length.toString()])), content: Text( tr('onlyWorksWithNonVersionDetectApps'), style: const TextStyle( @@ -760,7 +767,7 @@ class AppsPageState extends State { items: const [], initValid: true, message: tr('installStatusOfXWillBeResetExplanation', - args: [plural('app', selectedApps.length)]), + args: [plural('app', selectedAppIds.length)]), ); }); if (values != null) { @@ -836,7 +843,7 @@ class AppsPageState extends State { children: [ IconButton( visualDensity: VisualDensity.compact, - onPressed: selectedApps.isEmpty + onPressed: selectedAppIds.isEmpty ? null : () { appsProvider.removeAppsWithModal( @@ -848,7 +855,7 @@ class AppsPageState extends State { IconButton( visualDensity: VisualDensity.compact, onPressed: getMassObtainFunction(), - tooltip: selectedApps.isEmpty + tooltip: selectedAppIds.isEmpty ? tr('installUpdateApps') : tr('installUpdateSelectedApps'), icon: const Icon( @@ -856,13 +863,13 @@ class AppsPageState extends State { )), IconButton( visualDensity: VisualDensity.compact, - onPressed: selectedApps.isEmpty ? null : launchCategorizeDialog(), + onPressed: selectedAppIds.isEmpty ? null : launchCategorizeDialog(), tooltip: tr('categorize'), icon: const Icon(Icons.category_outlined), ), IconButton( visualDensity: VisualDensity.compact, - onPressed: selectedApps.isEmpty ? null : showMoreOptionsDialog, + onPressed: selectedAppIds.isEmpty ? null : showMoreOptionsDialog, tooltip: tr('more'), icon: const Icon(Icons.more_horiz), ), diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index f24dbd9..13fa426 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -34,6 +34,8 @@ class AppInMemory { AppInfo? installedInfo; AppInMemory(this.app, this.downloadProgress, this.installedInfo); + AppInMemory deepCopy() => + AppInMemory(app.deepCopy(), downloadProgress, installedInfo); } class DownloadedApk { @@ -97,6 +99,8 @@ class AppsProvider with ChangeNotifier { late Stream? foregroundStream; late StreamSubscription? foregroundSubscription; + Iterable getAppValues() => apps.values.map((a) => a.deepCopy()); + AppsProvider() { // Subscribe to changes in the app foreground status foregroundStream = FGBGEvents.stream.asBroadcastStream(); @@ -667,7 +671,8 @@ class AppsProvider with ChangeNotifier { bool onlyIfExists = true}) async { attemptToCorrectInstallStatus = attemptToCorrectInstallStatus && (await doesInstalledAppsPluginWork()); - for (var app in apps) { + for (var a in apps) { + var app = a.deepCopy(); AppInfo? info = await getInstalledInfo(app.id); app.name = info?.name ?? app.name; if (app.additionalSettings['appName']?.toString().isNotEmpty == true) { diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 7c3dcff..541b2e4 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -164,7 +164,8 @@ class SettingsProvider with ChangeNotifier { void setCategories(Map cats, {AppsProvider? appsProvider}) { if (appsProvider != null) { - List changedApps = appsProvider.apps.values + List changedApps = appsProvider + .getAppValues() .map((a) { var n1 = a.app.categories.length; a.app.categories.removeWhere((c) => !cats.keys.contains(c)); diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index 181d423..5e8f595 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -80,6 +80,22 @@ class App { return 'ID: $id URL: $url INSTALLED: $installedVersion LATEST: $latestVersion APK: $apkUrls PREFERREDAPK: $preferredApkIndex ADDITIONALSETTINGS: ${additionalSettings.toString()} LASTCHECK: ${lastUpdateCheck.toString()} PINNED $pinned'; } + App deepCopy() => App( + id, + url, + author, + name, + installedVersion, + latestVersion, + apkUrls, + preferredApkIndex, + Map.from(additionalSettings), + lastUpdateCheck, + pinned, + categories: categories, + changeLog: changeLog, + releaseDate: releaseDate); + factory App.fromJson(Map json) { var source = SourceProvider().getSource(json['url']); var formItems = source.combinedAppSpecificSettingFormItems diff --git a/pubspec.yaml b/pubspec.yaml index d9a9f6c..160c2da 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.11.27+149 # When changing this, update the tag in main() accordingly +version: 0.11.28+150 # When changing this, update the tag in main() accordingly environment: sdk: '>=2.18.2 <3.0.0'